mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 04:40:40 -07:00
fix(ui): parse naive-UTC timestamps consistently in formatAbsoluteDate (#953)
* fix(ui): parse naive-UTC timestamps consistently in formatAbsoluteDate Backend timestamps are naive UTC (Python `datetime.utcnow()`) and are serialized without a timezone suffix. `formatDate` already normalizes these by appending `Z` before parsing, but `formatAbsoluteDate` called `new Date(date)` directly. Per the ES spec, a timezone-less date-time string is parsed as local time, so absolute timestamps were shown off by the viewer's UTC offset (e.g. +9h in JST) — and disagreed with the relative time rendered by `formatDate` for the same value (visible in the Captures detail panel, which uses both on `capture.created_at`). Extract the normalization into a shared `parseServerDate` helper and use it in both formatters. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * docs(format): clarify parseServerDate comment on date-only vs date-time parsing ECMAScript parses date-only strings ("2026-07-23") as UTC but timezone-less date-time strings ("2026-07-23T10:00:00") as local time. The backend emits the latter, which is the case this helper normalizes. Corrects the comment per PR review feedback. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * docs(format): trim parseServerDate comment to match surrounding style Reduce the multi-line explanation to a single why-comment consistent with other utils comments. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
committed by
Jamie Pine
co-authored by
Claude Opus 4.8
parent
abe16de6ee
commit
f4d7c86ec0
+14
-13
@@ -23,27 +23,28 @@ function getDateLocale() {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDate(date: string | Date): string {
|
||||
let dateObj: Date;
|
||||
if (typeof date === 'string') {
|
||||
const dateStr = date.trim();
|
||||
if (!dateStr.includes('Z') && !dateStr.match(/[+-]\d{2}:\d{2}$/)) {
|
||||
dateObj = new Date(`${dateStr}Z`);
|
||||
} else {
|
||||
dateObj = new Date(dateStr);
|
||||
}
|
||||
} else {
|
||||
dateObj = date;
|
||||
// Backend timestamps are naive UTC — append `Z` so JS doesn't parse a
|
||||
// timezone-less date-time string as local time.
|
||||
function parseServerDate(date: string | Date): Date {
|
||||
if (typeof date !== 'string') {
|
||||
return date;
|
||||
}
|
||||
const dateStr = date.trim();
|
||||
if (!dateStr.includes('Z') && !dateStr.match(/[+-]\d{2}:\d{2}$/)) {
|
||||
return new Date(`${dateStr}Z`);
|
||||
}
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
return formatDistance(dateObj, new Date(), {
|
||||
export function formatDate(date: string | Date): string {
|
||||
return formatDistance(parseServerDate(date), new Date(), {
|
||||
addSuffix: true,
|
||||
locale: getDateLocale(),
|
||||
}).replace(/^about /i, '');
|
||||
}
|
||||
|
||||
export function formatAbsoluteDate(date: string | Date): string {
|
||||
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
||||
const dateObj = parseServerDate(date);
|
||||
return dateObj.toLocaleString(i18n.language, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
||||
Reference in New Issue
Block a user