From 81e461f6514dd7a8b313515109ecbb7acdf5c8c8 Mon Sep 17 00:00:00 2001 From: lylelllll <59271327+lylelllll@users.noreply.github.com> Date: Sun, 30 Aug 2026 23:19:45 +0800 Subject: [PATCH] fix(frontend): parse local expiry datetimes strictly --- frontend/src/utils/format.ts | 60 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/format.ts b/frontend/src/utils/format.ts index 17ba82e098..87204ff158 100644 --- a/frontend/src/utils/format.ts +++ b/frontend/src/utils/format.ts @@ -201,9 +201,63 @@ export function formatDateTimeLocalInput(timestampSeconds: number | null): strin */ export function parseDateTimeLocalInput(value: string): number | null { if (!value) return null - const date = new Date(value) - if (isNaN(date.getTime())) return null - return Math.floor(date.getTime() / 1000) + + // datetime-local intentionally has no timezone. Parse its components explicitly + // so malformed values or timezone-bearing strings are not reinterpreted by Date.parse. + const match = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?$/.exec(value) + if (!match) return null + + const year = Number(match[1]) + const month = Number(match[2]) + const day = Number(match[3]) + const hours = Number(match[4]) + const minutes = Number(match[5]) + const seconds = match[6] ? Number(match[6]) : 0 + const milliseconds = match[7] + ? Number(match[7].slice(0, 3).padEnd(3, '0')) + : 0 + + if ( + !Number.isSafeInteger(year) || + year < 1 || + month < 1 || + month > 12 || + day < 1 || + day > 31 || + hours > 23 || + minutes > 59 || + seconds > 59 + ) { + return null + } + + const date = new Date(0) + date.setFullYear(year, month - 1, day) + date.setHours(hours, minutes, seconds, milliseconds) + + // Date setters normalize calendar overflows (for example, February 30). Keep + // the platform's existing DST disambiguation for otherwise valid local times. + if ( + date.getFullYear() !== year || + date.getMonth() !== month - 1 || + date.getDate() !== day + ) { + return null + } + + const timestamp = date.getTime() + return Number.isFinite(timestamp) ? Math.floor(timestamp / 1000) : null +} + +/** + * 获取浏览器当前使用的 IANA 时区 + */ +export function getBrowserTimeZone(): string { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC' + } catch { + return 'UTC' + } } /**