格式化日期字符串

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(),才能得到正确的时间戳。