import type { ScheduleWindow } from '../domain/configuration/configuration.js' /** * Checks if the current time (or provided time) falls within the schedule window. * Returns true if no schedule is defined (no restriction). * * Handles: * - IANA timezone conversion (uses the timezone to get local hour and day) * - Midnight-spanning windows (startHour > endHour means spans midnight) * - Day-of-week filtering (daysOfWeek array, 0=Sunday) */ export function isWithinSchedule(schedule: ScheduleWindow | undefined, now?: Date): boolean { if (!schedule) { return true } const currentTime = now ?? new Date() // Get the local hour and day in the schedule's timezone const localParts = getLocalTimeParts(currentTime, schedule.timezone) const localHour = localParts.hour const localDay = localParts.dayOfWeek // 0=Sunday // Check day-of-week filter if (schedule.daysOfWeek.length > 0 && !schedule.daysOfWeek.includes(localDay)) { return false } // Check time window const { startHour, endHour } = schedule if (startHour <= endHour) { // Normal window (e.g., 2-6 means 02:00 to 06:00) return localHour >= startHour && localHour < endHour } else { // Midnight-spanning window (e.g., 22-6 means 22:00 to 06:00 next day) return localHour >= startHour || localHour < endHour } } /** * Gets the local hour and day-of-week for a given Date in a specific IANA timezone. */ function getLocalTimeParts( date: Date, timezone: string ): { hour: number; dayOfWeek: number } { const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone, hour: 'numeric', weekday: 'short', hour12: false, }) const parts = formatter.formatToParts(date) let hour = 0 let dayOfWeek = 0 for (const part of parts) { if (part.type === 'hour') { // Intl may return "24" for midnight in some locales, normalize to 0 hour = Number.parseInt(part.value, 10) % 24 } if (part.type === 'weekday') { dayOfWeek = weekdayToNumber(part.value) } } return { hour, dayOfWeek } } /** * Converts a short weekday name (from Intl) to a number (0=Sunday). */ function weekdayToNumber(weekday: string): number { const map: Record = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6, } return map[weekday] ?? 0 }