先记住这个答案
toISOString() 输出的是 UTC 时间,格式固定为 YYYY-MM-DDTHH:mm:ss.sssZ,结尾的 Z 表示零时区。北京比 UTC 快 8 小时,所以北京时间早上 8 点打出来是 00:00:00.000Z,看起来像是少了 8 小时,其实是同一个时刻。toLocaleString() 则按运行环境的时区和语言,生成给人看的文本。它们各管一件事:toISOString() 格式稳定,用于接口、日志、存储;toLocaleString() 格式随环境变化,只用于界面显示。
toISOString()固定 UTC,结尾Z表示零时区toLocaleString()按时区和语言输出,给人看- 用
toISOString()截日期,早上 8 点前会少一天 - 显示指定地区时间,显式传
timeZone
同一个时间点的三种输出
下面是一个 UTC 零点的 Date,分别用 ISO 格式、北京时间、纽约时间输出。
const instant = new Date('2026-09-10T00:00:00Z')
console.log(instant.toISOString()) // 永远是 UTC
console.log(instant.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }))
console.log(instant.toLocaleString('zh-CN', { timeZone: 'America/New_York', hour12: false }))
console.log(JSON.stringify({ at: instant })) // JSON 里用的也是 toISOString查看输出与解释
2026-09-10T00:00:00.000Z
2026/9/10 08:00:00
2026/9/9 20:00:00
{"at":"2026-09-10T00:00:00.000Z"}三行显示完全不同,纽约那行连日期都是前一天,但 instant.getTime() 从头到尾没变过。这里特意传了 timeZone,如果不传,toLocaleString() 就用运行环境的默认时区,同一行代码在服务器和用户浏览器上结果不同。
最后一行说明一个很实用的事实:JSON.stringify 遇到 Date 会调用它的 toJSON(),而 toJSON() 内部调用的就是 toISOString()。所以接口里传的时间,默认都是 UTC 字符串。
最常见的 bug:用 toISOString() 截今天的日期
很多代码想拿「今天」的日期字符串,写成 new Date().toISOString().slice(0, 10)。白天测试一切正常,到了凌晨就出问题。
const morning = new Date('2026-09-10T07:00:00+08:00') // 北京早上 7 点
console.log(morning.toISOString().slice(0, 10)) // UTC 还是 9 号晚上
console.log(morning.toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' }))查看输出与解释
2026-09-09
2026-09-10北京早上 7 点,UTC 还是前一天 23 点,截出来就是 9 号。国内用户在凌晨 0 点到 8 点之间操作,日期全部少一天,默认日期选择器、签到、日报这类功能都会中招。
正确的做法是按用户时区格式化。en-CA 这个语言的日期格式正好是 YYYY-MM-DD,配合 timeZone 就能拿到本地日期。更稳的写法是 Intl.DateTimeFormat 的 formatToParts(),按年月日片段自己拼,不依赖某个语言的格式恰好长什么样。
- 为什么不能反过来解析
toLocaleString() toLocaleString()的输出是给人看的,格式会随语言、浏览器版本、ICU 数据变化。比如英文的时间里可能带不换行空格,阿拉伯语的数字可能不是 0~9。把它存进数据库、再用new Date()解析回来,换个环境就解析失败。存储和传输一律用toISOString()或时间戳,显示的时候再格式化。
容易答错的地方
- 把
toISOString()的结果直接显示给用户 - 结尾的
Z明确表示 UTC。直接显示在国内用户面前,小时会比本地少 8,看起来像 bug。显示前要用toLocaleString()或Intl.DateTimeFormat转成本地时间。 - 以为
toISOString()会带上本地偏移 - 它不会输出
+08:00,永远是Z。想要带本地偏移的 ISO 字符串,得自己用getTimezoneOffset()拼,或者用 dayjs 这类库的format()。
面试官还会怎么问?
只想显示某个时区的年月日,怎么写?
用 toLocaleDateString() 或 Intl.DateTimeFormat,显式传 timeZone,再按需要传 year、month、day。列表里大量格式化时,建一个 Intl.DateTimeFormat 实例复用,比每次调 toLocaleString() 快。
SSR 页面里显示时间为什么会出现 hydration 报错?
服务端默认时区通常是 UTC,用户浏览器是东八区,两边各自 toLocaleString() 得到的文本不同,React 对比时就报不匹配。解决办法是渲染时显式传用户时区,或者时间部分只在客户端渲染。
toISOString() 什么时候会抛错?
Date 无效的时候。new Date('abc').toISOString() 抛 RangeError,而 toLocaleString() 只会返回 Invalid Date 字符串。所以序列化前最好先用 Number.isNaN(date.getTime()) 检查一下。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。