fix: treat naive backend timestamps as UTC for relative labels
Build / build (push) Successful in 10m35s

Backend emits store.Now() as "2026-04-23 14:05:32" — UTC but without a
Z marker or T separator. JavaScript parses such strings as local time,
so every "N ago" label is skewed by the browser's UTC offset (3h off
in UTC+3, causing the visible mismatch with the tooltip).

Normalise bare/space-separated timestamps to UTC inside toDate() so
every $fmt.* call, including relative, matches the absolute timestamp
shown in the tooltip. InstanceCard drops its own parser and delegates
to $fmt.relative for consistency.
This commit is contained in:
2026-04-23 14:39:41 +03:00
parent 90e6e59d9e
commit 03d58a072c
2 changed files with 20 additions and 12 deletions
+18 -1
View File
@@ -18,6 +18,23 @@ export type DateInput = string | number | Date | null | undefined;
const PLACEHOLDER = '—';
/**
* Backend emits `store.Now()` as `"2006-01-02 15:04:05"` (UTC, but no `Z`).
* Bare ISO strings without a zone (e.g. `"2026-04-23T14:05:32"`) show up too.
* JavaScript interprets both as **local** time, which silently skews every
* relative label ("3h ago") by the user's UTC offset. Normalise anything
* that lacks an explicit zone marker to UTC before constructing the Date.
*/
function normalizeIsoUtc(input: string): string {
const trimmed = input.trim();
// Already has Z or ±HH:MM / ±HHMM offset → trust it.
if (/(Z|[+-]\d{2}:?\d{2})$/.test(trimmed)) return trimmed;
// SQLite-style "YYYY-MM-DD HH:MM:SS(.ffff)?" — swap space for T, append Z.
const sqliteMatch = trimmed.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)$/);
if (sqliteMatch) return `${sqliteMatch[1]}T${sqliteMatch[2]}Z`;
return trimmed;
}
function toDate(input: DateInput): Date | null {
if (input === null || input === undefined || input === '') return null;
if (input instanceof Date) return isNaN(input.getTime()) ? null : input;
@@ -28,7 +45,7 @@ function toDate(input: DateInput): Date | null {
const d = new Date(ms);
return isNaN(d.getTime()) ? null : d;
}
const d = new Date(input);
const d = new Date(normalizeIsoUtc(input));
return isNaN(d.getTime()) ? null : d;
}