const DEFAULT_TIMEZONE = "Asia/Shanghai"; let appTimezone = DEFAULT_TIMEZONE; export function getAppTimezone(): string { return appTimezone; } export function setAppTimezone(timezone: string): void { try { new Intl.DateTimeFormat("en", { timeZone: timezone }).format(); appTimezone = timezone; } catch { appTimezone = DEFAULT_TIMEZONE; } } export function formatDateTime(value: Date | number | string, timezone = appTimezone): string { return new Intl.DateTimeFormat("zh-CN", { dateStyle: "medium", timeStyle: "short", timeZone: timezone, }).format(value instanceof Date || typeof value === "number" ? new Date(value) : new Date(value)); } export const formatDate = formatDateTime; export const dateText = formatDateTime; export function toDateInputValue(value: Date | number | string, timezone = appTimezone): string { const date = value instanceof Date || typeof value === "number" ? new Date(value) : new Date(value); const parts = new Intl.DateTimeFormat("en-CA", { timeZone: timezone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23", }).formatToParts(date); const values = Object.fromEntries(parts.map((part) => [part.type, part.value])); return `${values.year}-${values.month}-${values.day}T${values.hour}:${values.minute}`; } export const formatDateTimeInput = toDateInputValue; export function fromDateInput(value: string, timezone = appTimezone): string { const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})$/.exec(value.trim()); if (!match) throw new Error("请选择有效的支付日期和时间"); const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); const hour = Number(match[4]); const minute = Number(match[5]); const calendarProbe = new Date(0); calendarProbe.setUTCFullYear(year, month - 1, day); calendarProbe.setUTCHours(0, 0, 0, 0); if (month < 1 || month > 12 || day < 1 || day > 31 || calendarProbe.getUTCFullYear() !== year || calendarProbe.getUTCMonth() !== month - 1 || calendarProbe.getUTCDate() !== day || hour < 0 || hour > 23 || minute < 0 || minute > 59) { throw new Error("支付时间无效"); } // Interpret the form value as a wall-clock time in the configured zone. const wallUtc = Date.UTC(year, month - 1, day, hour, minute); const observedParts = new Intl.DateTimeFormat("en-CA", { timeZone: timezone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23", }).formatToParts(new Date(wallUtc)); const observed = Object.fromEntries(observedParts.map((part) => [part.type, part.value])); const observedUtc = Date.UTC(Number(observed.year), Number(observed.month) - 1, Number(observed.day), Number(observed.hour), Number(observed.minute)); return new Date(wallUtc + wallUtc - observedUtc).toISOString(); } export const dateFromInput = fromDateInput; export function currentMonth(timezone = appTimezone): string { const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", { timeZone: timezone, year: "numeric", month: "2-digit", }).formatToParts(new Date()).map((part) => [part.type, part.value])); return `${parts.year}-${parts.month}`; } export const monthNow = currentMonth;