引言:为什么时间是个麻烦
\n\n每个写过JavaScript代码的人,都遇到过这种麻烦。你给用户显示时间12:00,他却说:“我这里是14:00,全乱了。”或者你把日期保存到数据库,加载时却倒退了一个小时。熟悉吗?
\n\n处理JavaScript中的时区是许多开发者“以后再说”的话题。但这是错误的。因为如果不立即搞清楚,以后就得重写一半的应用程序。
\n\n在本文中,我将讲解如何正确处理JS中的时区,展示真实的代码示例,并给出一些能节省你数小时调试时间的技巧。开始吧!
\n\n为什么JavaScript处理日期如此奇怪?
\n\n首先从关键点开始:JavaScript中的Date对象,说白了,并不好用。它以自1970年1月1日UTC以来的毫秒数存储时间。这本身没什么问题,但是:
- \n
getMonth()返回的月份是从0到11(一月是0,十二月是11) \n getHours()、getMinutes()方法返回的是用户本地时区的时间 \n - 而
getUTCHours()、getUTCMinutes()方法返回的是UTC时间 \n
混乱就从这里开始。假设你写:
\n\nconst now = new Date();\nconsole.log(now.getHours()); // 14 — 如果你在MSK时区\nconsole.log(now.getUTCHours()); // 11 — UTC\n\n如果你把now保存到数据库,然后在另一个时区的服务器上读取它,就会得到混乱的结果。因此,处理JavaScript中的日期和时区的第一条规则是——始终以UTC存储时间,并在客户端进行转换。
Intl.DateTimeFormat:内置的救星
\n\n许多人不知道,现代JavaScript中有一个强大的内置API用于考虑时区的日期格式化。这就是Intl.DateTimeFormat。它支持所有主要时区,并能漂亮地格式化日期。
基本示例
\n\nconst date = new Date('2025-03-15T12:00:00Z');\n\nconst formatter = new Intl.DateTimeFormat('ru-RU', {\n timeZone: 'America/New_York',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n});\n\nconsole.log(formatter.format(date)); \n// "2025年3月15日,08:00" — 纽约时间\n\n请注意:我们传递的是UTC日期(末尾的字母Z),而Intl.DateTimeFormat会自动将其转换为所需的时区。这对于向不同地区的用户显示时间非常理想。
所有时区列表
\n\n如果你不知道指定哪个时区,可以获取完整列表:
\n\nconsole.log(Intl.supportedValuesOf('timeZone'));\n// ["Africa/Abidjan", "Africa/Accra", ..., "Pacific/Wallis"]\n\n这个列表包含500多个时区。用它来确认你的时区是否被支持。
\n\nMoment.js:经典,但有保留
\n\n长期以来,moment.js一直是JavaScript中处理日期的标准。它可以解析、格式化、操作日期,当然,也可以通过插件moment-timezone处理时区。
安装
\n\nnpm install moment moment-timezone\n\n时区示例
\n\nconst moment = require('moment-timezone');\n\nconst now = moment();\nconsole.log(now.tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));\n// 2025-03-15 14:00 — 莫斯科时间\n\nconst nyTime = moment.tz('2025-03-15 12:00', 'America/New_York');\nconsole.log(nyTime.clone().tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));\n// 2025-03-15 19:00 — 相差7小时\n\n重要提示: moment.js是一个遗留库。作者自己也建议迁移