golang中的package time
提供了用来表示时间的相关数据结构,包括:
time.Location
表示一个时区信息;time.Time
表示一个带时区的时间信息;time.Duration
表示一个可转换成任意单位的时间长度信息;time.Ticker
表示一个周期定时器,用来执行周期性任务。本文就主要总结这几种数据结构的常见用法。
日常生活中,类似下面这样的对话,每时每刻都在发生:
- “现在是几点了?”
- “嗯,6点半了”
- “哇,下班了,走!”
时间
“下午6点半”,这就是一个 时间点 了。说的更具体点(精确到秒),“2021年05月28日 18点30分”。
时区
为什么上面的要加个“中国”前缀呢?为什么全球不能统一时间呢?
这是因为人们都是通过观察太阳的位置来决定时间的:早上七点跟太阳一起起床;中午12点;晚上7点跟太阳一起下班。但是,由于地球的自转,导致不同经度的人,在同一时刻看到的太阳的位置是不一样的。比如说,在中国处于正午12点的时候,美国东部是凌晨0点呢。
因此,人们以经度为准则,以被15整除的经线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时。因此推算,东12区与西12区为同一地方,为避免同一地方日期却相差一天的矛盾,提出国际换日线的概念,国际换日线东侧比西侧迟整整24小时。
但是,为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区。具体可以参见时区列表。
时区有好几种表示方式:
标准时间
时区,只是给不同地区的人们划定了时间差。例如,位于东八区的中国,比东九区的日本时间相差1个小时。
但是,它无法解决时间基准问题。即,位于零时区的时间到底是多少?这应该是全球所有地区都认同的一个标准时间。
这就不得不提到两个非常常见的缩写了:GMT 和 UTC。关于 GMT 和 UTC 的具体信息和历史沿革,大家可以参考各自的维基百科。这里主要注意现在它两的区别:
总结来说,UTC是时间标准,各地区的时间根据当地的时区,用 UTC+N 来表示当地时间。
time.Time
在计算机中,表示一个时间戳有两种常见方式:
"2006-01-02T15:04:05Z07:00"
,通常用某个结构体来存储;"1970-01-01 00:00:00 UTC"
以来经历的秒数,通常用整数来存储。其中,绝对时间由两部分信息组成:时间戳 和 时区;相对时间则与时区无关,因为是两个UTC时间的差值。
从信息的角度来看,绝对时间比相对时间多包涵了时区的信息;抛开时区,两者可以互相转换。
在golang中,我们用time.Time
结构体来存储绝对时间。
type Time struct {
wall uint64
ext int64
loc *Location
}
结构体很简单,只包含3个成员变量:wall
和ext
表示时间戳;loc
表示时区。不过在介绍wall
和ext
的具体意义之前,我们先来看一组概念:墙上时间 和 单调时间。
墙上时间(Wall Clock)
顾名思义,挂在墙上的时钟,用来告诉人们现在的时间。虽然从理论上全球所有计算机的墙上时间应该是完全一致的,但是现实中你无法保证这种完全一致性,也因此计算机的墙上时间必须能够被更改,以便与其它计算机保持同步。
单调时间(Monotonic Clock)
既然墙上时间是可能变的,那么在需要度量某个时间段的长度时,就不能用墙上时间来计算了。因此,单调时间就出现了:它表示的是计算机自启动之后单调递增的时间。
回顾下Linux系统上的
clock_gettime
函数,它就是通过参数指定是返回墙上时间还是单调时间的struct timespec
结构体。
golang中的time.Time
就非常贴心啦!它将墙上时钟和单调时钟封装在一起,并且:在需要进行读操作时,就用墙上时钟;在需要进行计算时间差值时,就用单调时钟。
这里只是告诉大家有这么个知识点,至于具体到某个函数,用的是墙上时钟,还是单调时钟,还是要以代码和代码中的注释为准!
再回过头来看结构体中的wall
和 ext
变量的意义。
wall
的64位从高到低依次表示:1位的标记位(hasMonotonic
);33位的秒字段;30位的纳秒字段。ext
的内容则依赖hasMonotonic
标记位的值:
wall
的秒字段值为0,且ext
存储的是从 "0001年01月01日"到该时间点经过的墙上秒数;wall
的秒字段存储的是从 "1885年01月01日"到该时间点经过的墙上秒数,且ext
字段存储的是从进程启动开始的单调时钟(单位为纳秒)。当然,实际使用时,我们几乎不用关心这两个字段的意义(因为都是小写字母打头,根本没暴露出来给我们直接访问),只需要访问各个方法就行了。
接下来介绍对 time.Time
执行的常见操作。
t := time.Now()
默认情况下,上面返回的是本地时间。看下默认时区的值:
// Local represents the system‘s local time zone.
// On Unix systems, Local consults the TZ environment
// variable to find the time zone to use. No TZ means
// use the system default /etc/localtime.
// TZ="" means use UTC.
// TZ="foo" means use file foo in the system timezone directory.
var Local *Location = &localLoc
翻译一下,这里的“本地时区”是(优先)从环境变量 TZ
或者 文件/etc/localtime
获取的。
当然,也可以显式设置时区:
loc := time.LoadLocation("America/New_York")
t := time.Now().In(loc)
在Linux服务器环境中的很多场景下,我们会使用一个int64
代表的从 1970-01-01 00:00:00
开始流逝的秒数/毫秒来表示时间戳。
所以,在golang中,我们也经常需要在 time.Time
和 int64
之间互转。
将time.Time
转换成int64
t := time.Now()
tsec := t.Unix() // 转换成秒
tns := t.UnixNano() // 转换成纳秒
以上转换,与时区无关。
将int64
转换成time.Time
tsec := int64(1622203800)
t := time.Unix(tsec, 0)
tms := int64(1622203800000)
t := time.Unix(0, tms * 1000000)
tns := int64(1622203800000000000)
t := time.Unix(0, tns)
time.Unix(sec, nsec int64)
接收两个参数,分别表示秒和纳秒。time.Unix
会对两个值取和,然后转换成相应的time.Time
结构。
转换后的time.Time
的时区是本地时区(参考上面的说明)。
时间有时候是给计算机看的,有时候也是给人看的。给计算机看的是整形值,给人看的,那最好是字符串格式啦。
关于如何表示时间,国际上有很多标准,例如:
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"
)
将time.Time
转换成字符串
我们可以使用Time
结构体的 String()
和 Format()
方法,将其转换成字符串。其中,
String()
固定输出格式为"2006-01-02 15:04:05.999999999 -0700 MST"
;Format()
则可以选择输出格式,例如上面的格式常量中任意一种,也可以自定义格式。示例:
t := time.Now()
fmt.Println("%s", t.Format(time.RFC3339))
上面提到,Format()
也可以自定义格式,比如想生成 Druid 数据库中的时间格式:
t := time.Now()
fmt.Println("%s", t.Format("2006-01-02 15:04:05"))
注意,这里表示格式的时间字符串可不是瞎写的。因为golang要想识别自定义格式的话,它必须要知道哪个值对应的是年月日时分秒中哪个字段。
方法就是,用特定的值表示特定的字段:
const (
_ = iota
stdLongMonth = iota + stdNeedDate // "January"
stdMonth // "Jan"
stdNumMonth // "1"
stdZeroMonth // "01"
stdLongWeekDay // "Monday"
stdWeekDay // "Mon"
stdDay // "2"
stdUnderDay // "_2"
stdZeroDay // "02"
stdUnderYearDay // "__2"
stdZeroYearDay // "002"
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
)
将字符串转换成time.Time
t, err := time.Parse("2006-01-02 15:04:05", "2021-05-28 18:30:00")
time.Parse()
的第一个参数表示时间格式,第二个参数表示时间,最终得到一个time.Time
对象。
时间字符串中可以包含时区信息,若包含则生成对应时区的时间;若没有时区信息,则生成UTC时间。
也可以使用time.ParseInLocation()
将未携带时区信息的字符串,转换成指定时区的时间。
time.Duration
TODO
time.Ticker
TODO
原文:https://www.cnblogs.com/bookxiao/p/14823748.html