Go 语言的 time 包提供了时间的显示和测量用的函数。日历的计算采用的是公历。
Location 类型
在 Go 语言中,表示时区的类型是type Location struct{...}
,代码中使用*time.Location
对象。
// Location 代表一个(关联到某个时间点的)地点,以及该地点所在的时区
type Location struct {
// 内含隐藏或非导出字段
}
// Local 代表系统本地,对应本地时区
var Local *Location = &localLoc
// UTC 代表通用协调时间,对应零时区
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
// 获取零时区
// 方式一
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 代表一个纳秒精度的时间点
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 将返回值的 Location 设置为loc;
// 第二,当时间字符串提供了时区偏移量信息时,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
}
Timer 类型
// 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
// 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:
}
}
Ticker 类型
// Ticker 保管一个通道,并每隔一段时间向其传递 "tick"
type Ticker struct {
C <-chan Time // 周期性传递时间信息的通道
// 内含隐藏或非导出字段
}
// 返回一个新的 Ticker,该 Ticker 包含一个通道字段,并会每隔时间段 d 就向该通道发送当时的时间
// 它会调整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。如果 d<=0 会 panic。关闭该 Ticker 可以释放相关资源
func NewTicker(d Duration) *Ticker
// 关闭一个 Ticker。在关闭后,将不会发送更多的 tick 信息
// Stop 不会关闭通道 t.C,以避免从该通道的读取不正确的成功
func (t *Ticker) Stop()
// 会在另一线程经过时间段 d 后向返回值发送当时的时间。等价于 NewTimer(d).C
func After(d Duration) <-chan Time
// Tick 是 NewTicker 的封装,只提供对 Ticker 的通道的访问。如果不需要关闭 Ticker,本函数就很方便。
func Tick(d Duration) <-chan Time
示例代码如下:
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()
}
请发表评论