Go 语言标准库之 time 包

Go 语言的 time 包提供了时间的显示和测量用的函数。日历的计算采用的是公历。

time.Location 类型

Go 语言使用time.Location来表示地区相关的时区,一个time.Location可能表示多个时区。

// time.Location 代表一个(关联到某个时间点的)地点,以及该地点所在的时区
type Location struct {
    // 内含隐藏或非导出字段
}

// Local 代表系统本地,对应本地时区
var Local *Location = &localLoc

// UTC 代表通用协调时间,对应零时区。time 包默认使用零时区
var UTC *Location = &utcLoc

// 返回使用给定的名字创建的 Location
// 如果 name 是 "" 或 "UTC",返回 UTC;如果 name 是 "Local",返回 Local;
// 否则 name 应该是 IANA 时区数据库里有记录的地点名,如 "America/New_York"
func LoadLocation(name string) (*Location, error)

// 自定义地点和时区名称 name,在零时区基础上根据时间偏移量 offset(单位秒)创建并返回一个 Location
func FixedZone(name string, offset int) *Location

// 返回对时区信息的描述,返回值绑定为 LoadLocation 或 FixedZone 函数创建 l 时的 name 参数
func (l *Location) String() string

☕️ 示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    // 获取本地当前时间
    now := time.Now()

    // 获取本地时区
    // 方式一
    loc1 := time.Local
    fmt.Println(loc1.String(), now.In(loc1)) // Local 2021-12-04 14:19:42.8397569 +0800 CST
    // 方式二
    loc2, _ := time.LoadLocation("Local")
    fmt.Println(loc2.String(), now.In(loc2)) // Local 2021-12-04 14:19:42.8397569 +0800 CST
    // 方式三
    loc3 := now.Location()
    fmt.Println(loc3.String(), now.In(loc3)) // Local 2021-12-04 14:19:42.8397569 +0800 CST

    // 获取零时区
    // 方式一
    utc1 := time.UTC
    fmt.Println(utc1.String(), now.In(utc1)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC
    // 方式二
    utc2, _ := time.LoadLocation("")
    fmt.Println(utc2.String(), now.In(utc2)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC
    // 方式三
    utc3, _ := time.LoadLocation("UTC")
    fmt.Println(utc3.String(), now.In(utc3)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC

    // 指定地点和时区,东八区
    shanghai, _ := time.LoadLocation("Asia/Shanghai")
    fmt.Println(shanghai.String(), now.In(shanghai)) // Asia/Shanghai 2021-12-04 14:19:42.8397569 +0800 CST

    // 自定义地点和时区名称,东八区
    beijing := time.FixedZone("Beijing", 8*60*60)
    fmt.Println(beijing.String(), now.In(beijing)) // Beijing 2021-12-04 14:19:42.8397569 +0800 Beijing
}

time.Time 类型

time.Time类型表示时间,我们可以通过以下函数获取时间对象,然后获取时间对象的年月日时分秒等信息。

获取 Time 对象

// time.Time 代表一个纳秒精度的时间点
type Time struct {
    // 内含隐藏或非导出字段
}

// 返回本地当前时间
func Now() Time

// 返回一个时区为 loc、当地时间为 year-month-day hour:min:sec + nsec nanoseconds 的时间
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time

// 创建一个本地时间,对应 sec 和 nsec 表示的 Unix 时间(从 January 1, 1970 UTC 至该时间的秒数和纳秒数)
func Unix(sec int64, nsec int64) Time

// 返回采用本地地点和本地时区,但指向同一时间点的 Time
func (t Time) Local() Time

// 返回采用 UTC 和零时区,但指向同一时间点的 Time
func (t Time) UTC() Time

// 返回采用 loc 指定的地点和时区,但指向同一时间点的 Time
func (t Time) In(loc *Location) Time

⭐️ 示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    // Now:获取本地当前时间
    now := time.Now()
    fmt.Println(now) // 2021-12-04 14:43:13.5010095 +0800 CST m=+0.004735101

    // Date:自定义时间
    setTime1 := time.Date(2021, 12, 4, 12, 3, 56, 0, now.Location())
    fmt.Println(setTime1) // 2021-12-04 12:03:56 +0800 CST

    // Unix:自定义时间
    setTime2 := time.Unix(setTime1.Unix(), 1000).In(now.Location())
    fmt.Println(setTime2) // 2021-12-04 12:03:56.000001 +0800 CST
}

获取 Time 基本信息

// Weekday 代表一周的某一天
type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

// String 返回该日(周几)的英文名("Sunday"、"Monday",……)
func (d Weekday) String() string
// Month代表一年的某个月
type Month int

const (
    January Month = 1 + iota
    February
    March
    April
    May
    June
    July
    August
    September
    October
    November
    December
)

// String 返回月份的英文名("January","February",……)
func (m Month) String() string 
// 返回 t 的地点和时区信息
func (t Time) Location() *Location

// 计算 t 所在的时区,返回该时区的规范名(如"CET")和该时区相对于 UTC 的时间偏移量(单位秒)
func (t Time) Zone() (name string, offset int)

// 将 t 表示为 Unix 时间,即从时间点 January 1, 1970 UTC 到时间点 t 所经过的时间(单位秒)
func (t Time) Unix() int64

// 将 t 表示为 Unix 时间,即从时间点 January 1, 1970 UTC 到时间点 t 所经过的时间(单位纳秒)
func (t Time) UnixNano() int64

// 返回时间点 t 对应的年、月、日
func (t Time) Date() (year int, month Month, day int)

// 返回 t 对应的那一天的时、分、秒
func (t Time) Clock() (hour, min, sec int)

// 返回时间点 t 对应的年份
func (t Time) Year() int

// 返回时间点 t 对应那一年的第几月
func (t Time) Month() Month

// 返回时间点 t 对应的 ISO 9601 标准下的年份和星期编号,星期编号范围 [1,53]
// 1 月 1 号到 1 月 3 号可能属于上一年的最后一周,12 月 29 号到 12 月 31 号可能属于下一年的第一周
func (t Time) ISOWeek() (year, week int)

// 返回时间点 t 对应的那一年的第几天,平年的返回值范围 [1,365],闰年 [1,366]
func (t Time) YearDay() int

// 返回时间点 t 对应那一月的第几日
func (t Time) Day() int

// 返回时间点 t 对应的那一周的周几
func (t Time) Weekday() Weekday

// 返回 t 对应的那一天的第几小时,范围 [0, 23]
func (t Time) Hour() int

// 返回 t 对应的那一小时的第几分种,范围 [0, 59]
func (t Time) Minute() int

// 返回 t 对应的那一分钟的第几秒,范围 [0, 59]
func (t Time) Second() int

// 返回 t 对应的那一秒内的纳秒偏移量,范围 [0, 999999999]
func (t Time) Nanosecond() int

✏️ 示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // 地点和时区信息
    fmt.Println(now.Location()) // Local
    // 时区
    fmt.Println(now.Zone()) // CST 28800
    // Unix 时间(单位秒)
    fmt.Println(now.Unix()) // 1638602223
    // Unix 时间(单位纳秒)
    fmt.Println(now.UnixNano())   // 1638602223393227200
    fmt.Println(now.Date())       // 2021 December 4
    fmt.Println(now.Clock())      // 15 17 3
    fmt.Println(now.Year())       // 2021
    fmt.Println(now.Month())      // December
    fmt.Println(now.ISOWeek())    // 2021 48
    fmt.Println(now.YearDay())    // 338
    fmt.Println(now.Day())        // 4
    fmt.Println(now.Weekday())    // Saturday
    fmt.Println(now.Hour())       // 15
    fmt.Println(now.Minute())     // 17
    fmt.Println(now.Second())     // 3
    fmt.Println(now.Nanosecond()) // 393227200
}

Time 比较和运算

// Duration 类型代表两个时间点之间经过的时间,以纳秒为单位。可表示的最长时间段大约 290 年
type Duration int64 

const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)

// ParseDuration 解析一个时间段字符串
// 一个时间段字符串是一个序列,每个片段包含可选的正负号、十进制数、可选的小数部分和单位后缀,如"300ms"、"-1.5h"、"2h45m"。
// 合法的单位有"ns"、"us"、"µs"、"ms"、"s"、"m"、"h"
func ParseDuration(s string) (Duration, error)

// Hours 将时间段表示为 float64 类型的小时数
func (d Duration) Hours() float64

// Hours 将时间段表示为 float64 类型的分钟数
func (d Duration) Minutes() float64

// Hours 将时间段表示为 float64 类型的秒数
func (d Duration) Seconds() float64

// Hours 将时间段表示为 int64 类型的纳秒数,等价于 int64(d)
func (d Duration) Nanoseconds() int64

// 返回时间段采用 "72h3m0.5s" 格式的字符串表示
func (d Duration) String() string

// 表示按照 m 给定的单位,返回四舍五入
func (d Duration) Round(m Duration) Duration

// 表示按照 m 给定的单位,返回舍尾数计算
func (d Duration) Truncate(m Duration) Duration
// 报告 t 是否代表 Time 零值的时间点,January 1, year 1, 00:00:00 UTC
func (t Time) IsZero() bool

// 判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较
// 本方法和用 t == u 不同,这种方法还会比较地点和时区信息
func (t Time) Equal(u Time) bool

// 如果 t 代表的时间点在 u 之前,返回真;否则返回假
func (t Time) Before(u Time) bool

// 如果 t 代表的时间点在 u 之后,返回真;否则返回假
func (t Time) After(u Time) bool

// 返回时间点 t + d
func (t Time) Add(d Duration) Time

// 返回增加了给出的年份、月份和天数的时间点 Time。例如,时间点 January 1, 2011 调用 AddDate(-1, 2, 3) 会返回 March 4, 2010
// AddDate 会将结果规范化,类似 Date 函数的做法。因此,举个例子,给时间点 October 31 添加一个月,会生成时间点 December 1
func (t Time) AddDate(years int, months int, days int) Time

// 返回一个时间段 t - u。如果结果超出了 Duration 可以表示的最大值/最小值,将返回最大值/最小值
// 要获取时间点 t-d(d 为 Duration),可以使用 t.Add(-d)
func (t Time) Sub(u Time) Duration

// 返回从 t 到现在经过的时间,等价于 time.Now().Sub(t)
func Since(t Time) Duration

// 返回从现在到 t 经过的时间,等价于 t.Sub(time.Now())
func Until(t Time) Duration

示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    time1 := time.Date(2021, 12, 4, 12, 3, 56, 0, now.Location())

    fmt.Println(now.IsZero())            // false
    fmt.Println(now.Equal(time1))        // false
    fmt.Println(now.Before(time1))       // false
    fmt.Println(now.After(time1))        // true
    fmt.Println(now.Add(time.Hour))      // 2021-12-04 17:16:41.2619698 +0800 CST m=+3600.006734601
    fmt.Println(now.Add(-time.Hour))     // 2021-12-04 15:16:41.2619698 +0800 CST m=-3599.993265399
    fmt.Println(now.AddDate(1, 1, 1))    // 2023-01-05 16:16:41.2619698 +0800 CST
    fmt.Println(now.AddDate(-1, -1, -1)) // 2020-11-03 16:16:41.2619698 +0800 CST
    fmt.Println(now.Sub(time1))          // 4h12m45.2619698s
    fmt.Println(time.Since(time1))       // 4h12m45.2843379s
    fmt.Println(time.Until(time1))       // -4h12m45.2843379s
}

Time 与 string 转换

// 解析并返回一个格式化的时间字符串所代表的时间,缺少时区信息时,Parse 会将时区设置为 UTC 时间
// layout 定义了参考时间:Mon Jan 2 15:04:05 -0700 MST 2006
func Parse(layout, value string) (Time, error)

// ParseInLocation 类似 Parse 但有两个重要的不同之处
// 第一,当缺少时区信息时,Parse 将时区设置为 UTC 时间,而 ParseInLocation 将时区设置为 loc
// 第二,当时间字符串 value 提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc
func ParseInLocation(layout, value string, loc *Location) (Time, error)

// 将时间字符串转换为 time.Duration 对象
// 字符串可以使用这些单位: "ns", "us" (or "µs"), "ms", "s", "m", "h"
func ParseDuration(s string) (Duration, error)

// 根据 layout 指定的格式返回 t 代表的时间点的格式化文本表示
// layout 定义了参考时间:Mon Jan 2 15:04:05 -0700 MST 2006
func (t Time) Format(layout string) string

// 该方法为基本格式化时间对象为字符串,用该方法可以避免申请临时对象
// 需要由用户控制传入 buf 的长度,如果长度不够,则返回值是扩容后的可用的数据,而 buf 里面数据无效
func (t Time) AppendFormat(b []byte, layout string) []byte

// 返回采用如下格式字符串的格式化时间:"2006-01-02 15:04:05.999999999 -0700 MST"
func (t Time) String() string

✌ 示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    // Go 语言格式化时间模板不是常见的 Y-m-d H:M:S,而是一个固定时间,记忆口诀为 2006 1 2 3 4 5
    const timeFormat = "2006-01-02 15:04:05"
    now := time.Now()

    // 将字符串转换换为时间
    fmt.Println(time.Parse(timeFormat, "2021-12-04 10:53:58"))                           // 2021-12-04 10:53:58 +0000 UTC <nil>
    fmt.Println(time.ParseInLocation(timeFormat, "2021-12-04 10:53:58", now.Location())) // 2021-12-04 10:53:58 +0800 CST <nil>
    fmt.Println(time.ParseDuration("24h5m6s123ms456us321ns"))                            // 24h5m6.123456321s <nil>

    // 将当前时间转换为字符串
    fmt.Println(now.Format(timeFormat)) // 2021-12-04 16:56:38
    
    buf := make([]byte, 0, 64)
    fmt.Printf("%s\n", now.AppendFormat(buf, timeFormat)) // 2021-12-04 16:56:38
}

time.Timer 类型

// time.Timer 类型代表单次时间事件(到达指定时间触发且只触发一次)
// 当 Timer 到期时,当时的时间会被发送给 C,除非 Timer 是被 AfterFunc 函数创建的。
type Timer struct {
    C <-chan Time
    // 内含隐藏或非导出字段
}

// 创建一个 Timer,它会在最少过去时间段 d 后到期,向其自身的 C 字段发送当时的时间
func NewTimer(d Duration) *Timer

// 另起一个 Go 协程等待时间段 d 过去,然后调用 f
// 它返回一个 Timer,可以通过调用其 Stop 方法来取消等待和对 f 的调用。
func AfterFunc(d Duration, f func()) *Timer

// 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期
// 如果调用时 t 还在等待中会返回真;如果 t 已经到期或者被停止了会返回假
func (t *Timer) Reset(d Duration) bool

// 停止 Timer 的执行。如果停止了 t 会返回真;如果 t 已经被停止或者过期了会返回假
// Stop 不会关闭通道 t.C,以避免从该通道的读取不正确的成功
func (t *Timer) Stop() bool

// 会在另一线程经过时间段 d 后向返回值发送当时的时间。等价于 NewTimer(d).C
func After(d Duration) <-chan Time

// Sleep 阻塞当前 Go 协程至少 d 代表的时间段。d <= 0 时,Sleep 会立刻返回
func Sleep(d Duration)

✍ 示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    // 大多数场景用下面这种方式处理超时逻辑
    // FIXME: 特别注意下面方法存在内存泄露,当大量调用chanTimeout
    // FIXME: 会产生大量time.After,此时如果都在超时时间内走handle
    // FIXME: 那么time.After产生的对象都占着内存,直到超过timeout才会GC释放
    chanTimeout := func(c <-chan int, timeout time.Duration) {
        select {
        case tmp, ok := <-c:
            // handle(tmp, ok)
            fmt.Println(tmp, ok)
        case <-time.After(timeout):
            fmt.Println("timeout")
        }
    }
    // FIXME: 使用下面方法更安全,当在超时时间内走到处理流程,手动释放内存
    chanTimeout = func(c <-chan int, timeout time.Duration) {
        t := time.NewTimer(timeout)
        select {
        case tmp, ok := <-c:
            t.Stop() // 当走正常逻辑时手动停掉timer
            // handle(t, ok)
            fmt.Println(tmp, ok)
        case <-t.C:
            fmt.Println("timeout")
        }
    }

    send := make(chan int)
    go chanTimeout(send, time.Second)
    time.Sleep(time.Millisecond * 800)
    select {
    case send <- 100: // 在timeout之前进入处理逻辑
    default:
    }

    go chanTimeout(send, time.Second)
    time.Sleep(time.Second * 2)
    select { // 可以尝试不用select + default,只简单的使用send <- 200会不会报错
    case send <- 200: // 直接进入timeout逻辑
    default:
    }

    fmt.Println(time.Now().String())
    timer := time.AfterFunc(time.Second, func() {
        fmt.Println(time.Now().String())
    })
    time.Sleep(time.Second * 2)
    timer.Reset(time.Second * 5) // 重置一下,5秒后会再打印一条
    time.Sleep(time.Second * 6)
    select {
    case <-timer.C:
    default:
    }
}

time.Ticker 类型

// time.Ticker 保管一个通道,并每隔一段时间向其传递 "tick"(间隔特定时间触发)
type Ticker struct {
    C <-chan Time // 周期性传递时间信息的通道
    // 内含隐藏或非导出字段
}

// 返回一个新的 Ticker,该 Ticker 包含一个通道字段,并会每隔时间段 d 就向该通道发送当时的时间
// 它会调整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。如果 d<=0 会 panic。关闭该 Ticker 可以释放相关资源
func NewTicker(d Duration) *Ticker

// Tick 是 NewTicker 的封装,只提供对 Ticker 的通道的访问。如果不需要关闭 Ticker,本函数就很方便
func Tick(d Duration) <-chan Time

// 关闭一个 Ticker,在关闭后,将不会发送更多的 tick 信息。Stop 不会关闭通道 t.C,以避免从该通道的读取不正确的成功
func (t *Ticker) Stop()

示例代码

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    // 按照如下方式定时获取 time 非常简洁
    for t := range time.Tick(time.Second) {
        i++
        if i > 3 {
            break
        }
        fmt.Println(t.String())
    }

    // 如下测试是常规用法
    t := time.NewTicker(time.Second)
    send := make(chan int)
    go func(c chan<- int) {
        i := 0
        for {
            time.Sleep(time.Millisecond * 600)
            c <- i
            i++
        }
    }(send)
    go func(c <-chan int) {
        for {
            select {
            case tmp, ok := <-t.C:
                fmt.Println(tmp.String(), ok)
            case tmp, ok := <-c:
                fmt.Println(tmp, ok)
            }
        }
    }(send)
    time.Sleep(time.Second * 10)

    t.Reset(time.Second)
    t.Stop()
}

参考

  1. 关于golang的time包总结
  2. Go 语言 time 包常用用法笔记
  3. golang time包的用法详解
原文地址:https://www.cnblogs.com/zongmin/p/15649056.html