编辑 2:根据reply in github issues,事实证明,在布局字符串中“MST-0700”实际上是两个时区值"MST" 和数字时区值"-0700",它优先于前者。而"GMT+08" 或"GMT+00" 被视为一个整体的时区值,并与“MST”匹配。因此,"MST-0700" 将是 "GMT+08+0800"。
我很少接触时区相关的问题,但我个人认为这种行为令人困惑。
原答案:
这是 Go 的时间库的一个 错误 令人困惑的行为。虽然我不确定 GMT 时间格式的期望行为(因为我很少在时间格式字符串中看到类似 GMT+0800 的东西),但使 GMT+0800 有效但 GMT+0000 的代码逻辑没有意义。
我在github上提交了一个issue:https://github.com/golang/go/issues/40472
简而言之,Go 的时间库通过同时消耗布局字符串和值字符串来解析格式。目前在解析“GMT”时区时,如果以下值字符串(例如“+0800”或“+0000”)已签名且范围从-23 到+23,则它会消耗更多的值字符串.所以不仅"GMT+0000" 失败,而且"GMT+0030" 和"GMT+08"(匹配到`"MST-07" 时)也会失败。
这里是细节:在解析时区时,它检查布局字符串,发现“MST”,所以它尝试解析值字符串中的时区值,当时只有“GMT+0X00”(其中 X 为 '0' 或 '8')- 其他已被消耗(正确)。 Source
case stdTZ:
// Does it look like a time zone?
if len(value) >= 3 && value[0:3] == "UTC" {
z = UTC
value = value[3:]
break
}
n, ok := parseTimeZone(value)
if !ok {
err = errBad
break
}
zoneName, value = value[:n], value[n:]
这里一切都很好。 parseTimeZone 函数为 GMT 格式提供了一种特殊情况,原因对我来说并不明显,但 here is the code:
// Special case 2: GMT may have an hour offset; treat it specially.
if value[:3] == "GMT" {
length = parseGMT(value)
return length, true
}
然后parseGMT 代码就像this:
// parseGMT parses a GMT time zone. The input string is known to start "GMT".
// The function checks whether that is followed by a sign and a number in the
// range -23 through +23 excluding zero.
func parseGMT(value string) int {
value = value[3:]
if len(value) == 0 {
return 3
}
return 3 + parseSignedOffset(value)
}
从评论中,我们已经可以感觉到问题:“+0800”不是从-23 到+23 范围内的数字,不包括前导零(而“+0000”)是。显然,这个函数试图指示(从返回值)消耗超过 3 个字节,即超过“GMT”。我们可以在code of parseSignedOffset确认。
// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
// The function checks for a signed number in the range -23 through +23 excluding zero.
// Returns length of the found offset string or 0 otherwise
func parseSignedOffset(value string) int {
sign := value[0]
if sign != '-' && sign != '+' {
return 0
}
x, rem, err := leadingInt(value[1:])
// fail if nothing consumed by leadingInt
if err != nil || value[1:] == rem {
return 0
}
if sign == '-' {
x = -x
}
if x < -23 || 23 < x {
return 0
}
return len(value) - len(rem)
}
这里,leadingInt 是一个无趣的函数,它将 string 的前缀转换为 int64,以及字符串的其余部分。
所以parseSignedOffset 做了文档宣传的事情:它解析值字符串并查看它是否在-23 到+23 的范围内,但只是表面值:
-
它将+0800 视为800,大于+23,因此返回0。结果parseGMT(和parseTimeZone)返回3,所以Parse函数这次只消耗了3个字节,留下"+0800"匹配"-0700",所以解析正确。
-
它将+0000 视为0,该范围内的有效值,因此它返回5,意味着“+0000”是时区的一部分,因此parseGMT(和parseTimeZone)返回8 ,使Parse 函数消耗整个字符串,使"" 与"-0700" 匹配,从而导致错误。
编辑:
在格式字符串中使用 GMT 并获得“正确”值是因为格式字符串中的“GMT”不被视为时区,而是格式的一部分(就像字符串中的空格),“GMT”时区与默认时区(“UTC”)相同。
你可以time.Parse("Mon Jan 02 2006 15:04:05 XYZ-0700", "Tue Jun 11 2019 13:26:45 XYZ+0800") 不会出错。