几乎所有关于 Go 语言中时间解析的例子都会拿完整的包含日期在内的时间来解析,官方例子也不例外。 但是却很少有人以下面这段代码作为例子解释其中的行为,直到使用者出错的那一天。
问题背景
想让用户从终端输入一个可读的时间作为输入,但是我发现我解析出来的输入不对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
很简单一段代码(忽略错误处理),以当前位置(Location)解析一个输入的部分时间,下面看看执行结果:
1 2 3 |
|
时、分都是正确解析的,年、月、日、秒、纳秒也是正确的默认值。哪里不对?时区。
是不是挺奇怪的呢,一个是 CST(China Standard Time,中国标准时间,+0800),一个 LMT(Local Mean Time?,+0805)。 哎喂,我明明就是拿 now 的 Location 去 Parse 的,为什么 Parse 出来的时区/位置却不一样呢?甚是奇怪!
从源代码中找答案
调用栈:Time.String
→ Time.Format
→ Time.AppendFormat
→ Time.locabs
→ Location.lookup
。
1 2 3 4 5 |
|
这个 locabs
就是我要关注的点。其中的 name
即是时区的名字,offset
即是时区的偏移。问题就出在这里,它为什么算“错”了?
locabs
会调用 lookup
,以下是其源代码(有删减)。(注:这周末两天整个地被 log4j 的远程代码执行漏洞支配了,当看到 lookup 时,有点 PTSD 了。一个日志库,你 lookup 这 lookup 那地干嘛呢!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
在解读这段代码前,先来看看 Location
相关的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
从注释以及定义来看,问题开始变得有迹可循了:
一个 位置(Location) 包含多个 时区(Zone) 和多个 时区变迁(Transition) 。
所以 lookup
是在干嘛? 它在查找输入的参数 sec(Unix 时间戳) 该使用此位置的哪个时区。
对于有点地理盲和历史盲的我来说,对于这些并不是太了解。于是去查找了一些相关资料(维斯百科 - 中国时区):
中国幅员辽阔,按国际通行时区划分标准可划分为东五区、东六区、东七区、东八区、东九区5个时区。 中华民国大陆时期依据国际标准,将全国时区划分为昆仑时区、回藏时区(后改称新藏时区)、陇蜀时区、中原时区、以及长白时区。 1949年中华人民共和国成立后,改将中国大陆全境统一划为东八区(UTC+8),同时采用北京时间做为全国唯一的标准时间。
简单地说,同一个地理位置的时区,在不同的历史上(国家灭亡、国家建立、政权更替等)是会发生改变的。 这些信息被维护整理到时区信息数据库中。
再回到位置(Location) 的定义中,zone
记录了这个位置所有出现过的时区,而 tx
则记录了一个时区是何时变迁到另一个时区的。
现在可以明明白白 lookup
的所作所为了。
附:我示例中的 Location 的 Zone 内容:
1 2 3 4 5 6 7 8 9 |
|
CST 是我们目前使用的 +8 (8*60*60=28800)时区。
Tx 我就不附了,有 29 个之多。
答案解答
我让用户的输入只包含了时和分,那么默认的年是 0000 年,转换成 Unix 时间戳(相对于 1970 年来说是个负数)后通过 Tx 找到对应的 Zone 确实是 LMT。没毛病。
那怎么让输入值解析正确呢?用 FixedZone
。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
这个 Zone 构成的 Location 只有一个 Zone,一个 Transition。
再来修改一下示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
现在可以得到期望的答案:
1 2 3 |
|
最后
虽然是个小问题,但经过查源代码与相关资料,收获不少。