/** 业务时间统一按北京时间(Asia/Shanghai,无夏令时)处理。 */ const SHANGHAI = 'Asia/Shanghai' function pad(n: number): string { return String(n).padStart(2, '0') } /** * API ISO / 任意可解析时间 → 日期时间选择器模型(北京时间 `YYYY-MM-DD HH:mm:ss`)。 */ export function toShanghaiPickerValue(iso: string | null | undefined): string | null { if (!iso) return null const d = new Date(iso) if (Number.isNaN(d.getTime())) return null const parts = new Intl.DateTimeFormat('en-GB', { timeZone: SHANGHAI, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23', }).formatToParts(d) const get = (type: Intl.DateTimeFormatPartTypes): string => parts.find((p) => p.type === type)?.value ?? '00' const hour = get('hour') === '24' ? '00' : get('hour') return `${get('year')}-${get('month')}-${get('day')} ${hour}:${get('minute')}:${get('second')}` } /** * 选择器值(按北京时间理解)→ API ISO8601(带偏移,避免被当成 UTC)。 */ export function fromShanghaiPickerValue(s: string | null | undefined): string | null { const t = (s ?? '').trim() if (!t) return null const m = t.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?/) if (!m) return null const [, y, mo, d, h, mi, sec = '00'] = m return `${y}-${mo}-${d}T${h}:${mi}:${sec}+08:00` } /** 列表/详情展示用北京时间。 */ export function formatShanghaiDateTime(iso: string | null | undefined, empty = '—'): string { const v = toShanghaiPickerValue(iso) if (!v) return empty return v.replace(/:\d{2}$/, '') // YYYY-MM-DD HH:mm } export function formatShanghaiDateTimeFull(iso: string | null | undefined, empty = '—'): string { return toShanghaiPickerValue(iso) ?? empty } /** 调试/校验用 */ export function shanghaiOffsetLabel(): string { return `${SHANGHAI} (UTC+8)` } export { pad }