时区

先写一段测试代码:

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 < len(layout); i++ {
  switch c := int(layout[i]); c {
  case 'j': // january, jan
   if len(layout) >= 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' <= layout[i+1] && layout[i+1] <= '6' {
    return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
   }

  case '1': // 15, 1
   if len(layout) >= 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 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
    ch := layout[i+1]
    j := i + 1
    for j < len(layout) && layout[j] == ch {
     j++
    }
    // string of digits must end here - only fractional second is all digits.
    if !isdigit(layout, j) {
     std := stdfracsecond0
     if layout[i+1] == '9' {
      std = stdfracsecond9
     }
     std |= (j - (i + 1)) << stdargshift
     return layout[0:i], std, layout[j:]
    }
   }
  }
 }
 return layout, 0, ""
}

可以发现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 << 8    // need month, day, year
 stdneedclock = 2 << 8    // need hour, minute, second
 stdargshift = 16     // extra argument in high bits, above low stdargshift
 stdmask  = 1<<stdargshift - 1 // mask out argument
)

时区:

时区使用: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的时区和time.parse内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!