fix: a calendar date is the same day everywhere; legal pages use the shared language picker

formatDate read a date-only value as UTC midnight and showed it in the viewer's zone, so the legal pages' last update read a day early west of UTC. Date-only values are shown in UTC. The legal pages' hand-rolled language buttons are the landing page's LanguageSwitcher. formatDateTime and formatDate get the two-language test formatNumber already had.
This commit is contained in:
2026-09-17 03:53:40 +02:00
parent 7789db8240
commit 791ba5f507
5 changed files with 49 additions and 22 deletions
+32
View File
@@ -1,6 +1,8 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
RELATIVE_UNITS,
formatDate,
formatDateTime,
formatNumber,
formatRuntime,
formatTotalHours,
@@ -191,3 +193,33 @@ describe("formatRuntime", () => {
}
});
});
// Dates had the same bug as numbers (a bare `toLocaleString()`), and the same trap for a test: the
// runtime's own locale and time zone are the dev box's, so both languages — and a zone west of UTC —
// are pinned explicitly.
describe("formatDateTime / formatDate", () => {
const savedTz = process.env.TZ;
afterEach(() => {
i18nStub.language = "en";
if (savedTz === undefined) delete process.env.TZ;
else process.env.TZ = savedTz;
});
it("formats a timestamp in the UI language, not in the runtime's", () => {
const iso = "2026-09-17T08:30:14Z";
const asEnglish = formatDateTime(iso);
i18nStub.language = "hu";
const asHungarian = formatDateTime(iso);
expect(asEnglish).toBe(new Date(iso).toLocaleString("en"));
expect(asHungarian).toBe(new Date(iso).toLocaleString("hu"));
expect(asEnglish).not.toBe(asHungarian);
expect(formatDate(iso, "en")).not.toBe(formatDate(iso, "hu"));
});
it("shows a date-only value as that calendar day in every time zone", () => {
process.env.TZ = "America/New_York";
expect(formatDate("2026-09-17", "en")).toBe("Sep 17, 2026");
process.env.TZ = "Pacific/Kiritimati";
expect(formatDate("2026-09-17", "en")).toBe("Sep 17, 2026");
});
});
+11 -3
View File
@@ -176,8 +176,16 @@ export function formatDateTime(iso: string | null | undefined): string {
}
// Absolute day-precision date (e.g. "12 Jan 2017"), shown alongside a relative time. Empty for null.
// A DATE-only value ("2026-09-17": a release, the legal pages' last update) is a calendar day, not an
// instant: `new Date` reads it as UTC midnight, which is still the day before west of UTC, so it is
// shown in UTC. A timestamp is shown in the viewer's own zone, as it should be.
export function formatDate(iso: string | null | undefined, lang: string): string {
return iso
? new Date(iso).toLocaleDateString(lang, { year: "numeric", month: "short", day: "numeric" })
: "";
if (!iso) return "";
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(iso);
return new Date(iso).toLocaleDateString(lang, {
year: "numeric",
month: "short",
day: "numeric",
...(dateOnly ? { timeZone: "UTC" } : {}),
});
}