/** * Shared date parsing for Stampli M/D/YY and MM/DD/YYYY formats. * Ported from payments-dashboard/src/dates.js — both formats must parse; * anything else returns null (never pass through garbage). */ export type ParsedMDY = { year: number; month: number; day: number; }; /** Parse "M/D/YY" or "MM/DD/YYYY" into { year, month, day }, or null. */ export function parseMDY(value: string | null | undefined): ParsedMDY | null { const match = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/.exec(String(value ?? "").trim()); if (!match) return null; const month = Number.parseInt(match[1]!, 10); const day = Number.parseInt(match[2]!, 10); let year = Number.parseInt(match[3]!, 10); if (match[3]!.length === 2) year += 2000; const dt = new Date(year, month - 1, day); if (dt.getFullYear() !== year || dt.getMonth() !== month - 1 || dt.getDate() !== day) { return null; } return { year, month, day }; } /** "YYYY-MM-DD" or null. */ export function toISODate(value: string | null | undefined): string | null { const p = parseMDY(value); if (!p) return null; return `${p.year}-${String(p.month).padStart(2, "0")}-${String(p.day).padStart(2, "0")}`; } /** Canonical "MM/DD/YYYY" or null. Stored form for send_payment_on / CSV. */ export function toCanonicalMDY(value: string | null | undefined): string | null { const p = parseMDY(value); if (!p) return null; return `${String(p.month).padStart(2, "0")}/${String(p.day).padStart(2, "0")}/${p.year}`; } /** * Plausibility window for ingested send dates. * Relative to the current year (7 years back, 2 years forward). */ export function isPlausibleSendYear( value: string | null | undefined, nowYear: number = new Date().getFullYear(), ): boolean { const p = parseMDY(value); if (p === null) return false; return p.year >= nowYear - 7 && p.year <= nowYear + 2; }