Day05-GO日期与时间 | 青训营笔记

38 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

GO日期与时间——time包

实际开发中,经常会遇到日期和时间相关的操作,比如格式化日期和时间,解析一个日期时间字符串等。而在Go 语言中通过标准库time 包处理日期和时间相关的问题。

1、Time类型

  • Time 代表一个纳秒精度的时间点。程序中应使用 Time 类型值来保存和传递时间,而不是指针。就是说,表示时间的变量和字段,应为 time.Time 类型,而不是*time.Time类型。一个 Time 类型值可以被多个 go 协程同时使用。
  • 时间点可以使用 Before、After 和 Equal 方法进行比较。
  • Sub 方法让两个时间点相减,生成一个 Duration 类型值(代表时间段)。
  • Add 方法给一个时间点加上一个时间段,生成一个新的 Time 类型时间点。
  • Time 零值代表时间点 January 1, year 1, 00:00:00.000000000 UTC。因为本时间点一般不会出现在使用中,IsZero 方法提供了检验时间是否是显式初始化的一个简单途径。
  • 每一个 Time 都具有一个地点信息(即对应地点的时区信息),当计算时间的表示格式时,如 Format、Hour 和 Year 等方法,都会考虑该信息。
  • Local、UTC 和 In 方法返回一个指定时区(但指向同一时间点)的 Time。修改地点 / 时区信息只是会改变其表示;不会修改被表示的时间点,因此也不会影响其计算。
  • 通过 == 比较 Time 时,Location 信息也会参与比较,因此 Time 不应该作为 mapkey

Time 的内部结构:

type Time struct {
    // sec gives the number of seconds elapsed since
    // January 1, year 1 00:00:00 UTC.
    sec int64

    // nsec specifies a non-negative nanosecond
    // offset within the second named by Seconds.
    // It must be in the range [0, 999999999].
    nsec int32

    // loc specifies the Location that should be used to
    // determine the minute, hour, month, day, and year
    // that correspond to this Time.
    // Only the zero Time has a nil Location.
    // In that case it is interpreted to mean UTC.
    loc *Location
}

常用函数或方法:

1. 零值的判断

Time 的零值是 sec nsec 都是 0,表示 1 年 1 月 1 日。Time.IsZero() 函数用于判断 Time 表示的时间是否是 0 值。

2. 与 Unix 时间戳的转换

  • time.Unix(sec, nsec int64) 通过 Unix 时间戳生成 time.Time 实例;
  • time.Time.Unix() 得到 Unix 时间戳;
  • time.Time.UnixNano() 得到 Unix 时间戳的纳秒表示;

3. 格式化和解析(实际开发中常用到)

  • time.Parse
  • time.ParseInLocation
  • time.Time.Format

4. 实现序列化或反序列化相关接口

Time 实现了:

  • encoding 包中的 BinaryMarshaler 、 BinaryUnmarshaler 、 TextMarshaler 和TextUnmarshaler 接口;
  • encoding/json 包中的 Marshaler 和 Unmarshaler 接口。
  • gob 包中的 GobEncoder 和 GobDecoder 接口。
  • 对于文本序列化 / 反序列化,通过Parse 和 Format实现;
  • 对于二进制序列化,需要单独实现。

6. Round 和 Truncate 方法

获取当前时间整点的 Time 实例。例如,当前时间是 15:54:23,需要的是 15:00:00,time 包也给我们提供了专门的方法,功能强大且性能好,这就是 RoundTrunate ,它们区别,一个是取最接近的,一个是向下取整。 使用示例:

t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2016-06-13 15:34:39", time.Local)
// 整点(向下取整)
fmt.Println(t.Truncate(1 * time.Hour))

// 整点(最接近)
fmt.Println(t.Round(1 * time.Hour))

// 整分(向下取整)
fmt.Println(t.Truncate(1 * time.Minute))

// 整分(最接近)
fmt.Println(t.Round(1 * time.Minute))

t2, _ := time.ParseInLocation("2006-01-02 15:04:05", t.Format("2006-01-02 15:00:00"), time.Local)
fmt.Println(t2)

2、定时器

定时器是进程规划自己在未来某一时刻接获通知的一种机制。两种常用定时器: Timer (到达指定时间触发且只触发一次)和 Ticker (间隔特定时间触发)。

2.1、Timer 相关函数或方法的使用

通过time.After模拟超时:

c := make(chan int)
go func() {
    // time.Sleep(1 * time.Second)
    time.Sleep(3 * time.Second)
    <-c
}()

select {
case c <- 1:
    fmt.Println("channel...")
case <-time.After(2 * time.Second):
    close(c)
    fmt.Println("timeout...")
}

time.Stop 停止定时器 或 time.Reset 重置定时器:

start := time.Now()
timer := time.AfterFunc(2*time.Second, func() {
    fmt.Println("after func callback, elaspe:", time.Now().Sub(start))
})

time.Sleep(1 * time.Second)
// time.Sleep(3*time.Second)
// Reset 在 Timer 还未触发时返回 true;触发了或 Stop 了,返回 false
if timer.Reset(3 * time.Second) {
    fmt.Println("timer has not trigger!")
} else {
    fmt.Println("timer had expired or stop!")
}
time.Sleep(10 * time.Second)
// output:
// timer has not trigger!
// after func callback, elaspe: 4.00026461s
  • 如果定时器还未触发, Stop 会将其移除,并返回 true;否则返回 false;后续再对该 Timer 调用 Stop ,直接返回 false
  • Reset 会先调用 stopTimer 再调用 startTimer ,类似于废弃之前的定时器,重新启动一个定时器。返回值和 Stop 一样。
  • Sleep 的是通过 Timer 实现的,把当前goroutine 作为 arg 参数(getg())。

2.2、Ticker 相关函数或方法的使用:

  • TickerTimer 类似,区别是: Ticker 中的 runtimeTimer 字段的 period 字段会赋值为NewTicker(d Duration) 中的 d ,表示每间隔 d 纳秒,定时器就会触发一次。
  • 除非程序终止前定时器一直需要触发,否则,不需要时应该调用Ticker.Stop来释放相关资源。
  • 如果程序终止前需要定时器一直触发,可以使用更简单方便的 time.Tick 函数,因为 Ticker 实例隐藏起来了,因此,该函数启动的定时器无法停止

3、时区

不同国家(有时甚至是同一个国家内的不同地区)使用不同的时区。对于要输入和输出时间的程序来说,必须对系统所处的时区加以考虑。Go 语言使用 Location 来表示地区相关的时区,一个 Location 可能表示多个时区。

time 包提供了Location的两个实例: Local UTCLocal代表当前系统本地时区; UTC代表通用协调时间,也就是零时区。 time 包默认(为显示提供时区)使用 UTC 时区。

获得特定时区的实例:

函数 LoadLocation 可以根据名称获取特定时区的实例。函数声明如下:

func LoadLocation(name string) (*Location, error)

如果 name 是 “” 或 “UTC”,返回 UTC;如果 name 是 “Local”,返回 Local;否则 name 应该是 IANA 时区数据库里有记录的地点名(该数据库记录了地点和对应的时区),如 “America/New_York”。

4、time总结:

time 包提供了时间的显示和计量用的功能。日历的计算采用的是公历

1. Location:代表一个地区,并表示该地区所在的时区(可能多个)。 Location 通常代表地理位置的偏移,比如CEST 和 CET表示中欧。

  1. Time:代表一个纳秒精度的时间点,是公历时间。

3. Duration:代表两个时间点之间经过的时间,以纳秒为单位。可表示的最长时间段大约 290 年,也就是说如果两个时间点相差超过 290年,会返回 290 年,也就是 minDuration(-1 << 63) 或 maxDuration(1 << 63 - 1)。类型定义: type Duration int64

将 Duration 类型直接输出时,因为实现了 fmt.Stringer 接口,会输出人类友好的可读形式,如:72h3m0.5s

  1. Timer 和 Ticker:定时器相关类型。

5. Weekday 和 Month:这两个类型的原始类型都是 int,定义它们,语义更明确,同时,实现 fmt.Stringer 接口,方便输出