const DDMMYYYY = /^(\d{2})-(\d{2})-(\d{4})$/; export function parseOccurredAt(value?: string): Date { const m = value?.match(DDMMYYYY); if (!m) { throw new Error(`Expense occurred_at is not DD-MM-YYYY: ${value}`); } const [, dd, mm, yyyy] = m; const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd)); if ( Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() !== Number(mm) - 1 || d.getFullYear() !== Number(yyyy) ) { throw new Error(`Invalid expense occurred_at date: ${value}`); } return d; } export function isDdmmyyyy(value?: string): boolean { if (!value) return true; const m = value.match(DDMMYYYY); if (!m) return false; const [, dd, mm, yyyy] = m; const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd)); return !( Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() !== Number(mm) - 1 || d.getFullYear() !== Number(yyyy) ); } export function parseDdmmyyyy(value: string): Date { const m = value.match(DDMMYYYY); if (!m) throw new Error(`Date is not DD-MM-YYYY: ${value}`); const [, dd, mm, yyyy] = m; const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd)); if ( Number.isNaN(d.getTime()) || d.getDate() !== Number(dd) || d.getMonth() !== Number(mm) - 1 || d.getFullYear() !== Number(yyyy) ) { throw new Error(`Invalid date: ${value}`); } return d; } export function monthKey(value?: string): string { const d = parseOccurredAt(value); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; } export function currentMonthKey(): string { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; } export function monthLabel(key: string): string { const [y, m] = key.split("-").map(Number); if (!y || !m) return key; const d = new Date(y, m - 1, 1); return d.toLocaleDateString("en-IN", { month: "long", year: "numeric" }); } export function dateLabel(value: string): string { const d = parseOccurredAt(value); return d.toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" }); }