我们的目标是让软件出海更大略、快捷、高效。
近期在办理 墨西哥取消夏令时 的问题时,深入研究了ICU的tzdata时区数据(timezone data)。在此过程中遭遇了 Golang 1.13 time标准库的一个bug (go version <= 1.13)。
详细问题是,用Go1.13时某些时区的韶光打算结果会涌现缺点,经由和Google Golang团队的沟通确认了问题,摸清了整体问题细节。

如果你很关注出海企业会碰着哪些环球化技能问题,可以连续阅读,否则可以节省韶光了。
终极解法 - 多平台时区数据
为了节省韶光,终极办理方案可以参考我们的 I18N 领域知识库:
https://github.com/international-explore/i18n-icu-data
I18N 领域中的韶光难题
关于时区,大家熟知中国是东八区(GMT+8) ,大略讲地球以本初子午线为基准被划分为24个时区,东半球有12个时区,西半球有12个时区。每个时区有一个映射的别名,比如Asia/Shanghai、America/Mexcio等。
关于夏令时,是诸多令人头疼问题的源头。夏令时是古代农耕社会遗留到现在的一个传统,目的是跟随太阳日升而作日落而息节省照明。季候切换规则大略讲便是在立夏时全国公民把钟表往前拨快一小时,比如从01:00直接跳到02:00, 冬天再从02:00回退一小时到01:00。如此一天就可能有两个2点,全天时长也可能是25小时或23小时。
夏令时规则是多变的,取决于一个国家政府的决策,常常涌现延迟或者取消夏令时的情形,比如最近墨西哥政府宣告2023年往后永久取消夏令时,并且环球化大领悟的本日,各国取消夏令时是大势所趋。
某国夏令时规则变革时,会关照国际时区组织 [IANA],IANA卖力掩护原始tzdata时区数据,并发布新版本。然后 [ICU] 组织会在几天内更新时区数据,大部分公司和编程措辞都是基于ICU的。
Linux系统时区配置
Linux 系统的时区配置位于 /usr/share/zoneinfo 目录下,以目录树的形式组织每个时区的文件。当前系统默认时区决定于 /etc/localtime, 如果你想修正默认时区,可以这么做 :
sudo ln -sf /usr/share/zoneinfo/America/Mexcio /etc/localtime
Linux 时区目录构造:
各措辞如何更新时区数据企业在出海时碰着的其余一个痛点是,每个编程措辞办理时区的方案不同,归根结底是ICU只供应了 ICU4C(c++)、ICU4J(Java) 两种措辞的SDK,其他措辞都须要自己实现或者利用交叉编译的技能来办理。
各措辞如何更新时区数据的办法:
- Golang:方法1:依赖系统时区 ;方法2:自定义时区数据路径- PHP:依赖intl.so插件 + timezonedb.so 插件,不依赖系统时区- Java:依赖ICU4J, 不依赖系统时区- C++:依赖ICU4C, 不依赖系统时区- Python: 依赖ICU4C, 不依赖系统时区- Linux Shell: 依赖系统时区
1. 制作时区数据
cd /usr/local/go/lib/timevim update.bash # 修正时区版本bash update.bash末了天生一个压缩包 zoneinfo.zip
2. 生效时区数据
把zoneinfo.zip 解压到 /usr/share/zoneinfo/ 即可(还有其他生效办法,在此省略)
结论:go1.13解析zoneinfo文件时没有利用extend等信息,extend是用于推演未来韶光夏令时的,比如打算到2099年。
【zoneinfo 文件格式】
zoneinfo文件格式大致如下,核心有两个数组`trans`和`offset` 以及一个`extend`// 示例举例trans里第一个韶光戳是1272762000 对应的UTC韶光是 2010-05-02 01:00:00,第一个offset是 3600,表示1272762000 往后当地实际韶光 = 1272762000 + 3600// 季候切换韶光点trans: { 1272762000, 1281222000, 1301788800, 1312066800, 1335664800,1345428000, 1348970400, 1367114400, 1373162400 (2013-07-07 02:00:00) }// 偏移量Offsets: { 3600, 0, 0, 0, 0, 3600, 3600, 1800, 3600}// trans末了一个数组,'之后的年份' 按extend规则切换extend=PST8PDT,M3.2.0,M11.1.0
【go 1.13 如何打算韶光】
go 1.13(<=1.13)版本time包里的time.LoadLoaction()等方法内部逻辑大概是:1. 从trans数组里查找一个区间2. 从offset数组里查找韶光偏移量3. 基定时光 + offset => local time4. go 1.13 没有把extend利用起来 (重点!
!
!
)
这里一个核心问题是 trans 里末了一个韶光戳 1373162400,对应的韶光是2013-07-07 02:00:00, 它表示2013年往后就没有夏令时了?
但实际该国是有夏令时的,只是考虑到数据量的问题,trans数组没有推演到无穷远韶光(比如2038年),此时extend信息就该起浸染了,2013年往后该当按extend规则进行,但是go1.13没有利用此信息。
【为何之前都没问题】
最近IANA时区天生工具有变革,原来会默认天生数据直到2038年,但是最近2022b版本往后采纳 最小数据原则,只天生到当前年份。只有加 额外的参数选项 才能天生到 2038年。
和google Go团队的沟通结果
针对此问题也和Google的Golang团队进行了谈论,核心结论是:
1. Google表示1.13确实存在这个bug,并且在1.14修复了
2. 对我们还在利用1.13表示很诧异,由于有很多bug并且1.13已经不再掩护
如何办理 最佳实践- 方法1:升级go1.13 到 go1.17 (>=1.14即可,但本钱较高)
- 方法2:利用 改动版的zoneinfo.zip数据,https://github.com/international-explore/i18n-icu-data