首页 > 其他 > 详细

golang中如何访问和操作时间对象

时间:2021-05-28 22:47:24      阅读:32      评论:0      收藏:0      [点我收藏+]

golang中的package time提供了用来表示时间的相关数据结构,包括:

  • time.Location 表示一个时区信息;
  • time.Time 表示一个带时区的时间信息;
  • time.Duration 表示一个可转换成任意单位的时间长度信息;
  • time.Ticker 表示一个周期定时器,用来执行周期性任务。

本文就主要总结这几种数据结构的常见用法。

1 基本概念

日常生活中,类似下面这样的对话,每时每刻都在发生:

- “现在是几点了?”
- “嗯,6点半了”
- “哇,下班了,走!”

时间

“下午6点半”,这就是一个 时间点 了。说的更具体点(精确到秒),“2021年05月28日 18点30分”。

时区

为什么上面的要加个“中国”前缀呢?为什么全球不能统一时间呢?

这是因为人们都是通过观察太阳的位置来决定时间的:早上七点跟太阳一起起床;中午12点;晚上7点跟太阳一起下班。但是,由于地球的自转,导致不同经度的人,在同一时刻看到的太阳的位置是不一样的。比如说,在中国处于正午12点的时候,美国东部是凌晨0点呢。

因此,人们以经度为准则,以被15整除的经线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时。因此推算,东12区与西12区为同一地方,为避免同一地方日期却相差一天的矛盾,提出国际换日线的概念,国际换日线东侧比西侧迟整整24小时。

但是,为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区。具体可以参见时区列表

时区有好几种表示方式:

  • 东8区/西8区/零区,通常只用于中文环境;
  • UTC+8/UTC-8/UTC-0;
  • 英文缩写,例如 GMT, CST等等

标准时间

时区,只是给不同地区的人们划定了时间差。例如,位于东八区的中国,比东九区的日本时间相差1个小时。

但是,它无法解决时间基准问题。即,位于零时区的时间到底是多少?这应该是全球所有地区都认同的一个标准时间。

这就不得不提到两个非常常见的缩写了:GMTUTC。关于 GMT 和 UTC 的具体信息和历史沿革,大家可以参考各自的维基百科。这里主要注意现在它两的区别:

  • GMT(格林尼治标准时间)是一个时区,表示的是当地时间,值等于 UTC+0;
  • UTC(协调世界时)是时间标准,不与任何地区位置相关,也是现在使用的世界标准时间。

总结来说,UTC是时间标准,各地区的时间根据当地的时区,用 UTC+N 来表示当地时间。

2 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个成员变量:wallext表示时间戳;loc表示时区。不过在介绍wallext的具体意义之前,我们先来看一组概念:墙上时间 和 单调时间。

墙上时间(Wall Clock)

顾名思义,挂在墙上的时钟,用来告诉人们现在的时间。虽然从理论上全球所有计算机的墙上时间应该是完全一致的,但是现实中你无法保证这种完全一致性,也因此计算机的墙上时间必须能够被更改,以便与其它计算机保持同步。

单调时间(Monotonic Clock)

既然墙上时间是可能变的,那么在需要度量某个时间段的长度时,就不能用墙上时间来计算了。因此,单调时间就出现了:它表示的是计算机自启动之后单调递增的时间。

回顾下Linux系统上的clock_gettime函数,它就是通过参数指定是返回墙上时间还是单调时间的struct timespec结构体。

golang中的time.Time就非常贴心啦!它将墙上时钟和单调时钟封装在一起,并且:在需要进行读操作时,就用墙上时钟;在需要进行计算时间差值时,就用单调时钟。

这里只是告诉大家有这么个知识点,至于具体到某个函数,用的是墙上时钟,还是单调时钟,还是要以代码和代码中的注释为准!

再回过头来看结构体中的wallext 变量的意义。

  • wall的64位从高到低依次表示:1位的标记位(hasMonotonic);33位的秒字段;30位的纳秒字段。
  • ext的内容则依赖hasMonotonic标记位的值:
    • 如果标记位为0,那么wall的秒字段值为0,且ext存储的是从 "0001年01月01日"到该时间点经过的墙上秒数;
    • 如果标记位位1,那么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.Timeint64之间互转。

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()将未携带时区信息的字符串,转换成指定时区的时间。

3 time.Duration

TODO

4 time.Ticker

TODO

golang中如何访问和操作时间对象

原文:https://www.cnblogs.com/bookxiao/p/14823748.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!