import { knownTimeZone } from "./cron.js"; /** Minutes after local midnight; `start` > `end` means the range crosses midnight. */ export interface MinuteRange { start: number; end: number; } /** The local calendar date ("YYYY-MM-DD") and minute of day at `now`, UTC when the zone is unknown. */ export function localDateAndMinute( now: Date, timeZone: string | null | undefined, ): { date: string; minute: number } { const parts = new Map( new Intl.DateTimeFormat("en-US", { timeZone: knownTimeZone(timeZone) ?? "UTC", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23", }) .formatToParts(now) .map((part) => [part.type, part.value]), ); return { date: `${parts.get("year")}-${parts.get("month")}-${parts.get("day")}`, minute: Number(parts.get("hour")) * 60 + Number(parts.get("minute")), }; } /** True when `minute` lies in the range; the start is inclusive, the end exclusive. */ export function isQuietMinute(range: MinuteRange | null, minute: number): boolean { if (!range || range.start === range.end) return false; return range.start < range.end ? minute >= range.start && minute < range.end : minute >= range.start || minute < range.end; } /** True when `now` falls in the quiet hours of a user in `timeZone`. */ export function isQuietTime( range: MinuteRange | null, timeZone: string | null | undefined, now: Date, ): boolean { return isQuietMinute(range, localDateAndMinute(now, timeZone).minute); }