/** Returns the ISO date string (YYYY-MM-DD) of the Monday for the week containing `date`. */ export function getWeekStart(date: Date): string { const d = new Date(date); const day = d.getDay(); // 0=Sun, 1=Mon, ... const diff = day === 0 ? -6 : 1 - day; d.setDate(d.getDate() + diff); return formatDate(d); } /** Adds `n` weeks to a week-start date string and returns the new week-start. */ export function addWeeks(weekStart: string, n: number): string { const d = parseDate(weekStart); d.setDate(d.getDate() + n * 7); return formatDate(d); } /** Adds `n` days to a date string and returns the new date. */ export function addDays(dateStr: string, n: number): string { const d = parseDate(dateStr); d.setDate(d.getDate() + n); return formatDate(d); } /** Returns an array of 7 ISO date strings for the week starting at `weekStart` (Monday). */ export function getWeekDays(weekStart: string): string[] { const d = parseDate(weekStart); const days: string[] = []; for (let i = 0; i < 7; i++) { const day = new Date(d); day.setDate(d.getDate() + i); days.push(formatDate(day)); } return days; } /** Returns a human-readable heading like "Monday, April 7th". */ export function formatDayHeading(dateStr: string): string { const d = parseDate(dateStr); const weekday = d.toLocaleDateString(undefined, { weekday: "long" }); const month = d.toLocaleDateString(undefined, { month: "long" }); const day = d.getDate(); return `${weekday}, ${month} ${ordinal(day)}`; } /** Returns a short day label like "Mon" and day number like "7". */ export function formatShortDay(dateStr: string): { label: string; day: number } { const d = parseDate(dateStr); return { label: d.toLocaleDateString(undefined, { weekday: "short" }), day: d.getDate(), }; } /** Returns the month + year label like "April 2026". */ export function formatMonthYear(dateStr: string): string { const d = parseDate(dateStr); return d.toLocaleDateString(undefined, { month: "long", year: "numeric" }); } /** Compact range for a Mon–Sun week, e.g. "Apr 6 – 12, 2026" or spanning two months. */ export function formatWeekRangeLabel(weekStart: string): string { const start = parseDate(weekStart); const end = new Date(start); end.setDate(end.getDate() + 6); const sameMonth = start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear(); if (sameMonth) { const month = start.toLocaleDateString(undefined, { month: "short" }); return `${month} ${start.getDate()} – ${end.getDate()}, ${start.getFullYear()}`; } return `${start.toLocaleDateString(undefined, { month: "short", day: "numeric" })} – ${end.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })}`; } /** Checks if a date string is today. */ export function isToday(dateStr: string): boolean { return dateStr === formatDate(new Date()); } /** Formats a Date to YYYY-MM-DD. */ function formatDate(d: Date): string { const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } /** Parses a YYYY-MM-DD string to a local Date. */ function parseDate(dateStr: string): Date { const parts = dateStr.split("-").map(Number); return new Date(parts[0]!, parts[1]! - 1, parts[2]!); } function ordinal(n: number): string { const s = ["th", "st", "nd", "rd"] as const; const v = n % 100; return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]); }