我们前端常说「字符串别随便传给 new Date()
,会出事」,可你真的知道它为什么出事、怎么出事、在哪出事吗?
我自己就在实际项目中,被它坑过不止一次。
比如后端返回一个接口数据:
json
{
"date": "2025-05-28"
}
我像往常一样:
js
const d = new Date(data.date);
console.log(d);
结果在控制台里一看,呃......这时间怎么对不上?页面上展示是「5月28日」,实际 Date
对象里的时间却是「5月28日 08:00:00」?
明明后端说的是「当天凌晨」,前端偏偏多了 8 个小时,整整差了一个时区。
这不是 bug 是什么?
于是我开始刨根问底,这一查不要紧,发现这个"坑",居然藏在浏览器、标准、解析行为之间的缝隙里。
今天这篇文章,我们就通过几个能复现的例子,一步步带你看懂:
- 为什么浏览器解析日期行为不一致?
- 什么格式能用,什么格式会崩?
2025-05-28
到底是哪一天的几点?- 我们到底该怎么写代码才不踩坑?
如果你正在写和「时间、日期」相关的项目,比如日历、预约、日志时间戳、搜索过滤等等,强烈建议你花 5 分钟认真看完。
看个更真实、能复现的问题例子
下面这个例子,我建议你直接在 Chrome、Firefox 和 Safari 控制台都跑一遍看看:
js
console.log(new Date('2025/05/28'));
console.log(new Date('2025-05-28'));
console.log(new Date('2025-5-28'));
在 Chrome 和 Firefox 最新版 上,你会得到:
js
Wed May 28 2025 00:00:00 GMT+0800 (中国标准时间)
Wed May 28 2025 08:00:00 GMT+0800 (中国标准时间)
Wed May 28 2025 00:00:00 GMT+0800 (中国标准时间)
但在 Safari 上是:
js
Wed May 28 2025 00:00:00 GMT+0800 (中国标准时间)
Wed May 28 2025 08:00:00 GMT+0800 (中国标准时间)
Invalid Date
Safari 下最后一行直接解析失败了。
这里也是经常在iOS移动端H5遇到的兼容性问题,不过这不是本文关注点,不展开说了
但你有没有注意到中间这一行:2025-05-28
?虽然看起来还是同一天,但其实它表示的是:
js
2025-05-28T00:00:00.000Z // 被解析为 UTC
换算成本地时间(+08:00)后,就成了:
js
2025-05-28 08:00:00 GMT+0800
你以为是"5月28日凌晨0点",其实是"当天早上8点",时间点已经偏移了。
这就埋下了非常隐蔽的 bug,尤其当你和后端约定的是日期,而不是具体时刻时,很容易在比较、筛选、显示时踩坑。
Date 构造函数到底是怎么解析字符串的?
我们常用的几种格式:
字符串形式 | 是否标准 ISO 格式 | 解析方式 |
---|---|---|
2025/05/28 |
❌ 否 | 本地时区(不可靠) |
2025-05-28 |
✅ 是 | 按 UTC 解析 |
2025-5-28 |
❌ 否 | 有些浏览器不支持 |
结论:当你传入一个 ISO 格式(YYYY-MM-DD
)字符串,浏览器默认用 UTC 来解析它。
然后再转成本地时间,比如东八区的中国,就会变成早上 8 点。
为什么会出现这种差异?
我们来看看背后的标准演变。
历史时间线简略版:
- 早期浏览器(ES5 之前):不管什么格式,基本都当成本地时间来处理。
- ES5(2009):引入对 ISO 8601 格式的支持,规范模糊,没有明确说明"时区怎么解析"。
- Firefox:把
YYYY-MM-DD
当作 UTC 来处理。 - Chrome:一开始用本地时间,后来改来改去,最终和 Firefox 保持一致。
- Safari:部分格式直接
Invalid Date
,容错性较差。
所以今天主流浏览器的行为大致是:
YYYY/MM/DD
走本地时间(但不是标准格式,不推荐)YYYY-MM-DD
按 UTC 处理(标准格式,但有时区陷阱)
日期解析的最佳实践建议
根据目前的标准和主流实践,结合前后端接口交互的常见问题,下面是一些实用建议:
-
不要将
YYYY-MM-DD
这样的字符串直接传入new Date()
。- 浏览器会将其按 UTC 解析,导致时区偏移问题。
-
后端传时间时建议统一为 ISO 字符串 + 明确时区,例如:
json
"2025-05-28T00:00:00Z" // UTC
"2025-05-28T00:00:00+08:00" // 东八区
注意中间的那个大写字母T,一定不要缺少了,要不然另一个坑又在等着你。有时候后端会把中间的T换成一个空格,那么坑就挖好了
-
如果项目允许,建议使用时间戳(
timestamp
)进行前后端数据交换:- 时间戳是自 1970 年起的毫秒数或秒数,天然是 UTC,不受时区干扰
- 示例:
json
{
"startTime": 1748361600000 // 表示 2025-05-28 00:00:00.000 UTC
}
- 前端解析:
js
new Date(data.startTime);
- 缺点是 可读性差、单位需一致(秒 vs 毫秒)
-
前端解析推荐使用日期处理库(推荐顺序):
-
如果数据来自用户输入或者第三方服务,务必校验格式并处理时区。
-
如果你是重度依赖日期和时区的系统,建议关注 ECMAScript 的 Temporal API,虽然还在实验阶段,但它解决了很多
Date
API 无法解决的问题。
推荐代码写法:
js
// 使用 Date.UTC 明确指定 UTC 时间
new Date(Date.UTC(2025, 4, 28)) // 注意月份从 0 开始
// 使用第三方库 dayjs
import dayjs from 'dayjs';
dayjs('2025-05-28').format('YYYY-MM-DD')
总结一句话
你以为的"日期字符串"只是一个日期,但浏览器可不这么想,它觉得你可能想表达一个"时间点"。
搞清楚这点之后,JavaScript 里的日期处理你就不会被 Date() 搞疯了。
📚 相关文档
如果这篇文章对你有帮助,欢迎转发给你的前端同事,避免 "日期穿越" 事故从你这开始 😅