diff --git a/Web App/web/src/lib/format.js b/Web App/web/src/lib/format.js index f5cda20..451b1f6 100644 --- a/Web App/web/src/lib/format.js +++ b/Web App/web/src/lib/format.js @@ -92,9 +92,27 @@ export function formatTime(value) { const d = value instanceof Date ? value : new Date(value); if (isNaN(d)) return "—"; const opts = { hour: "2-digit", minute: "2-digit" }; - if (prefs.timeFormat === "24") opts.hourCycle = "h23"; - else if (prefs.timeFormat === "12") opts.hourCycle = "h12"; - return d.toLocaleTimeString(prefs.locale || undefined, opts); + const mode = prefs.timeFormat; + if (mode !== "24" && mode !== "12") { + // Auto is the region's, whole: 13.45 is how Danish writes a clock, and a + // setting that says "follow the region" has no business arguing with it. + return d.toLocaleTimeString(prefs.locale || undefined, opts); + } + + // Asking for a clock outright is asking for its separator too. Somebody who + // picked "24-hour" means 13:45, not the 13.45 their region would have written + // — the point of leaving auto was to stop the region deciding. Only the mark + // between the hour and the minute is pinned: the rest is still the locale's, + // including whether there is an am/pm marker, what it reads and where it sits. + opts.hourCycle = mode === "24" ? "h23" : "h12"; + const parts = new Intl.DateTimeFormat(prefs.locale || undefined, opts).formatToParts(d); + return parts + .map((p, i) => + p.type === "literal" && parts[i - 1]?.type === "hour" && parts[i + 1]?.type === "minute" + ? ":" + : p.value + ) + .join(""); } // Every number we render goes through here so the grouping separator follows