登录
首页 >  Golang >  Go教程

golang的时区和神奇的time.Parse的使用方法

来源:脚本之家

时间:2023-02-16 18:53:50 349浏览 收藏

亲爱的编程学习爱好者,如果你点开了这篇文章,说明你对《golang的时区和神奇的time.Parse的使用方法》很感兴趣。本篇文章就来给大家详细解析一下,主要介绍一下时区、time.Parse,希望所有认真读完的童鞋们,都有实质性的提高。

时区

先写一段测试代码:

const TIME_LAYOUT = "2006-01-02 15:04:05"

func parseWithLocation(name string, timeStr string) (time.Time, error) {
 locationName := name
 if l, err := time.LoadLocation(locationName); err != nil {
  println(err.Error())
  return time.Time{}, err
 } else {
  lt, _ := time.ParseInLocation(TIME_LAYOUT, timeStr, l)
  fmt.Println(locationName, lt)
  return lt, nil
 }
}
func testTime() {
 fmt.Println("0. now: ", time.Now())
 str := "2018-09-10 00:00:00"
 fmt.Println("1. str: ", str)
 t, _ := time.Parse(TIME_LAYOUT, str)
 fmt.Println("2. Parse time: ", t)
 tStr := t.Format(TIME_LAYOUT)
 fmt.Println("3. Format time str: ", tStr)
 name, offset := t.Zone()
 name2, offset2 := t.Local().Zone()
 fmt.Printf("4. Zone name: %v, Zone offset: %v\n", name, offset)
 fmt.Printf("5. Local Zone name: %v, Local Zone offset: %v\n", name2, offset2)
 tLocal := t.Local()
 tUTC := t.UTC()
 fmt.Printf("6. t: %v, Local: %v, UTC: %v\n", t, tLocal, tUTC)
 fmt.Printf("7. t: %v, Local: %v, UTC: %v\n", t.Format(TIME_LAYOUT), tLocal.Format(TIME_LAYOUT), tUTC.Format(TIME_LAYOUT))
 fmt.Printf("8. Local.Unix: %v, UTC.Unix: %v\n", tLocal.Unix(), tUTC.Unix())
 str2 := "1969-12-31 23:59:59"
 t2, _ := time.Parse(TIME_LAYOUT, str2)
 fmt.Printf("9. str2:%v,time: %v, Unix: %v\n", str2, t2, t2.Unix())
 fmt.Printf("10. %v, %v\n", tLocal.Format(time.ANSIC), tUTC.Format(time.ANSIC))
 fmt.Printf("11. %v, %v\n", tLocal.Format(time.RFC822), tUTC.Format(time.RFC822))
 fmt.Printf("12. %v, %v\n", tLocal.Format(time.RFC822Z), tUTC.Format(time.RFC822Z))

 //指定时区
 parseWithLocation("America/Cordoba", str)
 parseWithLocation("Asia/Shanghai", str)
 parseWithLocation("Asia/Beijing", str)
}
testTime()

输出:

0. now:  2018-09-19 19:06:07.3642781 +0800 CST m=+0.005995601
1. str:  2018-09-10 00:00:00
2. Parse time:  2018-09-10 00:00:00 +0000 UTC
3. Format time str:  2018-09-10 00:00:00
4. Zone name: UTC, Zone offset: 0
5. Local Zone name: CST, Local Zone offset: 28800
6. 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
7. t: 2018-09-10 00:00:00, Local: 2018-09-10 08:00:00, UTC: 2018-09-10 00:00:00
8. Local.Unix: 1536537600, UTC.Unix: 1536537600
9. str2:1969-12-31 23:59:59,time: 1969-12-31 23:59:59 +0000 UTC, Unix: -1
10. Mon Sep 10 08:00:00 2018, Mon Sep 10 00:00:00 2018
11. 10 Sep 18 08:00 CST, 10 Sep 18 00:00 UTC
12. 10 Sep 18 08:00 +0800, 10 Sep 18 00:00 +0000
America/Cordoba 2018-09-10 00:00:00 -0300 -03
Asia/Shanghai 2018-09-10 00:00:00 +0800 CST
cannot 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:

const (
 ANSIC  = "Mon Jan _2 15:04:05 2006"
 UnixDate = "Mon Jan _2 15:04:05 MST 2006"
 RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
 RFC822  = "02 Jan 06 15:04 MST"
 RFC822Z  = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
 RFC850  = "Monday, 02-Jan-06 15:04:05 MST"
 RFC1123  = "Mon, 02 Jan 2006 15:04:05 MST"
 RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
 RFC3339  = "2006-01-02T15:04:05Z07:00"
 RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
 Kitchen  = "3:04PM"
 // Handy time stamps.
 Stamp  = "Jan _2 15:04:05"
 StampMilli = "Jan _2 15:04:05.000"
 StampMicro = "Jan _2 15:04:05.000000"
 StampNano = "Jan _2 15:04:05.000000000"
)

还可以自定义layout,比如:

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

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

// nextStdChunk finds the first occurrence of a std string in
// layout and returns the text before, the std string, and the text after.
func nextStdChunk(layout string) (prefix string, std int, suffix string) {
 for i := 0; i = i+3 && layout[i:i+3] == "Jan" {
    if len(layout) >= i+7 && layout[i:i+7] == "January" {
     return layout[0:i], stdLongMonth, layout[i+7:]
    }
    if !startsWithLowerCase(layout[i+3:]) {
     return layout[0:i], stdMonth, layout[i+3:]
    }
   }

  case 'M': // Monday, Mon, MST
   if len(layout) >= i+3 {
    if layout[i:i+3] == "Mon" {
     if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
      return layout[0:i], stdLongWeekDay, layout[i+6:]
     }
     if !startsWithLowerCase(layout[i+3:]) {
      return layout[0:i], stdWeekDay, layout[i+3:]
     }
    }
    if layout[i:i+3] == "MST" {
     return layout[0:i], stdTZ, layout[i+3:]
    }
   }

  case '0': // 01, 02, 03, 04, 05, 06
   if len(layout) >= i+2 && '1' = i+2 && layout[i+1] == '5' {
    return layout[0:i], stdHour, layout[i+2:]
   }
   return layout[0:i], stdNumMonth, layout[i+1:]

  case '2': // 2006, 2
   if len(layout) >= i+4 && layout[i:i+4] == "2006" {
    return layout[0:i], stdLongYear, layout[i+4:]
   }
   return layout[0:i], stdDay, layout[i+1:]

  case '_': // _2, _2006
   if len(layout) >= i+2 && layout[i+1] == '2' {
    //_2006 is really a literal _, followed by stdLongYear
    if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
     return layout[0 : i+1], stdLongYear, layout[i+5:]
    }
    return layout[0:i], stdUnderDay, layout[i+2:]
   }

  case '3':
   return layout[0:i], stdHour12, layout[i+1:]

  case '4':
   return layout[0:i], stdMinute, layout[i+1:]

  case '5':
   return layout[0:i], stdSecond, layout[i+1:]

  case 'P': // PM
   if len(layout) >= i+2 && layout[i+1] == 'M' {
    return layout[0:i], stdPM, layout[i+2:]
   }

  case 'p': // pm
   if len(layout) >= i+2 && layout[i+1] == 'm' {
    return layout[0:i], stdpm, layout[i+2:]
   }

  case '-': // -070000, -07:00:00, -0700, -07:00, -07
   if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
    return layout[0:i], stdNumSecondsTz, layout[i+7:]
   }
   if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
    return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
   }
   if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
    return layout[0:i], stdNumTZ, layout[i+5:]
   }
   if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
    return layout[0:i], stdNumColonTZ, layout[i+6:]
   }
   if len(layout) >= i+3 && layout[i:i+3] == "-07" {
    return layout[0:i], stdNumShortTZ, layout[i+3:]
   }

  case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
   if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
    return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
   }
   if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
    return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
   }
   if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
    return layout[0:i], stdISO8601TZ, layout[i+5:]
   }
   if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
    return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
   }
   if len(layout) >= i+3 && layout[i:i+3] == "Z07" {
    return layout[0:i], stdISO8601ShortTZ, layout[i+3:]
   }

  case '.': // .000 or .999 - repeated digits for fractional seconds.
   if i+1 

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

比如年份:短年份06,长年份2006,
月份:01,Jan,January
日:02,2,_2
时:15,3,03
分:04, 4
秒:05, 5

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

const (
 _      = iota
 stdLongMonth    = iota + stdNeedDate // "January"
 stdMonth          // "Jan"
 stdNumMonth         // "1"
 stdZeroMonth         // "01"
 stdLongWeekDay         // "Monday"
 stdWeekDay          // "Mon"
 stdDay           // "2"
 stdUnderDay         // "_2"
 stdZeroDay          // "02"
 stdHour     = iota + stdNeedClock // "15"
 stdHour12          // "3"
 stdZeroHour12         // "03"
 stdMinute          // "4"
 stdZeroMinute         // "04"
 stdSecond          // "5"
 stdZeroSecond         // "05"
 stdLongYear    = iota + stdNeedDate // "2006"
 stdYear          // "06"
 stdPM     = iota + stdNeedClock // "PM"
 stdpm           // "pm"
 stdTZ     = iota    // "MST"
 stdISO8601TZ         // "Z0700" // prints Z for UTC
 stdISO8601SecondsTZ       // "Z070000"
 stdISO8601ShortTZ        // "Z07"
 stdISO8601ColonTZ        // "Z07:00" // prints Z for UTC
 stdISO8601ColonSecondsTZ      // "Z07:00:00"
 stdNumTZ          // "-0700" // always numeric
 stdNumSecondsTz        // "-070000"
 stdNumShortTZ         // "-07" // always numeric
 stdNumColonTZ         // "-07:00" // always numeric
 stdNumColonSecondsTZ       // "-07:00:00"
 stdFracSecond0         // ".0", ".00", ... , trailing zeros included
 stdFracSecond9         // ".9", ".99", ..., trailing zeros omitted

 stdNeedDate = 1 

时区:

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

func testTimeParse() {
 t, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", "2018-09-20 15:39:06 +0800 CST")
 fmt.Println(t)
 t, _ = time.Parse("2006-01-02 15:04:05 -0700 MST", "2018-09-20 15:39:06 +0000 CST")
 fmt.Println(t)
 t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST", "2018-09-20 15:39:06 +0800 CST")
 fmt.Println(t)
 t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST", "2018-09-20 15:39:06 Z GMT")
 fmt.Println(t)
 t, _ = time.Parse("2006-01-02 15:04:05 Z0700 MST", "2018-09-20 15:39:06 +0000 GMT")
 fmt.Println(t)
}

输出:
2018-09-20 15:39:06 +0800 CST
2018-09-20 15:39:06 +0000 CST
2018-09-20 15:39:06 +0800 CST
2018-09-20 15:39:06 +0000 UTC
2018-09-20 15:39:06 +0000 GMT

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

今天关于《golang的时区和神奇的time.Parse的使用方法》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!

声明:本文转载于:脚本之家 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>