backups: show timestamps in the site timezone
Backup timestamps read wrong because of two faults stacked, which is why it
looked like a single offset.
The API serialised naive ISO ("2026-08-07T12:00:00"), with nothing saying the
value was UTC. JavaScript's new Date() parses that as BROWSER-LOCAL, so every
timestamp shifted by the viewer's offset before any timezone formatting ran.
Every datetime this plugin stores is naive UTC, so the wire format now carries
a trailing Z.
The history view then formatted with toLocaleString(), i.e. the viewer's zone,
ignoring the site_timezone setting entirely. It now loads that setting and
formats through the shared formatInZone helper, matching NotificationsList.
The panel list label is built server-side with strftime, so a client cannot
correct it afterwards. It now converts to the site zone using the same Setting
lookup the notifications plugin uses - without that it showed UTC, four hours
out at West Jefferson.
Tests cover the wire format and that 16:30Z renders as 12:30 in
America/New_York.
This commit is contained in:
@@ -669,3 +669,44 @@ def test_a_kind_that_sets_emptytext_still_gets_it():
|
||||
|
||||
def test_base_kind_defaults_to_hiding_when_empty():
|
||||
assert registry.BackupKind.emptytext is None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Timestamp wire format
|
||||
# =============================================================================
|
||||
|
||||
def test_timestamps_are_serialised_as_utc(bk_app, bk_plugin):
|
||||
"""Naive ISO is parsed as BROWSER-LOCAL by JavaScript, silently shifting
|
||||
every timestamp by the viewer's offset before any site-timezone formatting
|
||||
runs. The wire format has to say the value is UTC."""
|
||||
from plugins.backups.models import BackupRevision
|
||||
with bk_app.app_context():
|
||||
result = bk_plugin.apply_collector_payload(
|
||||
_payload(collectedat='2026-08-07T12:00:00Z'))
|
||||
revision = _db.session.get(BackupRevision, result['backuprevisionid'])
|
||||
data = revision.to_dict()
|
||||
assert data['collectedat'].endswith('Z')
|
||||
assert data['createdat'].endswith('Z')
|
||||
# 12:00Z stored naive-UTC, so it round-trips as the same wall clock.
|
||||
assert data['collectedat'].startswith('2026-08-07T12:00:00')
|
||||
|
||||
|
||||
def test_utciso_helper_passes_none_through():
|
||||
from plugins.backups.models.backup import _utciso
|
||||
assert _utciso(None) is None
|
||||
|
||||
|
||||
def test_panel_label_is_rendered_in_the_site_zone(bk_app, bk_plugin):
|
||||
"""The label is baked server-side, so the client cannot correct it later.
|
||||
A UTC afternoon is the morning of the same day at West Jefferson."""
|
||||
from shopdb.core.models import Setting
|
||||
from plugins.backups.models import BackupRevision
|
||||
from plugins.backups.api.routes import _label
|
||||
with bk_app.app_context():
|
||||
_db.session.add(Setting(key='site_timezone', value='America/New_York'))
|
||||
_db.session.commit()
|
||||
result = bk_plugin.apply_collector_payload(
|
||||
_payload(collectedat='2026-08-07T16:30:00Z'))
|
||||
revision = _db.session.get(BackupRevision, result['backuprevisionid'])
|
||||
# 16:30 UTC on 2026-08-07 is 12:30 EDT.
|
||||
assert '12:30' in _label(revision)
|
||||
|
||||
Reference in New Issue
Block a user