先记住这个答案
Date 存的是时间戳。准确说,是一个距离 1970-01-01T00:00:00Z 的毫秒数,Date 内部没有任何字段记录时区。时区只在两个时刻起作用:用本地字段构造 Date 时,把本地时间换算成时间戳;调用 getHours() 或 toString() 这类本地方法时,把时间戳换算成本地时间。所以同一个 Date 在北京和纽约打印出来的钟点不同,但 getTime() 完全一样。
- Date 只存 UTC 毫秒数,不存时区
getHours()和toString()按本机时区读- UTC 方法和
toISOString()永远按 UTC 读 - 输入里写的
+08:00只参与换算,Date 不会记住它
同一个时间点,两种写法
北京时间早上 8 点和 UTC 零点是同一个时刻。用两种字符串分别构造,存下来的是同一个数。
const beijing = new Date('2026-09-10T08:00:00+08:00') // 北京早上 8 点
const utc = new Date('2026-09-10T00:00:00Z') // UTC 零点
console.log(beijing.getTime()) // 内部存的毫秒数
console.log(beijing.getTime() === utc.getTime()) // 同一个时间点
console.log(beijing.toISOString()) // 按 UTC 输出
console.log(beijing.getUTCHours()) // UTC 的小时查看输出与解释
1788998400000
true
2026-09-10T00:00:00.000Z
0解析 +08:00 时,引擎把 08:00 减去 8 小时得到 UTC 时间,然后只存下这个毫秒数。输入里写的 +08:00 用完就丢了,所以 toISOString() 打印出来的是 Z 结尾的 UTC 时间,而不是你传进去的东八区写法。
这几行在任何时区的机器上跑,结果都一样,因为它们都不涉及本地时区。
本地方法才会让结果随机器变化
像 getHours() 和 getDate(),还有 toString(),这些不带 UTC 的方法,读的时候会用运行环境的时区规则去换算。换一台时区不同的机器,结果就变了。
const d = new Date('2026-09-10T08:00:00+08:00')
// 在东八区(Asia/Shanghai)运行
d.getHours() // 8
d.getDate() // 10
d.toString() // Thu Sep 10 2026 08:00:00 GMT+0800 (中国标准时间)
// 在 America/New_York 运行
d.getHours() // 20
d.getDate() // 9,已经是前一天了
d.toString() // Wed Sep 09 2026 20:00:00 GMT-0400 (北美东部夏令时间)纽约这时候用夏令时,比 UTC 慢 4 小时,所以北京的 10 号早上 8 点在纽约还是 9 号晚上 8 点。日期都不一样了,但它们是同一个时刻。
这就是为什么「服务端渲染的时间和浏览器里的不一致」这类 bug 很常见。服务器的系统时区往往是 UTC,用户浏览器是东八区,两边各自调用 getHours() 格式化,结果当然不同。要么统一用 Intl.DateTimeFormat 显式传 timeZone,要么只在客户端格式化。
- 换算时查的是那个日期当时的规则
- 本地方法不是简单地加减一个固定偏移,而是查这个时间点在当地的时区规则。同样是纽约,1 月份按 UTC-5 算,7 月份按 UTC-4 算。历史日期也一样,有些地区几十年前的偏移和现在不同,运行时会按时区数据库里当年的规则换算。
容易答错的地方
- 以为 Date 记住了构造时的时区
- 输入字符串里的偏移只参与换算,Date 不保留它。后端返回
2026-09-10T08:00:00+09:00,前端new Date()之后就再也拿不到+09:00了。如果业务需要知道「这是东京的时间」,得把时区单独存一个字段。 - 把
toString()的结果当接口格式 toString()的输出带本机时区名,而且格式随环境变化,本来就是给人调试看的。接口传时间用toISOString()或者直接传毫秒数。
面试官还会怎么问?
两个时区不同的字符串,getTime() 可能相等吗?
可以,只要它们描述的是同一时刻。08:00+08:00 和 00:00Z 解析后毫秒数完全相同,=== 比较 getTime() 结果是 true。
Date 能存 Asia/Shanghai 这种时区名吗?
不能,Date 只有一个时间值。Intl.DateTimeFormat 可以按指定时区格式化,但原始的业务时区要自己用一个字段存下来。新的 Temporal API 里 ZonedDateTime 才会同时带上时区。
为什么 new Date(dateA) - new Date(dateB) 能直接得到毫秒差?
减号会把两个 Date 转成数字,调用的是 valueOf(),返回的就是 getTime() 那个毫秒数。因为存的是时间戳,两个时刻的差值和时区完全无关。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。