// How dates are DISPLAYED. Nothing here touches what is stored. // // The log keeps ADIF/ISO throughout — the database, ADIF export, LoTW, every // upload. This is the reading layer only, because an operator reading // "2026-07-30" as the 7th of the 30th month is reading their own log wrongly, // and that is a display problem, not a data one. // // Storage stays ISO deliberately and permanently: it sorts correctly as text, // it is what ADIF specifies, and a log whose stored format followed a UI // preference would become unreadable the day the preference changed. import { writeUiPref } from '@/lib/uiPref'; export type DateFormat = 'iso' | 'fr' | 'us'; const KEY = 'opslog.dateFormat'; function read(): DateFormat { const v = localStorage.getItem(KEY); return v === 'fr' || v === 'us' ? v : 'iso'; } let current: DateFormat = read(); // A minimal store so every grid and panel re-renders the moment the format // changes, rather than showing the old one until the next restart. const listeners = new Set<() => void>(); export function getDateFormat(): DateFormat { return current; } export function setDateFormat(f: DateFormat): void { current = f; writeUiPref(KEY, f); listeners.forEach((l) => l()); } export function subscribeDateFormat(fn: () => void): () => void { listeners.add(fn); return () => listeners.delete(fn); } // reloadDateFormat re-reads the stored value — for after the portable prefs // have been synced from the database at startup, which happens AFTER this // module's first read. export function reloadDateFormat(): void { const v = read(); if (v !== current) { current = v; listeners.forEach((l) => l()); } } const pad = (n: number) => String(n).padStart(2, '0'); // order arranges the three parts. Separators stay '-' in every format: a slash // in the French one would be conventional but it also invites the reader to // take it for a US date, which is exactly the confusion being fixed. function order(y: number, m: number, d: number): string { switch (current) { case 'fr': return `${pad(d)}-${pad(m)}-${y}`; case 'us': return `${pad(m)}-${pad(d)}-${y}`; } return `${y}-${pad(m)}-${pad(d)}`; } // formatDateTimeUTC renders a timestamp with the time, in UTC. // // UTC is not negotiable and is not part of the preference: a logbook is kept in // UTC, and a date shown in local time would disagree with the QSO's own record // twice a year. export function formatDateTimeUTC(s: unknown): string { if (!s) return ''; const d = new Date(String(s)); if (isNaN(d.getTime())) return String(s); return `${order(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; } // formatDateOnly renders a date with no time. It accepts both the ADIF form // (YYYYMMDD, used by every QSL and upload date) and anything Date can parse. export function formatDateOnly(s: unknown): string { if (!s) return ''; const t = String(s).trim(); const m = t.match(/^(\d{4})(\d{2})(\d{2})/); if (m) return order(Number(m[1]), Number(m[2]), Number(m[3])); const d = new Date(t); if (isNaN(d.getTime())) return t; return order(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()); }