天天看點

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語言社群

”,了解相關資訊可以關注“

”。