/** * Centralized date-window constants and membership helpers per TZ ยง4.1.2. * * All date arguments are "yyyyMMdd" strings (TZ URL date format). * Bounds are inclusive on both ends. Day-level comparison (time zeroed). */ export const BOARD_WINDOW_DAYS_BACK = 1; export const BOARD_WINDOW_DAYS_FORWARD = 14; export const SCHEDULE_WINDOW_DAYS_BACK = 1; export const SCHEDULE_WINDOW_DAYS_FORWARD = 330; export const MAP_WINDOW_DAYS_BACK = 1; export const MAP_WINDOW_MONTHS_FORWARD = 6; function today(): Date { const d = new Date(); d.setHours(0, 0, 0, 0); return d; } function addDays(base: Date, days: number): Date { const d = new Date(base); d.setDate(d.getDate() + days); return d; } function addMonths(base: Date, months: number): Date { const d = new Date(base); d.setMonth(d.getMonth() + months); return d; } function parseYyyymmdd(s: string): Date | null { if (!/^\d{8}$/.test(s)) return null; const y = Number(s.slice(0, 4)); const m = Number(s.slice(4, 6)); const d = Number(s.slice(6, 8)); if (m < 1 || m > 12 || d < 1 || d > 31) return null; const dt = new Date(y, m - 1, d); if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) return null; return dt; } function inRange(date: Date, min: Date, max: Date): boolean { return date.getTime() >= min.getTime() && date.getTime() <= max.getTime(); } export function boardWindowBounds(): [Date, Date] { const base = today(); return [addDays(base, -BOARD_WINDOW_DAYS_BACK), addDays(base, BOARD_WINDOW_DAYS_FORWARD)]; } export function scheduleWindowBounds(): [Date, Date] { const base = today(); return [addDays(base, -SCHEDULE_WINDOW_DAYS_BACK), addDays(base, SCHEDULE_WINDOW_DAYS_FORWARD)]; } export function mapWindowBounds(): [Date, Date] { const base = today(); return [addDays(base, -MAP_WINDOW_DAYS_BACK), addMonths(base, MAP_WINDOW_MONTHS_FORWARD)]; } export function isInBoardWindow(yyyymmdd: string): boolean { const d = parseYyyymmdd(yyyymmdd); if (!d) return false; const [min, max] = boardWindowBounds(); return inRange(d, min, max); } export function isInScheduleWindow(yyyymmdd: string): boolean { const d = parseYyyymmdd(yyyymmdd); if (!d) return false; const [min, max] = scheduleWindowBounds(); return inRange(d, min, max); } export function isInMapWindow(yyyymmdd: string): boolean { const d = parseYyyymmdd(yyyymmdd); if (!d) return false; const [min, max] = mapWindowBounds(); return inRange(d, min, max); }