golang的时区和神奇的time.Parse

简介:

时区

先写一段测试代码:

 1const TIME_LAYOUT = "2006-01-02 15:04:05"
2
3func parseWithLocation(name string, timeStr string) (time.Time, error) {
4    locationName := name
5    if l, err := time.LoadLocation(locationName); err != nil {
6        println(err.Error())
7        return time.Time{}, err
8    } else {
9        lt, _ := time.ParseInLocation(TIME_LAYOUT, timeStr, l)
10        fmt.Println(locationName, lt)
11        return lt, nil
12    }
13}
14func testTime() {
15    fmt.Println("0. now: ", time.Now())
16    str := "2018-09-10 00:00:00"
17    fmt.Println("1. str: ", str)
18    t, _ := time.Parse(TIME_LAYOUT, str)
19    fmt.Println("2. Parse time: ", t)
20    tStr := t.Format(TIME_LAYOUT)
21    fmt.Println("3. Format time str: ", tStr)
22    name, offset := t.Zone()
23    name2, offset2 := t.Local().Zone()
24    fmt.Printf("4. Zone name: %v, Zone offset: %v\n", name, offset)
25    fmt.Printf("5. Local Zone name: %v, Local Zone offset: %v\n", name2, offset2)
26    tLocal := t.Local()
27    tUTC := t.UTC()
28    fmt.Printf("6. t: %v, Local: %v, UTC: %v\n", t, tLocal, tUTC)
29    fmt.Printf("7. t: %v, Local: %v, UTC: %v\n", t.Format(TIME_LAYOUT), tLocal.Format(TIME_LAYOUT), tUTC.Format(TIME_LAYOUT))
30    fmt.Printf("8. Local.Unix: %v, UTC.Unix: %v\n", tLocal.Unix(), tUTC.Unix())
31    str2 := "1969-12-31 23:59:59"
32    t2, _ := time.Parse(TIME_LAYOUT, str2)
33    fmt.Printf("9. str2:%v,time: %v, Unix: %v\n", str2, t2, t2.Unix())
34    fmt.Printf("10. %v, %v\n", tLocal.Format(time.ANSIC), tUTC.Format(time.ANSIC))
35    fmt.Printf("11. %v, %v\n", tLocal.Format(time.RFC822), tUTC.Format(time.RFC822))
36    fmt.Printf("12. %v, %v\n", tLocal.Format(time.RFC822Z), tUTC.Format(time.RFC822Z))
37
38    //指定时区
39    parseWithLocation("America/Cordoba", str)
40    parseWithLocation("Asia/Shanghai", str)
41    parseWithLocation("Asia/Beijing", str)
42}
43testTime()

输出:

 10. now:  2018-09-19 19:06:07.3642781 +0800 CST m=+0.005995601
21. str:  2018-09-10 00:00:00
32. Parse time:  2018-09-10 00:00:00 +0000 UTC
43. Format time str:  2018-09-10 00:00:00
54. Zone name: UTC, Zone offset: 0
65. Local Zone name: CST, Local Zone offset: 28800
76. t: 2018-09-10 00:00:00 +0000 UTC, Local: 2018-09-10 08:00:00 +0800 CST, UTC: 2018-09-10 00:00:00 +0000 UTC
87. t: 2018-09-10 00:00:00, Local: 2018-09-10 08:00:00, UTC: 2018-09-10 00:00:00
98. Local.Unix: 1536537600, UTC.Unix: 1536537600
109. str2:1969-12-31 23:59:59,time: 1969-12-31 23:59:59 +0000 UTC, Unix: -1
1110. Mon Sep 10 08:00:00 2018, Mon Sep 10 00:00:00 2018
1211. 10 Sep 18 08:00 CST, 10 Sep 18 00:00 UTC
1312. 10 Sep 18 08:00 +0800, 10 Sep 18 00:00 +0000
14America/Cordoba 2018-09-10 00:00:00 -0300 -03
15Asia/Shanghai 2018-09-10 00:00:00 +0800 CST
16cannot find Asia/Beijing in zip file C:\Go\/lib/time/zoneinfo.zip

从以上代码的测试结果可以得出几点:

 ●  time.Now 得到的当前时间的时区跟电脑的当前时区一样。
 ●  time.Parse 把时间字符串转换为Time,时区是UTC时区。
 ●  不管Time变量存储的是什么时区,其Unix()方法返回的都是距离UTC时间:1970年1月1日0点0分0秒的秒数。
 ●  Unix()返回的秒数可以是负数,如果时间小于1970-01-01 00:00:00的话。
 ●  Zone方法可以获得变量的时区和时区与UTC的偏移秒数,应该支持夏令时和冬令时。
 ●  time.LoadLocation可以根据时区名创建时区Location,所有的时区名字可以在$GOROOT/lib/time/zoneinfo.zip文件中找到,解压zoneinfo.zip可以得到一堆目录和文件,我们只需要目录和文件的名字,时区名是目录名+文件名,比如"Asia/Shanghai"。中国时区名只有"Asia/Shanghai""Asia/Chongqing",而没有"Asia/Beijing"
 ●  time.ParseInLocation可以根据时间字符串和指定时区转换Time。

 ●  感谢中国只有一个时区而且没有夏令时和冬令时,可怕的美国居然有6个时区,想想都可怕。

神奇的time.Parse

一开始使用time.Parse时很不习惯,因为非常奇怪的layout参数。
除了golang自带定义的layout

 1const (
2    ANSIC       = "Mon Jan _2 15:04:05 2006"
3    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
4    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
5    RFC822      = "02 Jan 06 15:04 MST"
6    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
7    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
8    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
9    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
10    RFC3339     = "2006-01-02T15:04:05Z07:00"
11    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
12    Kitchen     = "3:04PM"
13    // Handy time stamps.
14    Stamp      = "Jan _2 15:04:05"
15    StampMilli = "Jan _2 15:04:05.000"
16    StampMicro = "Jan _2 15:04:05.000000"
17    StampNano  = "Jan _2 15:04:05.000000000"
18)

还可以自定义layout,比如:

1"2006-01-02 15:04:05"

网上基本上都在传说这个日子是golang项目开始创建的时间,为了纪念生日才这样设计,其实这真是无稽之谈瞎扯淡。
网上文章没有找到说的比较清楚的,幸好有源码,打开time.Parse的源码看了一下,发现这个设计很好很科学。
解析layout的主要代码在nextStdChunk方法中:

  1// nextStdChunk finds the first occurrence of a std string in
 2// layout and returns the text before, the std string, and the text after.
 3func nextStdChunk(layout string) (prefix string, std int, suffix string) {
 4    for i := 0; i < len(layout); i++ {
 5        switch c := int(layout[i]); c {
 6        case 'J'// January, Jan
 7            if len(layout) >= i+3 && layout[i:i+3] == "Jan" {
 8                if len(layout) >= i+7 && layout[i:i+7] == "January" {
 9                    return layout[0:i], stdLongMonth, layout[i+7:]
10                }
11                if !startsWithLowerCase(layout[i+3:]) {
12                    return layout[0:i], stdMonth, layout[i+3:]
13                }
14            }
15
16        case 'M'// Monday, Mon, MST
17            if len(layout) >= i+3 {
18                if layout[i:i+3] == "Mon" {
19                    if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
20                        return layout[0:i], stdLongWeekDay, layout[i+6:]
21                    }
22                    if !startsWithLowerCase(layout[i+3:]) {
23                        return layout[0:i], stdWeekDay, layout[i+3:]
24                    }
25                }
26                if layout[i:i+3] == "MST" {
27                    return layout[0:i], stdTZ, layout[i+3:]
28                }
29            }
30
31        case '0'// 01, 02, 03, 04, 05, 06
32            if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
33                return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
34            }
35
36        case '1'// 15, 1
37            if len(layout) >= i+2 && layout[i+1] == '5' {
38                return layout[0:i], stdHour, layout[i+2:]
39            }
40            return layout[0:i], stdNumMonth, layout[i+1:]
41
42        case '2'// 2006, 2
43            if len(layout) >= i+4 && layout[i:i+4] == "2006" {
44                return layout[0:i], stdLongYear, layout[i+4:]
45            }
46            return layout[0:i], stdDay, layout[i+1:]
47
48        case '_'// _2, _2006
49            if len(layout) >= i+2 && layout[i+1] == '2' {
50                //_2006 is really a literal _, followed by stdLongYear
51                if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
52                    return layout[0 : i+1], stdLongYear, layout[i+5:]
53                }
54                return layout[0:i], stdUnderDay, layout[i+2:]
55            }
56
57        case '3':
58            return layout[0:i], stdHour12, layout[i+1:]
59
60        case '4':
61            return layout[0:i], stdMinute, layout[i+1:]
62
63        case '5':
64            return layout[0:i], stdSecond, layout[i+1:]
65
66        case 'P'// PM
67            if len(layout) >= i+2 && layout[i+1] == 'M' {
68                return layout[0:i], stdPM, layout[i+2:]
69            }
70
71        case 'p'// pm
72            if len(layout) >= i+2 && layout[i+1] == 'm' {
73                return layout[0:i], stdpm, layout[i+2:]
74            }
75
76        case '-'// -070000, -07:00:00, -0700, -07:00, -07
77            if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
78                return layout[0:i], stdNumSecondsTz, layout[i+7:]
79            }
80            if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
81                return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
82            }
83            if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
84                return layout[0:i], stdNumTZ, layout[i+5:]
85            }
86            if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
87                return layout[0:i], stdNumColonTZ, layout[i+6:]
88            }
89            if len(layout) >= i+3 && layout[i:i+3] == "-07" {
90                return layout[0:i], stdNumShortTZ, layout[i+3:]
91            }
92
93        case 'Z'// Z070000, Z07:00:00, Z0700, Z07:00,
94            if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
95                return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
96            }
97            if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
98                return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
99            }
100            if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
101                return layout[0:i], stdISO8601TZ, layout[i+5:]
102            }
103            if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
104                return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
105            }
106            if len(layout) >= i+3 && layout[i:i+3] == "Z07" {
107                return layout[0:i], stdISO8601ShortTZ, layout[i+3:]
108            }
109
110        case '.'// .000 or .999 - repeated digits for fractional seconds.
111            if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
112                ch := layout[i+1]
113                j := i + 1
114                for j < len(layout) && layout[j] == ch {
115                    j++
116                }
117                // String of digits must end here - only fractional second is all digits.
118                if !isDigit(layout, j) {
119                    std := stdFracSecond0
120                    if layout[i+1] == '9' {
121                        std = stdFracSecond9
122                    }
123                    std |= (j - (i + 1)) << stdArgShift
124                    return layout[0:i], std, layout[j:]
125                }
126            }
127        }
128    }
129    return layout, 0""
130}

可以发现layout的所有代表年月日时分秒甚至时区的值都是互斥不相等的。

比如年份:短年份06,长年份2006,

月份:01,Jan,January

日:02,2,_2

时:15,3,03

分:04, 4

秒:05, 5

因为都不相等所以通过遍历layout就可以switch case解析出每个区块的意义和在字符串中的位置,这样输入对应格式的时间字符串就可以顺利解析出来。
这样layout也可以自定义,而且顺序任意,只要符合下列每个区块定义的规则即可,
代码中的注释就是规则写法:

 1const (
2    _                        = iota
3    stdLongMonth             = iota + stdNeedDate  // "January"
4    stdMonth                                       // "Jan"
5    stdNumMonth                                    // "1"
6    stdZeroMonth                                   // "01"
7    stdLongWeekDay                                 // "Monday"
8    stdWeekDay                                     // "Mon"
9    stdDay                                         // "2"
10    stdUnderDay                                    // "_2"
11    stdZeroDay                                     // "02"
12    stdHour                  = iota + stdNeedClock // "15"
13    stdHour12                                      // "3"
14    stdZeroHour12                                  // "03"
15    stdMinute                                      // "4"
16    stdZeroMinute                                  // "04"
17    stdSecond                                      // "5"
18    stdZeroSecond                                  // "05"
19    stdLongYear              = iota + stdNeedDate  // "2006"
20    stdYear                                        // "06"
21    stdPM                    = iota + stdNeedClock // "PM"
22    stdpm                                          // "pm"
23    stdTZ                    = iota                // "MST"
24    stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
25    stdISO8601SecondsTZ                            // "Z070000"
26    stdISO8601ShortTZ                              // "Z07"
27    stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
28    stdISO8601ColonSecondsTZ                       // "Z07:00:00"
29    stdNumTZ                                       // "-0700"  // always numeric
30    stdNumSecondsTz                                // "-070000"
31    stdNumShortTZ                                  // "-07"    // always numeric
32    stdNumColonTZ                                  // "-07:00" // always numeric
33    stdNumColonSecondsTZ                           // "-07:00:00"
34    stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
35    stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted
36
37    stdNeedDate  = 1 << 8             // need month, day, year
38    stdNeedClock = 2 << 8             // need hour, minute, second
39    stdArgShift  = 16                 // extra argument in high bits, above low stdArgShift
40    stdMask      = 1<<stdArgShift - 1 // mask out argument
41)

时区:
时区使用:MST
时区偏移使用-0700或者Z0700等等。
下面是一个使用时区的例子,Z0700比较特殊,当输入时间直接使用Z时就直接代表UTC时区。

 1func testTimeParse() {
2    t, _ := time.Parse("2006-01-02 15:04:05 -0700 MST""2018-09-20 15:39:06 +0800 CST")
3    fmt.Println(t)
4    t, _ = time.Parse("2006-01-02 15:04:05 -0700 MST""2018-09-20 15:39:06 +0000 CST")
5    fmt.Println(t)
6    t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST""2018-09-20 15:39:06 +0800 CST")
7    fmt.Println(t)
8    t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST""2018-09-20 15:39:06 Z GMT")
9    fmt.Println(t)
10    t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST""2018-09-20 15:39:06 +0000 GMT")
11    fmt.Println(t)
12}
13输出:
142018-09-20 15:39:06 +0800 CST
152018-09-20 15:39:06 +0000 CST
162018-09-20 15:39:06 +0800 CST
172018-09-20 15:39:06 +0000 UTC
182018-09-20 15:39:06 +0000 GMT

还有疑问的可以看看go自带的测试例子:
Go/src/time/example_test.go


原文发布时间为:2018-09-23

本文作者:云上听风

本文来自云栖社区合作伙伴“Golang语言社区”,了解相关信息可以关注“Golang语言社区”。

相关文章
|
30天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
79 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
46 7
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
113 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
116 67
|
1天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
17 5
|
11天前
|
算法 安全 Go
Go语言中的加密和解密是如何实现的?
Go语言通过标准库中的`crypto`包提供丰富的加密和解密功能,包括对称加密(如AES)、非对称加密(如RSA、ECDSA)及散列函数(如SHA256)。`encoding/base64`包则用于Base64编码与解码。开发者可根据需求选择合适的算法和密钥,使用这些包进行加密操作。示例代码展示了如何使用`crypto/aes`包实现对称加密。加密和解密操作涉及敏感数据处理,需格外注意安全性。
33 14
|
11天前
|
Go 数据库
Go语言中的包(package)是如何组织的?
在Go语言中,包是代码组织和管理的基本单元,用于集合相关函数、类型和变量,便于复用和维护。包通过目录结构、文件命名、初始化函数(`init`)及导出规则来管理命名空间和依赖关系。合理的包组织能提高代码的可读性、可维护性和可复用性,减少耦合度。例如,`stringutils`包提供字符串处理函数,主程序导入使用这些函数,使代码结构清晰易懂。
50 11
|
11天前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
15天前
|
监控 安全 算法
深度剖析核心科技:Go 语言赋能局域网管理监控软件进阶之旅
在局域网管理监控中,跳表作为一种高效的数据结构,能显著提升流量索引和查询效率。基于Go语言的跳表实现,通过随机化索引层生成、插入和搜索功能,在高并发场景下展现卓越性能。跳表将查询时间复杂度优化至O(log n),助力实时监控异常流量,保障网络安全与稳定。示例代码展示了其在实际应用中的精妙之处。
37 9