# 日期与时间

# Location

时区

常用的几个时区:

  • GMT: 格林尼治标准时间。
  • UTC: 世界协调时间。
  • DST: 夏日节约时间(夏令时)。
  • CST: 表示包含中国北京时间在内的时区,等于 UTC+8。

time 包提供了 Location 的两个实例: LocalUTC
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.Formattime.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
更新于
-->