Frontend: render timestamps in the instance timezone (Intl), once there's a display #42

Closed
opened 2026-06-05 13:16:50 +00:00 by logaritmisk · 1 comment
Owner

Context

The instance-locale milestone exposed default_timezone (IANA name, e.g. Europe/Stockholm)
via GET /api/config + useConfig(). It is deliberately not consumed in the UI yet — there
are currently no human-facing timestamp displays (the only date shown is recording_date, a
plain DATE with no time/zone). Building a formatter now would be dead code (YAGNI).

This issue tracks wiring it up when the first timestamp display lands.

Ask

When the UI starts showing UTC timestamps (e.g. an audit-history view, object created_at/
updated_at, or similar):

  • Add a small formatTimestamp(utcIso, timeZone, locale) helper using
    Intl.DateTimeFormat(locale, { timeZone }), reading timeZone from useConfig().default_timezone
    and locale from the active i18n language.
  • Render all UTC timestamps through it (storage/transmission stay UTC — this is display-only).
  • Guard against an invalid IANA string (Intl throws) — fall back to UTC display rather than crash.

Related

  • Server-side timestamp formatting (no browser) is owned by the PDF export (#39), which will
    need the same default_timezone value plus a Rust tz library.

Notes

  • The config value already exists; this is purely a display-layer task with no backend change.
## Context The instance-locale milestone exposed `default_timezone` (IANA name, e.g. `Europe/Stockholm`) via `GET /api/config` + `useConfig()`. It is deliberately **not consumed in the UI yet** — there are currently no human-facing timestamp displays (the only date shown is `recording_date`, a plain `DATE` with no time/zone). Building a formatter now would be dead code (YAGNI). This issue tracks wiring it up when the first timestamp display lands. ## Ask When the UI starts showing UTC timestamps (e.g. an audit-history view, object `created_at`/ `updated_at`, or similar): - Add a small `formatTimestamp(utcIso, timeZone, locale)` helper using `Intl.DateTimeFormat(locale, { timeZone })`, reading `timeZone` from `useConfig().default_timezone` and `locale` from the active i18n language. - Render all UTC timestamps through it (storage/transmission stay UTC — this is display-only). - Guard against an invalid IANA string (Intl throws) — fall back to UTC display rather than crash. ## Related - Server-side timestamp formatting (no browser) is owned by the PDF export (#39), which will need the same `default_timezone` value plus a Rust tz library. ## Notes - The config value already exists; this is purely a display-layer task with no backend change.
Author
Owner

Done — merged to main (fe44803).

Added a shared formatTimestamp(value, timeZone, locale) helper (web/src/lib/format-timestamp.ts) that renders a UTC ISO timestamp as date + short time in the instance timezone + active locale, via Intl.DateTimeFormat. The objects-table "Updated" column now routes through it — previously it was an inline, date-only, unguarded Intl.DateTimeFormat that would have thrown a RangeError and crashed the table on a misconfigured default_timezone.

Key points:

  • Invalid-IANA guard: a bad timeZone falls back to UTC formatting (try/catch) rather than crashing.
  • Edge handling mirrors the sibling lib/format-date.ts: null → "—", non-string → String(value), unparseable → returned unchanged.
  • Display-only — storage/transmission stay UTC. No backend change, no new dependency, no new i18n keys.
  • 4 unit tests incl. the load-bearing timezone day-shift (02:00 UTC = prev-day in America/New_York) and the invalid-zone no-throw case.

Gate green: 293 tests pass, largest chunk 90.2 KB gz (budget 250), check:colors clean.

Future timestamp displays (object-detail created/updated, audit history, when they land) route through the same helper.

Done — merged to `main` (fe44803). Added a shared `formatTimestamp(value, timeZone, locale)` helper (`web/src/lib/format-timestamp.ts`) that renders a UTC ISO timestamp as **date + short time** in the instance timezone + active locale, via `Intl.DateTimeFormat`. The objects-table "Updated" column now routes through it — previously it was an inline, date-only, unguarded `Intl.DateTimeFormat` that would have thrown a `RangeError` and crashed the table on a misconfigured `default_timezone`. Key points: - **Invalid-IANA guard:** a bad `timeZone` falls back to UTC formatting (try/catch) rather than crashing. - Edge handling mirrors the sibling `lib/format-date.ts`: `null` → "—", non-string → `String(value)`, unparseable → returned unchanged. - **Display-only** — storage/transmission stay UTC. No backend change, no new dependency, no new i18n keys. - 4 unit tests incl. the load-bearing timezone day-shift (02:00 UTC = prev-day in `America/New_York`) and the invalid-zone no-throw case. Gate green: 293 tests pass, largest chunk 90.2 KB gz (budget 250), `check:colors` clean. Future timestamp displays (object-detail created/updated, audit history, when they land) route through the same helper.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#42