final touches for beta skymoney (at least i think)
This commit is contained in:
156
web/src/utils/timezone.ts
Normal file
156
web/src/utils/timezone.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user