Show device timestamps in the reader's local time
CI / Test (pull_request) Successful in 48s
Release Drafter / Update release notes draft (pull_request) Successful in 3s

The exact issuance and expiry times in the device rows' hover titles were
rendered in UTC, which reads as wrong to anyone not living there.

- Mark the dates with a <time> element carrying the absolute (UTC) timestamp,
  rather than a custom data attribute whose name claimed to hold local time
- Rewrite those titles in the browser's own zone, keeping the server's UTC
  rendering as the fallback for when JavaScript is unavailable
- Only convert values that carry an explicit offset, so a date-only <time> is
  not read as UTC midnight and shifted onto the previous day
- Give the revoke button a little more room from the device name and dates

Part of #254
This commit is contained in:
raucao committed 2026-11-03 18:28:14 +04:00
1 parent faa7bf6146
commit 17bad40bf4
6 files changed
+68 -11

No files matched your search

+2 -1
View File
@@ -1,4 +1,5 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "turbo_stream_animations"
import "turbo_stream_animations"
import "local_time"
+32
View File
@@ -0,0 +1,32 @@
// Timestamps are rendered by the server in UTC, next to a <time> element's
// machine-readable value. Rewrite those elements' hover titles so they show the
// reader's own local time instead.
const LOCAL_TIME_FORMAT = new Intl.DateTimeFormat("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hourCycle: "h23",
})
// Only absolute timestamps carry a zone to convert from: a date-only value would
// be read as UTC midnight and shift to the previous day for most readers.
const ABSOLUTE_TIMESTAMP = /T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})$/
const localizeTimestamps = () => {
document.querySelectorAll("time[datetime]").forEach((element) => {
const value = element.getAttribute("datetime")
if (!ABSOLUTE_TIMESTAMP.test(value)) return
const timestamp = new Date(value)
if (!Number.isNaN(timestamp.getTime())) {
element.title = LOCAL_TIME_FORMAT.format(timestamp)
}
})
}
document.addEventListener("turbo:load", localizeTimestamps)