Appearance
一直以为 Asia/Shanghai
就等于 UTC+0800,直到最近遇到了个 bug,才发现它包含了 LMT、CDT、CST。
Asia/Shanghai
和 Etc/GMT-8
类似于 Asia/Shanghai
这种时区表达形式,来自 IANA 的时区数据库, 用区域和地点来表示,记录了当地的时区变化(隐藏在时区里的历史)。
Etc 用作一些管理时区,例如 Etc/GMT-8
代表东八区,正负号与 ISO 8601 相反。
夏令时
为了节约能源,人为规定时间,搞了个夏令时(Daylight Saving Time:DST)出来,在春夏的时间调快一个小时。
1919 年,民国政府在上海和天津曾短暂地实行了一年夏令时,共和国在 1986 年至 1991 年实行了六年夏令时。 所以在这些时间段内,Asia/Shanghai
的真正时区是 +0900 CDT
,其他时候是 +0800 CST
。
在显示给用户的时候常常直接隐藏了时区信息,在夏令时交替的那一个小时就会有歧义。 比如 1991-09-15 01:00:00
可以是 CDT 也可以是 CST。
go
sh, _ := time.LoadLocation("Asia/Shanghai")
g8, _ := time.LoadLocation("Etc/GMT-8")
ti = time.Date(1919, 9, 15, 0, 0, 0, 0, sh)
fmt.Println(ti) // 1919-09-15 00:00:00 +0900 CDT
ti = time.Date(1991, 9, 15, 0, 0, 0, 0, sh)
fmt.Println(ti) // 1991-09-15 01:00:00 +0900 CDT
ti = time.Unix(ti.Unix(), 0).In(g8)
fmt.Println(ti) // 1991-09-15 00:00:00 +0800 +08
ti = time.Date(1991, 9, 15, 1, 0, 0, 0, sh)
fmt.Println(ti) // 1991-09-15 01:00:00 +0800 CST
ti = time.Unix(ti.Unix(), 0).In(g8)
fmt.Println(ti) // 1991-09-15 01:00:00 +0800 +08
ti, err := time.ParseInLocation("2006-01-02 15:04:05", "1991-09-15 01:00:00", sh)
fmt.Println(ti, err) // 1991-09-15 01:00:00 +0800 CST <nil>
地方平时 LMT
地方平时 LMT(Local Mean Time) 是太阳时改变形式修正后,在指定的经度范围内使用一致时间的地方太阳时。 从 19 世纪初期开始逐渐被采用,后被标准时间取代。
比如中国在 19 世纪末改为东经 120 度标准时,切换时刻为当地时间 1901 年元旦零点。
go
// +8:05:43
shLMT := time.FixedZone("SHLMT", 3600*8+60*5+43)
ti = time.Date(1901, 1, 1, 0, 0, 0, 0, shLMT)
fmt.Println(ti) // 1901-01-01 00:00:00 +0805 SHLMT
ti = time.Unix(ti.Unix(), 0).In(g8)
fmt.Println(ti) // 1900-12-31 23:54:17 +0800 +08
ti = time.Date(1900, 12, 31, 23, 54, 16, 0, sh)
fmt.Println(ti) // 1900-12-31 23:54:16 +0805 LMT
ti = time.Unix(ti.Unix(), 0).In(g8)
fmt.Println(ti) // 1900-12-31 23:48:33 +0800 +08
ti = time.Date(1900, 12, 31, 23, 54, 17, 0, sh)
fmt.Println(ti) // 1900-12-31 23:54:17 +0800 CST
ti = time.Unix(ti.Unix(), 0).In(g8)
fmt.Println(ti) // 1900-12-31 23:54:17 +0800 +08
避坑
任何需要隐藏时区信息的时候,就应该使用 Etc/GMT-8
来避免历史政治因素带来的坑。