Golang日期处理
格式化日期字符串
https://stackoverflow.com/questions/20234104/how-to-format-current-time-using-a-yyyymmddhhmmss-format
Golang 中的时间字符串格式化和 C/C++ 以及 Python 等语言中不同,它没有 strftime 函数。 参考其官方文档,它使用模板(layout)来定义格式化和解析时间字符串的格式。
根据其源码中的注释,https://golang.org/src/time/format.go
// These are predefined layouts for use in Time.Format and time.Parse.
// The reference time used in the layouts is the specific time:
// Mon Jan 2 15:04:05 MST 2006
// which is Unix time 1136239445. Since MST is GMT-0700,
// the reference time can be thought of as
// 01/02 03:04:05PM '06 -0700
要自定义时间格式,只用按 2006年1月2日3点(15点)4分5秒 这个时间来变换来组合即可。其中可以0来补齐位数,也可以使用 _
来表示使用空格对齐某个字段。
例如在有 strftime 的语言中(例如 Python 中),获取当前的时间戳并格式如下
import datetime.datetime
n = datetime.datetime.now()
n.strftime("%Y-%m-%d %H:%M:%S")
# Out '2019-06-17 16:47:26'
在 Golang 中,按照上面的规定,layout 如下
https://play.golang.org/p/eOR9YF_kVM9
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
layout := "2006-01-02 15:04:05"
fmt.Println(t.Format(layout))
}
但要格式化成例如 “20190617” 这样的字符串时,使用 layout 会产生歧义无法得到想要的结果, 这时就需要获取日期的年、月、日字段后直接格式化字符串了。这种情况下 strftime 还是更加通用一些。
date := fmt.Sprintf("%d%02d%02d",
t.Year(), t.Month(), t.Day())
fmt.Println(date)
时间比较
Golang 中的 time package 源码位于 https://golang.org/src/time/time.go, 使用前大致浏览一下源码有助于更好的使用。
type Time struct {
// wall and ext encode the wall time seconds, wall time nanoseconds,
// and optional monotonic clock reading in nanoseconds.
//
// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
// The nanoseconds field is in the range [0, 999999999].
// If the hasMonotonic bit is 0, then the 33-bit field must be zero
// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
// unsigned wall seconds since Jan 1 year 1885, and ext holds a
// signed 64-bit monotonic clock reading, nanoseconds since process start.
wall uint64
ext int64
// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location
}
const (
hasMonotonic = 1 << 63
maxWall = wallToInternal + (1<<33 - 1) // year 2157
minWall = wallToInternal // year 1885
nsecMask = 1<<30 - 1
nsecShift = 30
)
因为 Golang 中不支持函数重载和运算符重载,时间的比较需要显示调用 Before、Equal、After 等函数来判断。
// sec returns the time's seconds since Jan 1 year 1.
func (t *Time) sec() int64 {
if t.wall&hasMonotonic != 0 {
return wallToInternal + int64(t.wall<<1>>(nsecShift+1))
}
return t.ext
}
func (t Time) After(u Time) bool {
if t.wall&u.wall&hasMonotonic != 0 {
return t.ext > u.ext
}
ts := t.sec()
us := u.sec()
return ts > us || ts == us && t.nsec() > u.nsec()
}
时区处理
从 mysql 的 datetime 字段取出的数据一般为 “2019-09-24 18:00:00” 格式,其隐含有时区为东八区的信息。
而 t.Unix() 函数会忽略时区信息,统一按 UTC 时间处理。所以生成 unix 时间戳的时候需要注意。
示例代码如下
package main
import (
"fmt"
"time"
)
func main() {
datetimeLayout := "2006-01-02 15:04:05"
timeStr := "2019-09-24 18:00:00"
timelocal := time.FixedZone("CST", 3600*8)
t1, _ := time.Parse(datetimeLayout, timeStr)
t2, _ := time.ParseInLocation(datetimeLayout, timeStr, timelocal)
fmt.Println(t1, t1.Unix())
fmt.Println(t2, t2.UTC(), 2019-09-24 10:00:00, t2.UTC().Unix())
fmt.Println(time.Unix(t1.Unix(), 0).Format(datetimeLayout))
fmt.Println(time.Unix(t2.UTC().Unix(), 0).Format(datetimeLayout))
fmt.Println(time.Unix(t2.UTC().Unix(), 0).In(timelocal).Format(datetimeLayout))
}
对应 playgroud 中运行的结果如下:
2019-09-24 18:00:00 +0000 UTC 1569348000
2019-09-24 18:00:00 +0800 CST 2019-09-24 10:00:00 +0000 UTC 1569319200
2019-09-24 18:00:00
2019-09-24 10:00:00
2019-09-24 18:00:00
对于时间字符串如果不指定时区调用 Parse 函数,默认是按 UTC 时间解析的,
可以看到第一个输出的结果为 2019-09-24 18:00:00 +0000 UTC
,而北京时间下午六点实际应该对应 2019-09-24 10:00:00 +0000 UTC
。
而使用 ParseInLocation 得到的结果则是正确的。
而转换回unix时间戳时,带时区信息的 time 结构体需要先调用 .UTC() 转换为 Unix 时间,再调用 Unix(),才能得到正确的时间戳。