时区与夏令时:存 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。本地时间只在两个地方出现:用户输入的那一刻、渲染给用户的那一帧。

← 返回文章列表

评论

…