export const TZ = 'America/Toronto' /** YYYY-MM-DD for right now in Toronto */ export function todayToronto(): string { return toDateToronto(new Date()) } /** YYYY-MM-DD in Toronto for any Date */ export function toDateToronto(date: Date): string { return new Intl.DateTimeFormat('en-CA', { timeZone: TZ }).format(date) } /** * Add n calendar days to a YYYY-MM-DD string. * Uses noon UTC so the shift is always exactly 24 h and never crosses * a Toronto DST boundary ambiguously. */ export function addDays(isoDate: string, n: number): string { const d = new Date(isoDate + 'T12:00:00Z') d.setUTCDate(d.getUTCDate() + n) return d.toISOString().slice(0, 10) } /** HH:MM in Toronto for a UTC ISO string */ export function fmtTime(utcIso: string): string { return new Date(utcIso).toLocaleTimeString('en-CA', { timeZone: TZ, hour: '2-digit', minute: '2-digit', hour12: false, }) } /** "Wednesday, April 20" style label for a YYYY-MM-DD string */ export function fmtDateLong(isoDate: string): string { return new Date(isoDate + 'T12:00:00Z').toLocaleDateString('en-CA', { timeZone: TZ, weekday: 'long', month: 'long', day: 'numeric', }) } /** { weekday: "Wed", short: "Apr 20" } for a YYYY-MM-DD string */ export function fmtDayHeader(isoDate: string): { weekday: string; short: string } { const d = new Date(isoDate + 'T12:00:00Z') return { weekday: d.toLocaleDateString('en-CA', { timeZone: TZ, weekday: 'short' }), short: d.toLocaleDateString('en-CA', { timeZone: TZ, month: 'short', day: 'numeric' }), } } /** * YYYY-MM-DD of the Monday that starts the ISO week containing isoDate. * Uses the en-CA weekday name to determine day-of-week in Toronto. */ export function weekMonday(isoDate: string): string { const d = new Date(isoDate + 'T12:00:00Z') const wd = new Intl.DateTimeFormat('en-CA', { timeZone: TZ, weekday: 'short' }).format(d) const offsets: Record = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 } const offset = offsets[wd] ?? 0 const monday = new Date(d) monday.setUTCDate(d.getUTCDate() - offset) return monday.toISOString().slice(0, 10) } /** All 7 YYYY-MM-DD strings for the ISO week containing isoDate (Mon → Sun) */ export function weekDates(isoDate: string): string[] { const mon = weekMonday(isoDate) return Array.from({ length: 7 }, (_, i) => addDays(mon, i)) } /** * UTC range string pair that safely covers all Toronto-local timestamps * falling within the isoFrom…isoTo date range. * Adds a 1-day buffer on each side so client-side date filtering is authoritative. */ export function utcRange(isoFrom: string, isoTo: string): { from: string; to: string } { return { from: addDays(isoFrom, -1) + 'T00:00:00Z', to: addDays(isoTo, +1) + 'T23:59:59Z', } }