/** * Timezone utility functions for consistent date handling across the application. * * All dates should be: * 1. Stored in the backend as UTC ISO strings * 2. Displayed to users in their saved timezone * 3. Input from users interpreted in their saved timezone * * The user's timezone is stored in the database and should be fetched from the dashboard. */ /** * Get today's date in the user's timezone as YYYY-MM-DD format. * This should be used for date inputs to ensure consistency with user's timezone. * * @param userTimezone - IANA timezone string (e.g., "America/New_York") * @returns Date string in YYYY-MM-DD format */ export function getTodayInTimezone(userTimezone: string): string { const now = new Date(); // Use Intl.DateTimeFormat to get the date in the user's timezone const formatter = new Intl.DateTimeFormat('en-CA', { // en-CA gives YYYY-MM-DD format timeZone: userTimezone, year: 'numeric', month: '2-digit', day: '2-digit', }); return formatter.format(now); } /** * Convert a date input string (YYYY-MM-DD) to an ISO string that represents * midnight in the user's timezone. * * This should be used when sending date-only data to the backend. * * @param dateString - Date in YYYY-MM-DD format * @param userTimezone - IANA timezone string * @returns ISO string representing midnight in the user's timezone */ export function dateStringToUTCMidnight(dateString: string, userTimezone: string): string { // Parse the date string as-is (YYYY-MM-DD) const [year, month, day] = dateString.split('-').map(Number); // Create a date object representing midnight in the user's timezone // We format a string that includes timezone info const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T00:00:00`; // Get the date/time string in the user's timezone to calculate offset const tzDate = new Date(new Date(dateStr).toLocaleString('en-US', { timeZone: userTimezone })); const utcDate = new Date(new Date(dateStr).toLocaleString('en-US', { timeZone: 'UTC' })); const offset = tzDate.getTime() - utcDate.getTime(); // Create final date adjusted for timezone const adjustedDate = new Date(Date.UTC(year, month - 1, day, 0, 0, 0) - offset); return adjustedDate.toISOString(); } /** * Format an ISO date string for display in the user's timezone. * * @param isoString - ISO date string from backend * @param userTimezone - IANA timezone string * @param options - Intl.DateTimeFormatOptions for formatting * @returns Formatted date string */ export function formatDateInTimezone( isoString: string, userTimezone: string, options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' } ): string { const date = new Date(isoString); return new Intl.DateTimeFormat('en-US', { ...options, timeZone: userTimezone, }).format(date); } /** * Convert an ISO string to YYYY-MM-DD format in the user's timezone. * This is useful for populating date inputs. * * @param isoString - ISO date string from backend * @param userTimezone - IANA timezone string * @returns Date string in YYYY-MM-DD format */ export function isoToDateString(isoString: string, userTimezone: string): string { const date = new Date(isoString); const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: userTimezone, year: 'numeric', month: '2-digit', day: '2-digit', }); return formatter.format(date); } /** * Get the current date and time as an ISO string in UTC. * This should be used for timestamps (not date-only fields). * * @returns ISO string in UTC */ export function getCurrentTimestamp(): string { return new Date().toISOString(); } /** * Compare two date strings (YYYY-MM-DD) to determine if date1 is before date2. * This is timezone-safe because it compares date strings directly. * * @param date1 - First date string * @param date2 - Second date string * @returns true if date1 is before date2 */ export function isDateBefore(date1: string, date2: string): boolean { return date1 < date2; } /** * Compare two date strings (YYYY-MM-DD) to determine if date1 is after date2. * This is timezone-safe because it compares date strings directly. * * @param date1 - First date string * @param date2 - Second date string * @returns true if date1 is after date2 */ export function isDateAfter(date1: string, date2: string): boolean { return date1 > date2; } /** * Add days to a date string, accounting for the user's timezone. * * @param dateString - Date in YYYY-MM-DD format * @param days - Number of days to add * @param userTimezone - IANA timezone string * @returns New date string in YYYY-MM-DD format */ export function addDaysToDate(dateString: string, days: number, userTimezone: string): string { const baseISO = dateStringToUTCMidnight(dateString, userTimezone); const base = new Date(baseISO); base.setUTCDate(base.getUTCDate() + days); return isoToDateString(base.toISOString(), userTimezone); } /** * Get the user's browser timezone as a fallback. * This should only be used when the backend timezone is not available. * * @returns IANA timezone string */ export function getBrowserTimezone(): string { return Intl.DateTimeFormat().resolvedOptions().timeZone; }