前端时间国际化入门

本文探讨了前端时间国际化处理的挑战,从时区的概念、处理时区的困难、时区转换的方法,以及语言文字区域的区分进行了深入讨论。文章指出JavaScript的Date对象在处理时区时的不足,并建议使用ES6的Intl命名空间来实现更精确的时区转换和格式化。同时,文章提醒开发者注意时区历史变更和语言文字区域设置在时间国际化中的重要性。
摘要由CSDN通过智能技术生成

时间只是幻觉。—— 阿尔伯特·爱因斯坦

最近在开发一个需要完善国际化方案的前端项目,在处理时间国际化的时候遇到了一些问题。于是花了一些时间研究,有了这篇文章。不过由于网上关于 JavaScript 中 Date 对象的坑的文章已经一抓一大把了,因此这篇文章不是 JavaScript 中  Date 对象的使用指南,而是只专注于前端时间国际化

从时区说起

要想处理时间,UTC 是一个绕不开的名字。协调世界时(Coordinated Universal Time)是目前通用的世界时间标准,计时基于原子钟,但并不等于 TAI(国际原子时)。TAI 不计算闰秒,但 UTC 会不定期插入闰秒,因此 UTC 与 TAI 的差异正在不断扩大。UTC 也接近于 GMT(格林威治标准时间),但不完全等同。可能很多人都发现近几年 GMT 已经越来越少出现了,这是因为 GMT 计时基于地球自转,由于地球自转的不规则性且正在逐渐变慢,目前已经基本被 UTC 所取代了。

JavaScript 的 Date 实现不处理闰秒。实际上,由于闰秒增加的不可预测性,Unix/POSIX 时间戳完全不考虑闰秒。在闰秒发生时,Unix 时间戳会重复一秒。这也意味着,一个时间戳对应两个时间点是有可能发生的。

由于 UTC 是标准的,我们有时会使用 UTC+/-N 的方式表达一个时区。这很容易理解,但并不准确。中国通行的 Asia/Shanghai 时区大部分情况下可以用 UTC+8 表示,但英国通行的 Europe/London 时区并不能用一个 UTC+N 的方式表示——由于夏令时制度,Europe/London 在夏天等于 UTC+1,在冬天等于 UTC/GMT。

一个时区与 UTC 的偏移并不一定是整小时。如 Asia/Yangon 当前为 UTC+6:30,而 Australia/Eucla 目前拥有奇妙的 UTC+8:45 的偏移。

夏令时的存在表明时间的表示不是连续的,时区之间的时差也并不是固定的,我们并不能用固定时差来处理时间,这很容易意识到。但一个不容易意识到的点是,时区还包含了其历史变更信息。中国目前不实行夏令时制度,那我们就可以放心用 UTC+8 来表示中国的时区了吗?你可能已经注意到了上一段中描述 Asia/Shanghai 时区时我使用了大部分一词。Asia/Shanghai 时区在历史上实行过夏令时,因此 Asia/Shanghai 在部分时间段可以使用 UTC+9 来表示。

new Date('1988-04-18 00:00:00')
// Mon Apr 18 1988 00:00:00 GMT+0900 (中国夏令时间)

夏令时已经够混乱了,但它实际上比你想象得更混乱——部分穆斯林国家一年有四次夏令时切换(进入斋月时夏令时会暂时取消),还有一些国家使用混沌的 15/30 分钟夏令时而非通常的一小时。

不要总是基于 00:00 来判断一天的开始。部分国家使用 0:00-1:00 切换夏令时,这意味着 23:59 的下一分钟有可能是 1:00。

事实上,虽然一天只有 24 个小时,但当前(2021.10)正在使用的时区有超过 300 个。每一个时区都包含了其特定的历史。虽然有些时区在现在看起来是一致的,但它们都包含了不同的历史。时区也会创造新的历史。由于政治、经济或其他原因,一些时区会调整它们与 UTC 的偏差(萨摩亚曾经从 UTC-10 切换到 UTC+14,导致该国 2011.12.30 整一天都消失了),或是启用/取消夏令时,甚至有可能导致一个时区重新划分为两个。因此,为了正确处理各个时区,我们需要一个数据库来存放时区变更信息。还好,已经有人帮我们做了这些工作。目前大多数 *nix 系统和大量开源项目都在使用 IANA 维护的时区数据库[1](IANA TZ Database),其中包含了自 Unix 时间戳 0 以来各时区的变更信息。当然这一数据库也包含了大量 Unix 时间戳 0 之前的时区变更信息,但并不能保证这些信息的准确性。IANA 时区数据库会定期更新,以反映新的时区变更和新发现的历史史实导致的时区历史变更。

Windows 不使用 IANA 时区数据库。微软为 Windows 自己维护了一套时区数据库[2],这有时会导致在一个系统上合法的时间在另一系统上不合法。

既然我们不能使用 UTC 偏移来表示一个时区,那就只能为每个时区定义一个标准名称。通常地,我们使用 <大洲>/<城市> 来命名一个时区。这里的城市一般为该时区中人口最多的城市。于是,我们可以将中国的通行时区表示为 Asia/Shanghai。也有一些时区有自己的别名,如太平洋标准时间 PST 和协调世界时 UTC

时区名称使用城市而非国家,是由于国家的变动通常比城市的变动要快得多。

城市不是时区的最小单位。有很多城市同时处于多个时区,甚至澳大利亚有一个机场[3]的跑道两端处于不同的时区。

处理时区困难重重

几个月前的一天,奶冰在他的 Telegram 频道里发了这样的一条消息:

6acf51b9706ac6213c9807b995ba3f42.png

你想的没错,这个问题正是由时区与 UTC 偏移的不同造成的。Asia/Shanghai 时区在 1940 年前后和 1986 年前后曾实行过夏令时,而夏令时的切换会导致一小时的出现和消失。具体来说,启用夏令时当天会有一个小时消失,如 2021.3.28 英国启用夏令时,1:00 直接跳到 3:00,导致 2021-03-28 01:30:00Europe/London 时区中是不合法的;取消夏令时当天又会有一个小时重复,如 2021.10.31 英国取消夏令时,2:00 会重新跳回 1:00 一次,导致 2021-10-31 01:30:00Europe/London 时区中对应了两个时间点。而在奶冰的例子中,1988-04-10 00:46:50 正好处于因夏令时启用而消失的一小时中,因此系统会认为此时间字符串不合法而拒绝解析。

你可能会注意到在历史上 1988.4.10 这一天 Asia/Shanghai 时区实际上是去掉了 1:00-2:00 这一小时而不是 0:00-1:00。上文问题更深层次的原因是,在 IANA TZDB 2018a 及更早版本中,IANA 因缺乏历史资料而设置了错误的夏令时规则,规则设定了夏令时交界于 0:00-1:00 从而导致上文问题发生。而随后社区发现了更准确的史实[4],因此 IANA 更新了数据库。上文的问题在更新了系统的时区数据库后便解决了。

49b0e290b25e0eafba7f053b92f83e5a.png
IANA TZDB 2018a 及之前版本的错误数据

再来考虑另一种情况。你的应用的某位巴西用户在 2018 年保存了一个未来时间 2022-01-15 12:00(按当时的规律那应该是个夏令时时间),不巧那时候你的应用是以格式化的时间字符串形式保存的时间。之后你发现巴西已经于 2019 年 4 月宣布彻底取消夏令时制度,那么 2022-01-15 12:00 这个时间对应的 Unix 时间戳发生了变化,变得不再准确,要正确处理这一字符串就需要参考这一字符串生成的时间(或生成时计算的 UTC 偏移)来做不同的处理。因此,应用从一开始就应该避免使用字符串来传输、存储时间,而是使用 Unix 时间戳。如果不得不使用字符串存储时间,请尽可能:

  • 使用 UTC 描述时间,你永远不

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值