157 lines
5.2 KiB
TypeScript
157 lines
5.2 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|