From ac0f25c8fdff4139d25c1e67b4f2cbde5cd6a2b6 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Sun, 21 Jun 2026 22:59:25 +0800 Subject: [PATCH] fix(settings): format about release dates without timezone --- .../src/pages/settings/AboutSection.tsx | 8 +-- .../agent-gui/src/pages/settings/aboutDate.ts | 58 +++++++++++++++++++ .../test/settings/about-date.test.mjs | 18 ++++++ 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 crates/agent-gui/src/pages/settings/aboutDate.ts create mode 100644 crates/agent-gui/test/settings/about-date.test.mjs diff --git a/crates/agent-gui/src/pages/settings/AboutSection.tsx b/crates/agent-gui/src/pages/settings/AboutSection.tsx index 2680f78b..a53e7058 100644 --- a/crates/agent-gui/src/pages/settings/AboutSection.tsx +++ b/crates/agent-gui/src/pages/settings/AboutSection.tsx @@ -15,6 +15,7 @@ import { Button } from "../../components/ui/button"; import { useLocale } from "../../i18n"; import type { AppUpdateCheckResult, AppUpdateController } from "../../lib/appUpdates"; import { updateUpdateSettings } from "../../lib/settings"; +import { formatReleaseDate } from "./aboutDate"; import { AgentActivationSwitch } from "./shared"; import type { SettingsSectionProps } from "./types"; @@ -22,13 +23,6 @@ type AboutSectionProps = SettingsSectionProps & { appUpdate: AppUpdateController; }; -function formatReleaseDate(value?: string | null) { - if (!value) return ""; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return value; - return date.toLocaleString(); -} - function releaseTitle(result?: AppUpdateCheckResult) { if (!result) return ""; return result.releaseName?.trim() || result.releaseTag?.trim() || result.version || ""; diff --git a/crates/agent-gui/src/pages/settings/aboutDate.ts b/crates/agent-gui/src/pages/settings/aboutDate.ts new file mode 100644 index 00000000..1c4bcd5f --- /dev/null +++ b/crates/agent-gui/src/pages/settings/aboutDate.ts @@ -0,0 +1,58 @@ +const PORTABLE_RELEASE_DATE_PATTERN = + /^(\d{4})-(\d{1,2})-(\d{1,2})[ T](\d{1,2}):(\d{2})(?::(\d{2})(\.\d+)?)?(?:\s*(Z|[+-]\d{2}:\d{2})(?::\d{2})?)?$/i; + +function padDatePart(value: number | string) { + return String(value).padStart(2, "0"); +} + +function parsePortableReleaseDate(value: string) { + const match = value.match(PORTABLE_RELEASE_DATE_PATTERN); + if (!match) return null; + + const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction = "", zone] = + match; + const year = Number.parseInt(yearText, 10); + const month = Number.parseInt(monthText, 10); + const day = Number.parseInt(dayText, 10); + const hour = Number.parseInt(hourText, 10); + const minute = Number.parseInt(minuteText, 10); + const second = secondText ? Number.parseInt(secondText, 10) : 0; + + if (zone) { + const normalized = `${yearText}-${padDatePart(month)}-${padDatePart(day)}T${padDatePart( + hour, + )}:${padDatePart(minute)}:${padDatePart(second)}${fraction}${zone.toUpperCase()}`; + const date = new Date(normalized); + return Number.isNaN(date.getTime()) ? null : date; + } + + const date = new Date(year, month - 1, day, hour, minute, second); + if ( + date.getFullYear() !== year || + date.getMonth() !== month - 1 || + date.getDate() !== day || + date.getHours() !== hour || + date.getMinutes() !== minute + ) { + return null; + } + return date; +} + +function formatDateTime(date: Date) { + return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart( + date.getDate(), + )} ${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`; +} + +export function formatReleaseDate(value?: string | null) { + const trimmed = value?.trim(); + if (!trimmed) return ""; + + const portableDate = parsePortableReleaseDate(trimmed); + if (portableDate) return formatDateTime(portableDate); + + const date = new Date(trimmed); + if (Number.isNaN(date.getTime())) return trimmed; + return formatDateTime(date); +} diff --git a/crates/agent-gui/test/settings/about-date.test.mjs b/crates/agent-gui/test/settings/about-date.test.mjs new file mode 100644 index 00000000..4c3ad043 --- /dev/null +++ b/crates/agent-gui/test/settings/about-date.test.mjs @@ -0,0 +1,18 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { createTsModuleLoader } from "../helpers/load-ts-module.mjs"; + +process.env.TZ = "UTC"; + +const loader = createTsModuleLoader(); +const { formatReleaseDate } = loader.loadModule("src/pages/settings/aboutDate.ts"); + +test("formats updater release dates without seconds, milliseconds, or timezone", () => { + assert.equal(formatReleaseDate("2026-06-21 9:26:56.441 +00:00:00"), "2026-06-21 09:26"); + assert.equal(formatReleaseDate("2026-06-21T09:26:56.441Z"), "2026-06-21 09:26"); +}); + +test("keeps unknown release date values readable", () => { + assert.equal(formatReleaseDate(" not-a-date "), "not-a-date"); + assert.equal(formatReleaseDate(null), ""); +});