fix(tz): server-side timezone conversion for all user-facing time displays

The audit showed only 5 of ~11 time-display surfaces converted to the
user's timezone; everything else rendered pgx-decoded UTC. The server
owns conversion — clients (web/app/KOReader) receive display-ready
strings or machine-readable instants and never convert.

- Progress page: LastUpdated formatted in the requesting user's tz
  (was raw UTC).
- Analytics: date-range defaults, day buckets, most-active-day, device
  last-sync, and popular-book last-read all computed in the user's tz —
  evening sessions no longer land on the next UTC day. Also fixed the
  end-date boundary: the query bounds with <= and the end bound was
  midnight, so the end date's own sessions (including 'today') never
  appeared.
- Queue page: new created_at_display field (admin tz) rendered by the
  template; created_at stays RFC3339 for API consumers.
- Admin hash-conflicts SSR bridge: detected date rendered in the
  admin's tz.
- OPDS: .UTC() before the Z-suffixed feed timestamps (honesty fix —
  correct today only because the session zone happens to be UTC).
- KOReader annotation datetimes deliberately untouched (device owns its
  display).
This commit is contained in:
John O'Keefe committed 2026-10-13 13:59:49 -04:00
1 parent 740213f0a4
commit 1601548e6c
8 files changed
+75 -24

No files matched your search

+1 -1
View File
@@ -140,7 +140,7 @@ templ Queue(user User, queueItems []handlers.QueueItemResponse, stats handlers.Q
</div>
<div class="flex justify-between items-center text-sm" style="color: var(--text-secondary)">
<span>Attempts: { item.Attempts }/{ item.MaxAttempts }</span>
<span>{ item.CreatedAt }</span>
<span>{ item.CreatedAtDisplay }</span>
</div>
if item.ErrorMessage != nil {
<div class="mt-2 text-sm flex items-start gap-2" style="color: var(--status-danger);">
+2 -2
View File
@@ -244,9 +244,9 @@ func Queue(user User, queueItems []handlers.QueueItemResponse, stats handlers.Qu
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(item.CreatedAt)
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(item.CreatedAtDisplay)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `queue.templ`, Line: 143, Col: 30}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `queue.templ`, Line: 143, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {