/** Convert an absolute instant into weekday + minute-of-day in an IANA time zone. */ const JS_WEEKDAY: Record = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6, }; export function isValidIanaTimeZone(timeZone: string): boolean { if (!timeZone || timeZone.length > 64) { return false; } try { Intl.DateTimeFormat('en-US', { timeZone }).format(new Date(0)); return true; } catch { return false; } } export function zonedWeekdayAndMinutes( date: Date, timeZone: string, ): { jsWeekday: number; minuteOfDay: number } { const parts = new Intl.DateTimeFormat('en-US', { timeZone, weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', }).formatToParts(date); const weekdayToken = parts.find((p) => p.type === 'weekday')?.value ?? 'Sun'; let hour = Number(parts.find((p) => p.type === 'hour')?.value ?? '0'); const minute = Number(parts.find((p) => p.type === 'minute')?.value ?? '0'); if (hour === 24) { hour = 0; } return { jsWeekday: JS_WEEKDAY[weekdayToken] ?? 0, minuteOfDay: hour * 60 + minute, }; } /** Weekday of a YYYY-MM-DD civil date (Gregorian; same worldwide). */ export function civilDateJsWeekday(isoDate: string): number { const [y, m, d] = isoDate.split('-').map(Number); const utcNoon = new Date(Date.UTC(y, m - 1, d, 12, 0, 0, 0)); return utcNoon.getUTCDay(); }