diff --git a/src/web/pages/SiteAnnouncements.tsx b/src/web/pages/SiteAnnouncements.tsx index 61e4cdd..6332429 100644 --- a/src/web/pages/SiteAnnouncements.tsx +++ b/src/web/pages/SiteAnnouncements.tsx @@ -6,6 +6,7 @@ import { clearFocusParams, readFocusAnnouncementId } from './helpers/navigationF import { formatSiteAnnouncementSeenAt, readClientTimeZone, + resolveSiteAnnouncementTimeZone, SiteAnnouncementContent, } from './helpers/siteAnnouncementPresentation.js'; import { tr } from '../i18n.js'; @@ -44,7 +45,7 @@ export default function SiteAnnouncements() { const rowRefs = useRef>(new Map()); const highlightTimerRef = useRef(null); const viewerTimeZone = useMemo(() => readClientTimeZone(), []); - const displayTimeZone = serverTimeZone || viewerTimeZone; + const displayTimeZone = resolveSiteAnnouncementTimeZone(viewerTimeZone, serverTimeZone); const siteNameById = useMemo(() => { const map = new Map(); diff --git a/src/web/pages/helpers/siteAnnouncementPresentation.test.tsx b/src/web/pages/helpers/siteAnnouncementPresentation.test.tsx index 38c4486..036f55d 100644 --- a/src/web/pages/helpers/siteAnnouncementPresentation.test.tsx +++ b/src/web/pages/helpers/siteAnnouncementPresentation.test.tsx @@ -5,6 +5,7 @@ import { SiteAnnouncementContent, formatSiteAnnouncementSeenAt, readClientTimeZone, + resolveSiteAnnouncementTimeZone, } from './siteAnnouncementPresentation.js'; type BrowserGlobals = { @@ -85,6 +86,11 @@ describe('siteAnnouncementPresentation helpers', () => { expect(formatSiteAnnouncementSeenAt('2026-03-20 04:23:27', 'Asia/Shanghai')).toBe('2026/03/20 12:23:27'); }); + it('prefers client timezone over server timezone', () => { + expect(resolveSiteAnnouncementTimeZone('Asia/Shanghai', 'UTC')).toBe('Asia/Shanghai'); + expect(resolveSiteAnnouncementTimeZone('', 'UTC')).toBe('UTC'); + }); + it('reads the browser timezone when available', () => { expect(readClientTimeZone()).toBeTruthy(); }); diff --git a/src/web/pages/helpers/siteAnnouncementPresentation.tsx b/src/web/pages/helpers/siteAnnouncementPresentation.tsx index f82ef8b..5c5834c 100644 --- a/src/web/pages/helpers/siteAnnouncementPresentation.tsx +++ b/src/web/pages/helpers/siteAnnouncementPresentation.tsx @@ -340,6 +340,17 @@ export function readClientTimeZone(): string | undefined { } } +export function resolveSiteAnnouncementTimeZone( + clientTimeZone?: string | null, + serverTimeZone?: string | null, +): string | undefined { + const client = String(clientTimeZone || '').trim(); + if (client) return client; + + const server = String(serverTimeZone || '').trim(); + return server || undefined; +} + export function formatSiteAnnouncementSeenAt( value: string | null | undefined, timeZone = readClientTimeZone(), diff --git a/src/web/pages/siteAnnouncements.test.tsx b/src/web/pages/siteAnnouncements.test.tsx index 66c664c..e0522c4 100644 --- a/src/web/pages/siteAnnouncements.test.tsx +++ b/src/web/pages/siteAnnouncements.test.tsx @@ -21,6 +21,14 @@ vi.mock('../api.js', () => ({ api: apiMock, })); +vi.mock('./helpers/siteAnnouncementPresentation.js', async () => { + const actual = await vi.importActual('./helpers/siteAnnouncementPresentation.js'); + return { + ...actual, + readClientTimeZone: () => 'Asia/Shanghai', + }; +}); + async function flushMicrotasks() { await act(async () => { await Promise.resolve(); @@ -107,7 +115,7 @@ describe('SiteAnnouncements page', () => { expect(apiMock.getRuntimeSettings).toHaveBeenCalled(); expect(JSON.stringify(root!.toJSON())).toContain('站点公告'); expect(JSON.stringify(root!.toJSON())).toContain('Maintenance'); - expect(JSON.stringify(root!.toJSON())).toContain('2026/03/20 04:23:27'); + expect(JSON.stringify(root!.toJSON())).toContain('2026/03/20 12:23:27'); const highlightedRows = root!.root.findAll((node) => { const className = typeof node.props.className === 'string' ? node.props.className : '';