日期与时间

Go 语言通过标准库 time 包处理日期和时间相关的问题

简单示例

打印当前时间

  1. now := time.Now()
  2. fmt.Println(now) // 2021-06-17 13:29:40.801445 +0800 CST m=+0.001636524
  3. fmt.Println(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) // 2021 June 17 13 29 40

Time结构体

我们看到Now()返回的是个Time结构体, 这也是Go内部表示时间的数据结构

  1. type Time struct {
  2. // wall and ext encode the wall time seconds, wall time nanoseconds,
  3. // and optional monotonic clock reading in nanoseconds.
  4. //
  5. // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
  6. // a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
  7. // The nanoseconds field is in the range [0, 999999999].
  8. // If the hasMonotonic bit is 0, then the 33-bit field must be zero
  9. // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
  10. // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
  11. // unsigned wall seconds since Jan 1 year 1885, and ext holds a
  12. // signed 64-bit monotonic clock reading, nanoseconds since process start.
  13. wall uint64
  14. ext int64
  15. // loc specifies the Location that should be used to
  16. // determine the minute, hour, month, day, and year
  17. // that correspond to this Time.
  18. // The nil location means UTC.
  19. // All UTC times are represented with loc==nil, never loc==&utcLoc.
  20. loc *Location
  21. }
  • Time 代表一个纳秒精度的时间点
  • 程序中应使用 Time 类型值来保存和传递时间,而不是指针。就是说,表示时间的变量和字段,应为 time.Time 类型,而不是 *time.Time. 类型
  • 时间点可以使用 Before、After 和 Equal 方法进行比较
  • Sub 方法让两个时间点相减,生成一个 Duration 类型值(代表时间段)
  • Add 方法给一个时间点加上一个时间段,生成一个新的 Time 类型时间点
  • Time 零值代表时间点 January 1, year 1, 00:00:00.000000000 UTC。因为本时间点一般不会出现在使用中,IsZero 方法提供了检验时间是否是显式初始化的一个简单途径
  • Time是有时区的
  • 通过 == 比较 Time 时,Location 信息也会参与比较,因此 Time 不应该作为 map 的 key

now() 的具体实现在 runtime 包中, 由汇编实现的, 和平台有关, 一般在sys_{os_platform}_amd64.s 中

时间的格式化

方法:time.Now().Format()

时间的解析

方法:time.Parse(),返回转换后的时间格式和一个判断信息(err)

  1. d1, err := time.Parse("2006-01-02 15:04:05", "2021-06-18 12:12:12")
  2. fmt.Println(d1, err)

为什么是 2006-01-02 15:04:05
这是固定写法,类似于其他语言中 Y-m-d H:i:s 等。为什么采用这种形式, 最直接的说法是很好记:2006 年 1 月 2 日 3 点 4 分 5 秒

时间戳转换

Time -> Timestamp 方法:time.Now().Unix()

  1. ts := time.Now().Unix()
  2. fmt.Println(ts)

Timestamp -> Time 方法:

  1. ts := time.Now().Unix()
  2. fmt.Println(ts)
  3. fmt.Println(time.Unix(ts, 0))

时间的比较

使用 Before、After 和 Equal 方法进行比较

  1. d1, err := time.Parse("2006-01-02 15:04:05", "2021-06-18 12:12:12")
  2. fmt.Println(d1, err)
  3. now := time.Now()
  4. fmt.Println(now.After(d1))
  5. fmt.Println(now.Before(d1))
  6. fmt.Println(now.Equal(d1))

时间长度: Duration

  1. // A Duration represents the elapsed time between two instants
  2. // as an int64 nanosecond count. The representation limits the
  3. // largest representable duration to approximately 290 years.
  4. type Duration int64
  5. const (
  6. minDuration Duration = -1 << 63
  7. maxDuration Duration = 1<<63 - 1
  8. )
  9. // Common durations. There is no definition for units of Day or larger
  10. // to avoid confusion across daylight savings time zone transitions.
  11. //
  12. // To count the number of units in a Duration, divide:
  13. // second := time.Second
  14. // fmt.Print(int64(second/time.Millisecond)) // prints 1000
  15. //
  16. // To convert an integer number of units to a Duration, multiply:
  17. // seconds := 10
  18. // fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
  19. //
  20. const (
  21. Nanosecond Duration = 1
  22. Microsecond = 1000 * Nanosecond
  23. Millisecond = 1000 * Microsecond
  24. Second = 1000 * Millisecond
  25. Minute = 60 * Second
  26. Hour = 60 * Minute
  27. )

time.Duration表示时间长度

  • 以纳秒为基数
  • 底层数据类型为int64

int64 类型的变量不能直接和time.Duration类型相乘,需要显示转换,常量除外

  • 不行:num * time.Second
  • 可以: time.Duration(num) * time.Second
  • 可以: 5 * time.Second

时长计算

1.Add: Add 方法给一个时间点加上一个时间段,生成一个新的 Time 类型时间点

  1. now := time.Now()
  2. after1 := now.Add(time.Hour * 24)
  3. fmt.Println(after1)

2.Sub: 方法让两个时间点相减,生成一个 Duration 类型值(代表时间段)

  1. now := time.Now()
  2. after1 := now.Add(time.Hour * 24)
  3. fmt.Println(now.Sub(after1))

Sleep

定时器

定时器是进程规划自己在未来某一时刻接获通知的一种机制。定时器有2种:

  • 单次触发: Timer
  • 周期性触发: Ticker

Timer

注意:Timer 的实例必须通过 NewTimer 或 AfterFunc 获得, 我们先看2个简单的用法:

1.通过 time.AfterFunc中断循环, 到时触发自定义函数

  1. stop := false
  2. time.AfterFunc(5*time.Second, func() {
  3. stop = true
  4. })
  5. for {
  6. if stop {
  7. fmt.Println("exit")
  8. break
  9. }
  10. time.Sleep(1 * time.Second)
  11. }

2.通过time.After实现同步等待

  1. m := time.NewTimer(5 * time.Second)
  2. fmt.Println(<-m.C)
  3. fmt.Println("exit")

3.timer的stop

如果定时器还未触发,Stop 会将其移除,并返回 true;否则返回 false;后续再对该 Timer 调用 Stop,直接返回 false。

4.timer的Reset

Reset 会先调用 stopTimer 再调用 startTimer,类似于废弃之前的定时器,重新启动一个定时器。返回值和 Stop 一样

5.timer数据结构

  1. // The Timer type represents a single event.
  2. // When the Timer expires, the current time will be sent on C,
  3. // unless the Timer was created by AfterFunc.
  4. // A Timer must be created with NewTimer or AfterFunc.
  5. type Timer struct {
  6. C <-chan Time
  7. r runtimeTimer
  8. }
  • C: 一个存放Time对象的Channel
  • runtimeTimer: 它定义在 sleep.go 文件中,必须和 runtime 包中 time.go 文件中的 timer 必须保持一致

Ticker

  1. tk := time.NewTicker(2 * time.Second)
  2. count := 1
  3. for {
  4. if count > 2 {
  5. tk.Stop()
  6. break
  7. }
  8. fmt.Println(<-tk.C)
  9. count++
  10. }

我们看看Ticker结构体

  1. // A Ticker holds a channel that delivers ``ticks'' of a clock
  2. // at intervals.
  3. type Ticker struct {
  4. C <-chan Time // The channel on which the ticks are delivered.
  5. r runtimeTimer
  6. }