import { calendarDateInZone, viewerTimeZone, wallClockInZone, zonedWallClockToInstant } from '../practiceTimezone'; /* * THE PATIENT'S TIME LIST IS DRAWN ON THE PATIENT'S CLOCK (option B, Spencer 2026-08-10), so a picked * label such as "2:00 PM" is a time on that clock. The availability windows are real instants rendered in * the viewer's zone, and the held slots are checked the same way; the stored instant has to be read on * that clock too. Reading it as practice time stored a New York patient's 2:00 PM as 3:00 PM against a * Chicago practice, and let 11:00 AM land on a visit already held at noon. * * `clock` defaults to the viewer's zone and exists so a test can name one. */ export function pickedTimeInstant(dateStr: string, timeStr: string, clock: string = viewerTimeZone()): Date { return zonedWallClockToInstant(dateStr, timeStr, clock); } type HeldSlot = { start?: string; end?: string; status?: string }; const minutesOf = (hhmm: string): number => { const [hours, minutes] = hhmm.split(':').map(Number); return hours * 60 + minutes; }; /** * Busy [startMin, endMin) ranges, in minutes of the day on `clock`, for the slots that begin on `dateStr` * as read on that clock. The day is the patient's day, not the UTC one: an evening hold in New York is * already tomorrow in UTC and used to drop off the list it blocks. */ export function heldRangesOnDate( slots: HeldSlot[], dateStr: string, clock: string = viewerTimeZone(), ): { startMin: number; endMin: number }[] { return slots .filter((slot) => slot.start && String(slot.status ?? '').includes('busy')) .filter((slot) => calendarDateInZone(new Date(slot.start as string), clock) === dateStr) .map((slot) => { const start = new Date(slot.start as string); const startMin = minutesOf(wallClockInZone(start, clock)); if (!slot.end) return { startMin, endMin: startMin + 1 }; const end = new Date(slot.end); // A hold that runs past midnight blocks the rest of this day. const endMin = calendarDateInZone(end, clock) === dateStr ? minutesOf(wallClockInZone(end, clock)) : 24 * 60; return { startMin, endMin: Math.max(endMin, startMin + 1) }; }); }