# 日期与时间
# Location
时区
常用的几个时区:
- GMT: 格林尼治标准时间。
- UTC: 世界协调时间。
- DST: 夏日节约时间(夏令时)。
- CST: 表示包含中国北京时间在内的时区,等于 UTC+8。
time
包提供了 Location 的两个实例: Local
和 UTC
。Local
代表当前系统本地时区;UTC
代表通用协调时间,也就是零时区。time
包默认(为显示提供时区)使用 UTC
时区。
// Location 将时间映射到当时正在使用的时区。 | |
// Location 通常用来表示地理位置中的时间偏移的集合。 | |
// 对于许多位置,时间偏移会根据夏令时而变化 | |
type Location struct { | |
name string | |
zone []zone | |
tx []zoneTrans | |
//tzdata 信息后面可以跟一个字符串,描述如何处理 zoneTrans 中未记录的 DST 转换。 格式为 TZ 环境变量,不带冒号; | |
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html. | |
// 例如 America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0 | |
extend string | |
// 大多数查找都是针对当前时间的。 | |
// 为了避免通过 tx 进行二分搜索,请保持静态单元素缓存,该缓存在创建位置时提供正确的区域。 | |
// 如果 cacheStart <= t < cacheEnd,查找返回 cacheZone。 | |
//cacheStart 和 cacheEnd 是时间戳 | |
cacheStart int64 | |
cacheEnd int64 | |
cacheZone *zone | |
} |
func FixedZone(name string, offset int) *Location | |
func LoadLocation(name string) (*Location, error) | |
func LoadLocationFromTZData(name string, data []byte) (*Location, error) |
Unix 系统以标准格式存于文件中,这些文件位于 /usr/share/zoneinfo
本地时区可以通过 /etc/localtime
获取,这是一个符号链接,指向 /usr/share/zoneinfo
中某一个时区。
因此,在初始化 Local 时,通过读取 /etc/localtime
可以获取到系统本地时区。
当然,如果设置了环境变量 TZ
,则会优先使用它。
用法
func main() { | |
time.Local = time.FixedZone("CST", 8*60*60) | |
d := time.Date(2023, 12, 1, 1, 56, 24, 0, time.UTC) | |
fmt.Println(d) // 2023-12-01 01:56:24 +0000 UTC | |
fmt.Println(d.Local()) // 2023-12-01 09:56:24 +0800 CST | |
fmt.Println(d.UTC()) // 2023-12-01 01:56:24 +0000 UTC | |
// 时区转换,区分大小写。 | |
loc, _ := time.LoadLocation("Asia/Shanghai") | |
t := d.In(loc) | |
fmt.Println(t.Location()) // Asia/Shanghai | |
fmt.Println(t.Zone()) // CST 28800 | |
// 比较时间时,会考虑时区。 | |
fmt.Println(d == t) // false | |
fmt.Println(d.Equal(t)) // true | |
t = t.AddDate(0, 0, 1) | |
fmt.Println(d.Before(t)) // true | |
fmt.Println(d.After(t)) // false | |
} |
# Time
纳秒进度的时间。
type Time struct { | |
//wall 和 ext 编码了现实时间 (wall time) 的秒数、纳秒数和可选的单调时钟读数(以纳秒为单位)。 | |
// | |
// 从高到低的位顺序,wall 编码了一个 1 位标志(hasMonotonic)、一个 33 位秒数字段和一个 30 位现实时间纳秒数字段。 | |
// 纳秒数字段的范围是 [0, 999999999]。 | |
// 如果 hasMonotonic 位为 0,则 33 位字段必须为零,并且完整的有符号 64 位墙秒数(自公元 1 年 1 月 1 日起)存储在 ext 中。 | |
// 如果 hasMonotonic 位为 1,则 33 位字段包含自 1885 年 1 月 1 日起的 33 位无符号墙秒数,并且 ext 包含有符号的 64 位单调时钟读数(自进程启动以来的纳秒数)。 | |
wall uint64 | |
ext int64 | |
//loc 指定用于确定与此 Time 对应的分钟、小时、月份、日期和年份的位置(Location)。 | |
//nil 位置表示 UTC。 | |
// 所有 UTC 时间都用 loc==nil 表示,而不是 loc==&utcLoc。 | |
loc *Location | |
} |
生成 Time
// 返回当前时间 | |
func Now() Time | |
// 按照日期构造时间 | |
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time | |
// 按照指定格式解析字符串 | |
func Parse(layout, value string) (Time, error) | |
// 按照指定格式解析字符串,带时区信息 | |
func ParseInLocation(layout, value string, loc *Location) (Time, error) | |
// 自 1970-1-1 00:00:00 UTC 开始到现在的秒数、毫秒数、微秒数。 | |
func Unix(sec int64, nsec int64) Time | |
func UnixMicro(usec int64) Time | |
func UnixMilli(msec int64) Time |
demo:
func main() { | |
tNow := time.Now() | |
tDate := time.Date(tNow.Year(), tNow.Month(), tNow.Day(), 0, 0, 0, 0, time.Local) | |
tParse, _ := time.Parse("2006-01-02 15:04:05", "2023-01-01 00:00:00") | |
fmt.Println("time.Now(): \t", tNow, ",", tNow.UnixNano()) | |
fmt.Println("time.Date(): \t", tDate, ",", tDate.UnixNano()) | |
fmt.Println("time.Parse(): \t", tParse, ",", tParse.UnixNano()) | |
} | |
// time.Now(): 2023-12-25 13:51:09.368530458 +0000 UTC m=+0.000030840 , 1703512269368530458 | |
// time.Date(): 2023-12-25 00:00:00 +0000 UTC , 1703462400000000000 | |
// time.Parse(): 2023-01-01 00:00:00 +0000 UTC , 1672531200000000000 |
方法
// 判空 | |
func (t Time) IsZero() bool | |
// 返回时间 | |
func (t Time) Date() (year int, month Month, day int) | |
func (t Time) Clock() (hour, min, sec int) | |
// 对比时间 | |
func (t Time) After(u Time) bool | |
func (t Time) Before(u Time) bool | |
func (t Time) Equal(u Time) bool | |
func (t Time) Compare(u Time) int | |
// 时间戳,单位 秒、微秒、毫秒、纳秒 | |
func (t Time) Unix() int64 | |
func (t Time) UnixMicro() int64 | |
func (t Time) UnixMilli() int64 | |
func (t Time) UnixNano() int64 | |
// 加减时间 | |
func (t Time) Add(d Duration) Time | |
func (t Time) Sub(u Time) Duration | |
// 格式化输出 | |
func (t Time) Format(layout string) string |
demo:
func main() { | |
var t time.Time | |
fmt.Println(t.IsZero()) // true | |
t = time.Date(2023, 1, 1, 0, 0, 0, 0, time.Local) | |
fmt.Println(t.IsZero()) // false | |
fmt.Println(t.Unix()) // 1672531200 | |
fmt.Println(t.UnixNano()) // 1672531200000000000 | |
} |
// 取整 | |
func (t Time) Truncate(d Duration) Time | |
// 四舍五入 | |
func (t Time) Round(d Duration) Time |
func main() { | |
t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC) | |
DurationList := []time.Duration{ | |
time.Nanosecond, | |
time.Microsecond, | |
time.Millisecond, | |
time.Second, | |
2 * time.Second, | |
time.Minute, | |
10 * time.Minute, | |
time.Hour, | |
} | |
for _, d := range DurationList { | |
fmt.Printf("t.Round(%6s) = %s\n", d, t.Round(d).Format("15:04:05.999999999")) | |
} | |
for _, d := range DurationList { | |
fmt.Printf("t.Truncate(%5s) = %s\n", d, t.Truncate(d).Format("15:04:05.999999999")) | |
} | |
} | |
// t.Round( 1ns) = 12:15:30.918273645 | |
// t.Round( 1µs) = 12:15:30.918274 | |
// t.Round( 1ms) = 12:15:30.918 | |
// t.Round( 1s) = 12:15:31 | |
// t.Round( 2s) = 12:15:30 | |
// t.Round( 1m0s) = 12:16:00 | |
// t.Round( 10m0s) = 12:20:00 | |
// t.Round(1h0m0s) = 12:00:00 | |
// t.Truncate( 1ns) = 12:15:30.918273645 | |
// t.Truncate( 1µs) = 12:15:30.918273 | |
// t.Truncate( 1ms) = 12:15:30.918 | |
// t.Truncate( 1s) = 12:15:30 | |
// t.Truncate( 2s) = 12:15:30 | |
// t.Truncate( 1m0s) = 12:15:00 | |
// t.Truncate(10m0s) = 12:10:00 | |
// t.Truncate(1h0m0s) = 12:00:00 |
# format
格式化输出
用于 Time.Format
和 time.Parse
部分日期使用了将数字月份放在日期之前的美国习惯。
注意,RFC822、RFC850、RFC1123 格式应仅应用于本地时间。
将其应用于 UTC 时间 将使用UTC
作为时区缩写。严格来说,这些 RFC 应该使用使用GMT
。
通常情况下,应该使用 RFC1123Z 而不是 RFC1123,对于坚持使用该格式的服务器,应优先选择 RFC3339 作为新协议的格式。
RFC3339、RFC822、RFC822Z、RFC1123 和 RFC1123Z 适用于格式化;
但在与 time.Parse 一起使用时,它们不接受 RFCs 允许的所有时间格式,但它们接受形式上未定义的时间格式。
RFC3339Nano 格式从秒字段中删除尾随零,因此格式化后可能无法正确排序。
大多数程序可以使用定义的常量之一作为传递给 Format 或 Parse 的布局。
下面是布局字符串的组成部分摘要。每个元素都通过示例显示了参考时间的格式。
只有这些值会被识别。在布局字符串中,未被识别为参考时间的文本会原样显示在 Format 中,
并且在 Parse 的输入中也会原样出现。
// Year: "2006" "06" | |
// Month: "Jan" "January" "01" "1" | |
// Day of the week: "Mon" "Monday" | |
// Day of the month: "2" "_2" "02" | |
// Day of the year: "__2" "002" | |
// Hour: "15" "3" "03" (PM or AM) | |
// Minute: "4" "04" | |
// Second: "5" "05" | |
// AM/PM mark: "PM" |
数字时区偏移的格式如下:
// "-0700" ±hhmm | |
// "-07:00" ±hh:mm | |
// "-07" ±hh | |
// "-070000" ±hhmmss | |
// "-07:00:00" ±hh:mm:ss |
在格式中用 Z 替换符号会触发打印 Z 而不是 UTC 区域的偏移的 ISO 8601 行为。
因此:
// "Z0700" Z or ±hhmm | |
// "Z07:00" Z or ±hh:mm | |
// "Z07" Z or ±hh | |
// "Z070000" Z or ±hhmmss | |
// "Z07:00:00" Z or ±hh:mm:ss |
在格式字符串中,"_2" 和 "__2" 中的下划线表示空格,如果以下数字有多个数字,则可以用数字替换空格,以与固定宽度的 Unix 时间格式兼容。前导零表示零填充值。
格式 "__2" 和 "002" 是空格填充和零填充的三个字符的年份中的日期;没有未填充的年份中的日期格式。
逗号或小数点后跟一个或多个零表示小数秒,根据给定的小数位数打印。逗号或小数点后跟一个或多个 9 表示小数秒,
根据给定的小数位数打印,尾随的零将被删除。
例如,"15:04:05,000" 或 "15:04:05.000" 以毫秒精度格式化或解析。
一些有效的布局对于 time.Parse 来说是无效的时间值,
原因是格式中的_用于空格填充,Z 用于区域信息。
重要的是,只能用 01 表示月份,02 表示日期等等,具体 std 如下。
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 | |
stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators | |
stdMask = 1<<stdArgShift - 1 // mask out argument | |
) |
time.format 也提供了一些常用的格式:
const ( | |
Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order. | |
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" | |
DateTime = "2006-01-02 15:04:05" | |
DateOnly = "2006-01-02" | |
TimeOnly = "15:04:05" | |
) |
demo:
func main() { | |
str := "2023-01-01 00:00:00" | |
strTime, err := time.Parse(time.DateTime, str) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(strTime) // 2023-01-01 00:00:00 +0000 UTC | |
} |
# Duration
时间间隔
Duration 将时间间隔表示为 int64 纳秒计数。 该表示法将最大可表示持续时间限制为大约 290 年。
type Duration int64 |
// 当前到 t 时过了多久 等价于 time.Now ().Sub (t). | |
func Since(t Time) Duration | |
// 当前到 t 时还有多久 等价于 t.Sub (time.Now ()). | |
func Until(t Time) Duration | |
// 多久 | |
func (d Duration) Hours() float64 | |
func (d Duration) Minutes() float64 | |
func (d Duration) Seconds() float64 | |
func (d Duration) Milliseconds() int64 | |
func (d Duration) Microseconds() int64 | |
func (d Duration) Nanoseconds() int64 | |
func (d Duration) Truncate(m Duration) Duration | |
func (d Duration) Round(m Duration) Duration |
demo:
func main() { | |
t := time.Now() | |
time.Sleep(1 * time.Second) | |
d := time.Since(t) | |
fmt.Println(d.Minutes()) // 0.016668886883333334 | |
} |
# Timer,Ticker
定时器,定时器是进程规划自己在未来某一时刻接获通知的一种机制。
- Timer: 到达指定时间触发且只触发一次。
- Ticker: 间隔特定时间触发。
type Timer struct { | |
// 时间到了会把当前时间传到 C 中 | |
C <-chan Time | |
r runtimeTimer | |
} | |
type Ticker struct { | |
// 时间到了会把当前时间传到 C 中 | |
C <-chan Time | |
r runtimeTimer | |
} |
//runtimeTimer 结构与 runtime.timer 保持一致,所以下面介绍 runtime.timer | |
type timer struct { | |
pp puintptr | |
//timer 在 when 被唤醒,接着在 when+period 唤醒 (仅当 period>0) | |
// 每次 timer 在 goroutine 中调用 f (arg,now) 的时候,f 必须是一个函数而不是 block | |
when int64 | |
period int64 | |
f func(any, uintptr) | |
arg any | |
seq uintptr | |
// 在 timerModifiedXX 状态下,将 when 字段设置为什么。 | |
nextwhen int64 | |
// The status field holds one of the values below. | |
status atomic.Uint32 | |
} |
新建 Timer
// 返回 Timer | |
func NewTimer(d Duration) *Timer | |
// 自带 goroutine | |
func AfterFunc(d Duration, f func()) *Timer | |
// 返回 Timer 的通道 | |
func After(d Duration) <-chan Time | |
// 返回 Ticker | |
func NewTicker(d Duration) *Ticker |
func (t *Timer) Reset(d Duration) bool | |
func (t *Timer) Stop() bool | |
func (t *Ticker) Reset(d Duration) | |
func (t *Ticker) Stop() |
demo:
func main() { | |
fmt.Println(time.Now()) | |
t := time.NewTimer(2 * time.Second) | |
fmt.Println("time.NewTimer:\t", <-t.C) | |
c := time.After(time.Second) | |
fmt.Println("time.After:\t", <-c) | |
var wg sync.WaitGroup | |
wg.Add(1) | |
time.AfterFunc(time.Second*1, func() { // goroutine | |
defer wg.Done() | |
fmt.Println("time.AfterFunc:\t", time.Now()) | |
}) | |
wg.Wait() | |
} | |
// 2023-12-26 06:24:05.735232696 +0000 UTC m=+0.000019777 | |
// time.NewTimer: 2023-12-26 06:24:07.736438674 +0000 UTC m=+2.001225828 | |
// time.After: 2023-12-26 06:24:08.736722245 +0000 UTC m=+3.001509399 | |
// time.AfterFunc: 2023-12-26 06:24:09.736995409 +0000 UTC m=+4.001782545 |
func main() { | |
t := time.NewTicker(time.Second * 1) | |
defer t.Stop() | |
for i := 0; i < 10; i++ { | |
fmt.Println(<-t.C) | |
} | |
} | |
// 2023-12-26 06:30:20.909428813 +0000 UTC m=+1.000071030 | |
// 2023-12-26 06:30:21.90954379 +0000 UTC m=+2.000186072 | |
// 2023-12-26 06:30:22.909554203 +0000 UTC m=+3.000196480 | |
// 2023-12-26 06:30:23.909497156 +0000 UTC m=+4.000139428 | |
// 2023-12-26 06:30:24.909547462 +0000 UTC m=+5.000189724 | |
// 2023-12-26 06:30:25.909610114 +0000 UTC m=+6.000252360 | |
// 2023-12-26 06:30:26.909803791 +0000 UTC m=+7.000446205 | |
// 2023-12-26 06:30:27.909542563 +0000 UTC m=+8.000184828 | |
// 2023-12-26 06:30:28.909693368 +0000 UTC m=+9.000335659 | |
// 2023-12-26 06:30:29.90956762 +0000 UTC m=+10.000210005 |