Skip to content

Honor the Display date/time preference everywhere - #102

Open
ndandan wants to merge 6 commits into
Shoshuo:mainfrom
ndandan:pr/display-date-preference
Open

Honor the Display date/time preference everywhere#102
ndandan wants to merge 6 commits into
Shoshuo:mainfrom
ndandan:pr/display-date-preference

Conversation

@ndandan

@ndandan ndandan commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #54.

The Display settings already offer date format (French / American / ISO), time format (24h/12h) and timezone, and the prismarr_date / prismarr_time / prismarr_datetime filters already implement them — but ~40 template call sites still rendered hardcoded European d/m/Y formats, three of them with a hardcoded Europe/Paris timezone, and the client-side formatters ignored the preference entirely. This PR closes the gap:

  • Server sweep: every user-facing |date('d/m…')-family call (34 templates) now uses the preference-aware filters; the three Europe/Paris args are gone (the series air-date keeps its weekday via display_pref('timezone')). formatTime/formatDateTime gain an opt-in withSeconds flag so log/system pages keep their seconds (this also fixes a pre-existing H:i:ss double-seconds typo). Internal uses (Y-m-d bucketing keys, data-* ISO attrs, copyright years) are deliberately untouched.
  • Client sweep: base.html.twig exposes the prefs (window._prismarrDatePrefs) plus three tiny formatters that mirror the Twig filters exactly (same us/iso output, same · separator, same no-leading-zero 12h, invalid dates → ), defined in the head block because some pages format dates during initial parse. The page-local formatters on the qBittorrent/Deluge/Transmission/Prowlarr/Jellyseerr/films pages — several of which hardcoded fr-FR — now delegate to them, so poller-rebuilt rows match the server-rendered ones. The prefs re-stamp on every document, so a settings change takes effect without a hard reload. A final chore commit drops the var LOCALE locals this orphaned.

Two deliberate behavior notes:

  • Null dates previously rendered as today (Twig's |date(null) behavior); they now render empty — truthful rather than fabricated.
  • Date-only payloads (e.g. release dates) are now converted into the display timezone, so they can legitimately shift a day for users whose display timezone differs from the server's — same behavior as Radarr's own UI.

Known follow-up (left out as non-mechanical): the calendar page hand-builds dd/mm/yyyy in JS.

Tests: seconds-flag unit tests (DisplayPreferencesServiceTest, 16/16), lint:twig across all 159 templates, smoke suite green — full suite on this branch: 779/779. Verified live: torrent Added columns, tooltips, log pages and the settings round-trip all follow the preference, in all three date formats.

🤖 Generated with Claude Code

ndandan and others added 6 commits August 22, 2026 20:07
formatTime()/formatDateTime() gain an opt-in `bool $withSeconds = false`
so log and command tables can render second precision while every
existing caller keeps its seconds-free output unchanged.

The |prismarr_time / |prismarr_datetime Twig filters pass the flag
through, giving templates `|prismarr_time(true)`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ream Shoshuo#54

Server-rendered dates were hardcoded to the French d/m/Y layout (and, in
three series templates, to a literal Europe/Paris), so the Display
date-format / time-format / timezone preferences had no effect on most
pages. Every user-facing call site now goes through |prismarr_date,
|prismarr_datetime or |prismarr_time, which honour all three.

Per-format mapping:
  'd/m/Y H:i'   (20x) -> |prismarr_datetime
  'd/m/Y'        (8x) -> |prismarr_date
  'd/m/y'        (6x) -> |prismarr_date
  'd/m H:i:s'    (4x) -> |prismarr_datetime(true)
  'H:i:ss'       (4x) -> |prismarr_time(true)   (upstream double-seconds typo)
  'd/m/Y H:i:s'  (2x) -> |prismarr_datetime(true)
  'd/m H:i'      (2x) -> |prismarr_datetime
  'M j, Y'       (1x) -> |prismarr_date
  'D d/m H:i'    (1x) -> |date('D', tz) + |prismarr_datetime (keeps the weekday)

Internal, non-display uses are deliberately left alone: the 'Y-m-d'
grouping keys in admin/settings and dashboard/index, the 'N'/'H' weekday
and hour lookups, the 'c' data-attributes in media/series, the three
copyright "now"|date("Y") lines, and the year placeholder in
radarr/exclusions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e — upstream Shoshuo#54

Rows rebuilt by a poller formatted their dates off the UI locale (torrent
pages, Prowlarr history) or a hardcoded fr-FR (Jellyseerr tasks, film
history), so they disagreed with the now preference-aware server-rendered
markup. base.html.twig gains window._prismarrFmtDate / _prismarrFmtTime /
_prismarrFmtDateTime plus the _prismarrDatePrefs payload, and the eight
page-local formatters defer to them.

The helpers live in the <head> script next to window.prismarrBytes rather
than beside window._prismarrToast: the toast block renders around line
2560, well after `{% block javascripts %}` at line 1683, and
films.html.twig's renderQueue() calls the ETA formatter during its initial
parse — defining them later would race with the first call exactly as the
prismarrBytes comment documents. Verified by asserting the render order.

Callers use the globals unguarded, matching the existing
`function fmtSize(bytes) { return window.prismarrBytes(bytes); }` idiom on
these same pages. Times use the browser timezone (unchanged behaviour);
only the format becomes a preference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The JS globals added for Shoshuo#54 mirrored the Twig filters only approximately,
so a poller rewriting an element Twig had already rendered could change how
it read. Four divergences, all in the base.html.twig globals block:

- _prismarrFmtDateTime joined with ' ' where formatDateTime() joins with
  ' · '. Jellyseerr tasks_cache server-renders #next-<jobId> and then
  rewrites that same element from JS, so the separator visibly vanished.
- the 'us' path used a bare toLocaleDateString('en-US') → "4/21/2026",
  but the app's 'us' format is 'M j, Y' → "Apr 21, 2026".
- 12h times padded the hour ("02:30 PM") where PHP 'g' does not ("2:30 PM").
- the 'iso' path emitted "NaN-NaN-NaN" for an Invalid Date; callers only
  guard truthiness, so all three helpers now return the pages' em-dash.

Verified against DisplayPreferencesServiceTest's pinned expectations: the
formatters now emit 21/04/2026, Apr 21, 2026, 2026-04-21, 14:30, 14:30:07,
2:30 PM, 2:30:07 PM and "2026-04-21 · 14:30" — matching the service
exactly, separator included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e + live prefs refresh

Two findings from the final whole-branch review, both Shoshuo#54 regressions.

1. The Commit B/C sweep missed a SECOND pair of hand-rolled formatters on
   each torrent page: fmtShortDate (dd/mm/yy) and fmtLongDate
   (dd/mm/YYYY HH:mm), alongside the fmtDate already converted. They build
   the very cells Commit B made preference-aware, so the Added column showed
   two formats at once depending on whether a row was patched or rebuilt —
   and disagreed even under the default fr preference (21/04/2026 from Twig
   vs 21/04/26 from JS). Both now delegate to _prismarrFmtDate /
   _prismarrFmtDateTime in all three templates, keeping each page's existing
   falsy-timestamp guard.

   fmtLongDate and fmtDate are deliberately NOT collapsed: they now differ
   only in their guards (fmtDate returns '-' for `!ts || ts < 0`, fmtLongDate
   returns an em-dash for `!ts`), so merging them would change the rendered
   placeholder at one set of call sites rather than falling out for free.

2. _prismarrDatePrefs was assigned inside the `if (!window._prismarrFmtDate)`
   guard, so a Turbo-navigated document kept the stale prefs after an admin
   changed the preference while the server-rendered dates around it updated —
   reintroducing exactly the drift Shoshuo#54 exists to remove. The payload is now
   re-stamped on every document; the pure function bodies stay guarded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ters

The three torrent pages each declared `var LOCALE = app.request.locale` for
one purpose only: picking 'en-US' vs 'fr-FR' inside their hand-rolled date
formatters. Those formatters now delegate to _prismarrFmtDate /
_prismarrFmtDateTime, which read the Display preference instead, leaving
LOCALE declared and unreferenced on all three pages.

Verified unreferenced before removal: the only remaining `LOCALE` match in
qbittorrent/, deluge/ and transmission/index.html.twig was the declaration
itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ndandan added a commit to ndandan/Prismarr that referenced this pull request Aug 28, 2026
Fork follow-ups from the Shoshuo#54/Shoshuo#35/Shoshuo#47/Shoshuo#71 upstream issue set:
- calendar tooltip date now honors the Display date preference (was
  hardcoded dd/mm/yyyy; the Shoshuo#54-sweep gap noted in PR Shoshuo#102)
- getTimezone() validates the stored zone at the source so raw Twig
  date(tz) sites (series episode rows, admin preview) can't fatal on a
  corrupted value
- consolidated the duplicated safeInfoUrl guard into App\Util\SafeUrl

Beta live-verified (calendar tooltip 'Jul 27, 2026' us; series episode
rows 'Thu Aug 27, 2026 · 7:00 PM'; console clean). Full suite green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Date and Time Formatting

1 participant