export type DateLike = string | number | Date | null | undefined; const hasTimezoneDesignator = (value: string): boolean => { return /[zZ]$/.test(value) || /[+-]\d{2}:?\d{2}$/.test(value); }; const truncateExcessFraction = (value: string): string => { // Keep at most 3 fractional digits for milliseconds return value.replace(/(\.\d{3})\d+/, '$1'); }; export const parseUtcDate = (input: DateLike): Date => { if (input == null) return new Date(Number.NaN); if (input instanceof Date) return new Date(input.getTime()); if (typeof input === 'number') { const millis = input < 1e12 ? input * 1000 : input; return new Date(millis); } const raw = String(input).trim(); if (!raw) return new Date(Number.NaN); // Numeric strings (epoch seconds or millis) if (/^\d{10}$/.test(raw)) return new Date(Number(raw) * 1000); if (/^\d{13}$/.test(raw)) return new Date(Number(raw)); // Already has timezone; trust the browser parser if (hasTimezoneDesignator(raw)) return new Date(raw); // Normalize common forms: "YYYY-MM-DD HH:MM:SS[.fff...]" -> ISO-like let norm = raw.replace(' ', 'T'); norm = truncateExcessFraction(norm); // Date-only string -> treat as UTC midnight if (/^\d{4}-\d{2}-\d{2}$/.test(norm)) { return new Date(`${norm}T00:00:00Z`); } // If ISO-like without timezone, explicitly mark as UTC if (/^\d{4}-\d{2}-\d{2}T/.test(norm) && !hasTimezoneDesignator(norm)) { return new Date(`${norm}Z`); } // Fallback to native parsing return new Date(norm); }; export const formatLocalDate = (value: DateLike, options?: Intl.DateTimeFormatOptions): string => { const d = value instanceof Date ? value : parseUtcDate(value); if (Number.isNaN(d.getTime())) return 'N/A'; return d.toLocaleDateString(undefined, options); }; export const formatLocalDateTime = (value: DateLike, options?: Intl.DateTimeFormatOptions): string => { const d = value instanceof Date ? value : parseUtcDate(value); if (Number.isNaN(d.getTime())) return 'N/A'; const base: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZoneName: 'short', }; return d.toLocaleString(undefined, { ...base, ...(options || {}) }); };