时区与夏令时:存 UTC,存本地时间一定会出事
夏令时会让某一天有 23 或 25 小时,且切换时刻的本地时间可能不存在或出现两次。只存 UTC 时间戳,展示时再转,是唯一稳的模型。
只要系统里存的是本地时间字符串,夏令时切换那天一定出错。原因是它破坏了「本地时间到时刻」的一一对应。
切换日的两个异常
春天跳表:当地时钟从 02:00 直接跳到 03:00,02:30 这一刻根本不存在。
秋天重叠:时钟从 03:00 退回 02:00,02:30 出现两次,相差一小时。
存 "2026-03-08 02:30" 时,你无法回答「这是哪个绝对时刻」—— 它可能不存在,也可能是两个之一。
正确模型
| 存什么 | 什么时候转 |
|---|---|
| UTC 时间戳(或带偏移的 ISO 8601) | 展示给用户时 |
用户所在时区标识(Asia/Shanghai) |
需要本地语义时 |
// 存
const instant = Date.now(); // UTC 毫秒
const iso = new Date().toISOString(); // 带 Z
// 展示(用 IANA 时区,不是固定偏移)
new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
dateStyle: 'full',
timeStyle: 'short',
}).format(instant);
注意 timeZone 要写 IANA 名称。固定偏移如 +08:00 在夏令时地区会错一小时。
定时任务在切换日会怎样
「每天 02:30 执行」在跳表日没有 02:30。两种约定:
- 跳过:那天不执行(多数调度器默认)
- 顺延:执行时刻平移到 03:00
必须显式选一个并写进文档。不选就会出现「某天任务没跑」或「某天跑两次」。
跨天计算别用减法
「相差一天」不能用 (t2 - t1) / 86400000 再取整 —— 夏令时那天只有 23 小时。要按日历判断:先在目标时区取出两个日期,再比较年月日。
const dayIn = (tz: string, t: number) =>
new Intl.DateTimeFormat('en-CA', { timeZone: tz }).format(t);
dayIn('Asia/Shanghai', a) !== dayIn('Asia/Shanghai', b);
存时区名而不是偏移
用户搬家后,历史事件若存的是偏移就永远错了。存 Asia/Shanghai,未来规则变化(政府改夏令时政策是常有的事)也能自动跟上。
系统内部一律 UTC。本地时间只在两个地方出现:用户输入的那一刻、渲染给用户的那一帧。

评论
…