// The period a report row was counted over, carried to the list it opens: its days are the practice's, // on the clock the report used, never the viewer's. import { addCalendarDays, calendarDateInZone, zonedWallClockToInstant } from './practiceTimezone'; export interface DrillPeriod { from: string; to: string; zone: string } const DAY = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; const knownZone = (zone: string) => { try { new Intl.DateTimeFormat('en-US', { timeZone: zone }); return true; } catch { return false; } }; export const drillPeriodParams = (filters: { start: string; end: string }, zone: string) => ({ start: filters.start, end: filters.end, tz: zone }); export const drillPeriodFrom = (params: URLSearchParams): DrillPeriod | null => { const from = params.get('start'); const to = params.get('end'); const zone = params.get('tz'); if (!from || !to || !zone || !DAY.test(from) || !DAY.test(to) || from > to || !knownZone(zone)) return null; return { from, to, zone }; }; /** The instant a calendar day begins on the given clock, for a server-side search bound. */ export const dayStartIn = (day: string, zone: string): string => zonedWallClockToInstant(day, '00:00', zone).toISOString(); /** A FHIR `date` search covering the period's days on its clock: from the first day's midnight, before the day after the last. */ export const drillPeriodDateSearch = (period: DrillPeriod): string[] => ['ge' + dayStartIn(period.from, period.zone), 'lt' + dayStartIn(addCalendarDays(period.to, 1), period.zone)]; export const withinDrillPeriod = (instant: string | undefined, period: DrillPeriod): boolean => { if (!instant) return false; const at = new Date(instant); if (Number.isNaN(at.getTime())) return false; const day = calendarDateInZone(at, period.zone); return day >= period.from && day <= period.to; };