export function startOfMonth(d: Date) { return new Date(d.getFullYear(), d.getMonth(), 1); } export function endOfMonth(d: Date) { return new Date(d.getFullYear(), d.getMonth() + 1, 0, 23, 59, 59, 999); } export function addMonths(d: Date, n: number) { return new Date(d.getFullYear(), d.getMonth() + n, 1); } export function sameDay(a: Date, b: Date) { return ( a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() ); } export function toDateKey(d: Date) { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; } export function parseDateKey(key: string) { const [y, m, day] = key.split("-").map(Number); return new Date(y, m - 1, day); } /** Monday-first calendar grid cells (includes leading/trailing days). */ export function buildMonthGrid(month: Date): Date[] { const start = startOfMonth(month); const end = endOfMonth(month); const startPad = (start.getDay() + 6) % 7; const cells: Date[] = []; const gridStart = new Date(start); gridStart.setDate(start.getDate() - startPad); for (let i = 0; i < 42; i++) { const cell = new Date(gridStart); cell.setDate(gridStart.getDate() + i); cells.push(cell); if (i >= 27 && cell >= end && cell.getDay() === 0) break; } return cells; } export function formatMonthYear(d: Date) { return d.toLocaleDateString("en-US", { month: "long", year: "numeric" }); } export function matchDisplayDate(iso: string | null) { if (!iso) return "TBD"; return new Date(iso).toLocaleString(undefined, { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }