import type { WeekDay } from './calendar.types'; /** Parse a Date or parseable string into a Date (throws on invalid). */ export function toDate(value: Date | string): Date { if (value instanceof Date) { const copy = new Date(value.getTime()); if (Number.isNaN(copy.getTime())) { throw new Error(`[42/calendar] Invalid date: ${String(value)}`); } return copy; } // Treat a bare `YYYY-MM-DD` as a *local* date (not UTC) to avoid day drift. const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); const date = match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(value); if (Number.isNaN(date.getTime())) { throw new Error(`[42/calendar] Invalid date: ${String(value)}`); } return date; } /** Midnight (local) of the given date. */ export function startOfDay(date: Date): Date { const out = new Date(date.getTime()); out.setHours(0, 0, 0, 0); return out; } /** `YYYY-MM-DD` key in local time. */ export function dateKey(date: Date): string { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } export function addDays(date: Date, days: number): Date { const out = new Date(date.getTime()); out.setDate(out.getDate() + days); return out; } export function addMonths(date: Date, months: number): Date { const out = new Date(date.getTime()); const targetDay = out.getDate(); out.setDate(1); out.setMonth(out.getMonth() + months); // Clamp to the last valid day of the resulting month. const lastDay = new Date(out.getFullYear(), out.getMonth() + 1, 0).getDate(); out.setDate(Math.min(targetDay, lastDay)); return out; } export function startOfMonth(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), 1); } export function startOfWeek(date: Date, weekStartsOn: WeekDay): Date { const out = startOfDay(date); const diff = (out.getDay() - weekStartsOn + 7) % 7; return addDays(out, -diff); } export function isSameDay(a: Date, b: Date): boolean { return ( a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() ); } export function isSameMonth(a: Date, b: Date): boolean { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth(); } /** Fraction (0..1) of the day represented by the time portion of `date`. */ export function dayFraction(date: Date): number { return (date.getHours() * 60 + date.getMinutes()) / 1440; }