import { addCalendarDays, viewerTimeZone, zonedWallClockToInstant } from '../practiceTimezone'; /* * THE BUSY TIMES A PICKER READS FOR A DAY ARE THAT DAY ON THE CLOCK IT DRAWS THE DAY ON. * * Both booking pickers asked for a selected date's held slots with a date-only `new Date('YYYY-MM-DD')`, which a * browser parses as UTC midnight. The day read was the UTC day, ending at 7 PM in Chicago: a 7:15 PM hold * was never read, so its time stayed offered after it was booked. Measured on the demo booking walk. * * The window is the date's local day from its own midnight to the next one, so a 23- or 25-hour day * across a DST change is read whole. With more than one clock (the patient's picker is drawn on the * patient's clock while the visit belongs to the practice's), it spans the local day on each of them. * A clock this runtime cannot keep time in is skipped; with none left, the viewer's own is used. */ const keepsTime = (zone: string): boolean => { try { new Intl.DateTimeFormat('en-US', { timeZone: zone }); return true; } catch { return false; } }; export function slotReadWindow( dateStr: string, clocks: (string | null | undefined)[], fallback: string = viewerTimeZone(), ): { start: string; end: string } { const usable = clocks.filter((zone): zone is string => typeof zone === 'string' && zone.trim() !== '' && keepsTime(zone)); const zones = usable.length > 0 ? usable : [fallback]; const next = addCalendarDays(dateStr, 1); const starts = zones.map((zone) => zonedWallClockToInstant(dateStr, '00:00', zone).getTime()); const ends = zones.map((zone) => zonedWallClockToInstant(next, '00:00', zone).getTime()); return { start: new Date(Math.min(...starts)).toISOString(), end: new Date(Math.max(...ends)).toISOString() }; }