/** * Returns the ISO Monday (YYYY-MM-DD) for the week containing `date`. */ export function getWeekStart(date: Date): string { const d = new Date(date); const day = d.getUTCDay(); // 0=Sun, 1=Mon, … const diff = day === 0 ? -6 : 1 - day; // shift to Monday d.setUTCDate(d.getUTCDate() + diff); return d.toISOString().slice(0, 10); } /** * Returns the Monday of the previous week relative to `weekStart`. */ export function prevWeek(weekStart: string): string { const d = new Date(weekStart + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() - 7); return d.toISOString().slice(0, 10); } /** * Returns the Monday of the next week relative to `weekStart`. */ export function nextWeek(weekStart: string): string { const d = new Date(weekStart + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() + 7); return d.toISOString().slice(0, 10); } /** * Formats a date string (YYYY-MM-DD) as a localized day abbreviation. */ export function formatDayAbbr(dateStr: string, length: 'narrow' | 'short' = 'narrow'): string { const d = new Date(dateStr + 'T00:00:00Z'); return d.toLocaleDateString('de-DE', { weekday: length, timeZone: 'UTC' }); } /** * Returns an array of 7 date strings for the week starting on `weekStart`. */ export function weekDays(weekStart: string): string[] { const days: string[] = []; for (let i = 0; i < 7; i++) { const d = new Date(weekStart + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() + i); days.push(d.toISOString().slice(0, 10)); } return days; } /** * Formats a date string as "Mo, 30.03." style label. */ export function formatDayLabel(dateStr: string): string { const d = new Date(dateStr + 'T00:00:00Z'); const day = d.toLocaleDateString('de-DE', { weekday: 'short', timeZone: 'UTC' }); const date = d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', timeZone: 'UTC' }); return `${day}, ${date}`; } /** * Formats a date string as "30. März" style label. */ export function formatDayFull(dateStr: string): string { const d = new Date(dateStr + 'T00:00:00Z'); return d.toLocaleDateString('de-DE', { day: 'numeric', month: 'long', timeZone: 'UTC' }); } /** * Returns true if dateStr is today (UTC date). * Uses UTC consistently with all other date functions in this module. */ export function isToday(dateStr: string): boolean { const todayStr = new Date().toISOString().slice(0, 10); return dateStr === todayStr; } const EFFORT_ORDER: Record = { easy: 0, medium: 1, hard: 2 }; /** * Returns a new array of recipes sorted easiest first (effort ASC, cookTimeMin ASC). * Used for the J4 mid-week swap context — different from variety-first sorting in J2. */ export function sortEasiestFirst( recipes: T[] ): T[] { return [...recipes].sort((a, b) => { const ea = a.effort != null ? (EFFORT_ORDER[a.effort] ?? 99) : 99; const eb = b.effort != null ? (EFFORT_ORDER[b.effort] ?? 99) : 99; if (ea !== eb) return ea - eb; const ta = a.cookTimeMin ?? Infinity; const tb = b.cookTimeMin ?? Infinity; return ta - tb; }); } /** * Formats a week range: "30. Mär – 5. Apr 2026". */ export function formatWeekRange(weekStart: string): string { const start = new Date(weekStart + 'T00:00:00Z'); const end = new Date(weekStart + 'T00:00:00Z'); end.setUTCDate(end.getUTCDate() + 6); const startStr = start.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', timeZone: 'UTC' }); const endStr = end.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); return `${startStr} – ${endStr}`; }