Administrator
发布于 2023-06-26 / 25 阅读
0
0

Time

Duration

A period of time is represented with a time.Duration, a type based on an int64.

The smallest amount of time that Go can represent is one nanosecond, but the time package defines constants of type time.Duration to represent a Nanosecond,Microsecond, Millisecond Second, Minute, and Hour.

For example, you represent a duration of two hours and thirty minutes with:

d := 2*time.Hour + 30*time.Minute // d is of type time.Duration
const (
                         
            
	Nanosecond  Duration = 1   
              
	Microsecond          = 100 * Nanosecond  
	Millisecond          = 100 * Microsecond 
	Second              = 1000 * Millisecond 
	Minute               = 60 *   Second
    Hour                  =  60 * Minute

参考文档:https://pkg.go.dev/time

func ParseDuration

func ParseDuration(s string) (Duration, error)

ParseDuration parses a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as “300ms”, “-1.5h” or “2h45m”. Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”.

package main

import (
	"fmt"
	"time"
)

func main() {
	hours, _ := time.ParseDuration("10h")
	complex, _ := time.ParseDuration("1h10m10s")
	micro, _ := time.ParseDuration("1µs")
	// The package also accepts the incorrect but common prefix u for micro.
	micro2, _ := time.ParseDuration("1us")

	fmt.Println(hours)
	fmt.Println(complex)
	fmt.Printf("There are %.0f seconds in %v.\n", complex.Seconds(), complex)
	fmt.Printf("There are %d nanoseconds in %v.\n", micro.Nanoseconds(), micro)
	fmt.Printf("There are %6.2e seconds in %v.\n", micro2.Seconds(), micro)
}

输出

10h0m0s
1h10m10s
There are 4210 seconds in 1h10m10s.
There are 1000 nanoseconds in 1µs.
There are 1.00e-06 seconds in 1µs.

func (Duration) Truncate

func (d Duration) Truncate(m Duration) Duration

Truncate returns the result of rounding d toward zero to a multiple of m. If m <= 0, Truncate returns d unchanged.

Truncate返回的是,将d缩小至m的倍数 。如果m<=0,直接返回

import (
	"fmt"
	"time"
)

func main() {
	d, err := time.ParseDuration("1h15m30.918273645s")
	if err != nil {
		panic(err)
	}

	trunc := []time.Duration{
		time.Nanosecond,
		time.Microsecond,
		time.Millisecond,
		time.Second,
		2 * time.Second,
		time.Minute,
		10 * time.Minute,
		time.Hour,
	}

	for _, t := range trunc {
		fmt.Printf("d.Truncate(%6s) = %s\n", t, d.Truncate(t).String())
	}
}

输出

d.Truncate(   1ns) = 1h15m30.918273645s
d.Truncate(   1µs) = 1h15m30.918273s
d.Truncate(   1ms) = 1h15m30.918s
d.Truncate(    1s) = 1h15m30s
d.Truncate(    2s) = 1h15m30s
d.Truncate(  1m0s) = 1h15m0s
d.Truncate( 10m0s) = 1h10m0s
d.Truncate(1h0m0s) = 1h0m0s

Time

An instant of time is represented with the time.Time type, complete with a time zone.

The time.Parse function converts from a string to a time.Time, while the Format method converts a time.Time to a string.

Golang时间格式化

Golang时间类型通过自带的 Format 方法进行格式化。

需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S,而是使用Go语言的诞生时间 2006-01-02 15:04:05 -0700 MST

为了记忆方便,按照美式时间格式 月日时分秒年 外加时区 排列起来依次是 01/02 03:04:05PM ‘06 -0700,刚开始使用时需要注意。

实际项目中,Format 函数中可以自定义时间格式,也可以使用time包中的预定义格式:

const (
   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"
)

time包中,定义了年、月、日、时、分、秒、周、时区的多种表现形式:

  • 年:  06,2006
  • 月份: 1,01,Jan,January
  • 日:  2,02,_2
  • 时:  3,03,15,PM,pm,AM,am
  • 分:  4,04
  • 秒:  5,05
  • 周几: Mon,Monday
  • 时区: -07,-0700,Z0700,Z07:00,-07:00,MST

根据以上提供的数据,我们可以组合成多种格式化模板,示例代码如下:

func main() {

 currentTime := time.Now()

 fmt.Println("当前时间  : ", currentTime)

 fmt.Println("当前时间字符串: ", currentTime.String())

 fmt.Println("MM-DD-YYYY : ", currentTime.Format("01-02-2006"))

 fmt.Println("YYYY-MM-DD : ", currentTime.Format("2006-01-02"))

 fmt.Println("YYYY.MM.DD : ", currentTime.Format("2006.01.02 15:04:05"))

 fmt.Println("YYYY#MM#DD {Special Character} : ", currentTime.Format("2006#01#02"))

 fmt.Println("YYYY-MM-DD hh:mm:ss : ", currentTime.Format("2006-01-02 15:04:05"))

 fmt.Println("Time with MicroSeconds: ", currentTime.Format("2006-01-02 15:04:05.000000"))

 fmt.Println("Time with NanoSeconds: ", currentTime.Format("2006-01-02 15:04:05.000000000"))

 fmt.Println("ShortNum Month : ", currentTime.Format("2006-1-02"))

 fmt.Println("LongMonth : ", currentTime.Format("2006-January-02"))

 fmt.Println("ShortMonth : ", currentTime.Format("2006-Jan-02"))

 fmt.Println("ShortYear : ", currentTime.Format("06-Jan-02"))

 fmt.Println("LongWeekDay : ", currentTime.Format("2006-01-02 15:04:05 Monday"))

 fmt.Println("ShortWeek Day : ", currentTime.Format("2006-01-02 Mon"))

 fmt.Println("ShortDay : ", currentTime.Format("Mon 2006-01-2"))

 fmt.Println("Short Hour Minute Second: ", currentTime.Format("2006-01-02 3:4:5"))

 fmt.Println("Short Hour Minute Second: ", currentTime.Format("2006-01-02 3:4:5 PM"))

 fmt.Println("Short Hour Minute Second: ", currentTime.Format("2006-01-02 3:4:5 pm"))

}

输出结果:

当前时间  :  2020-06-01 10:10:46.1551731 +0800 CST m=+0.002992001
当前时间字符串:  2020-06-01 10:10:46.1551731 +0800 CST m=+0.002992001
MM-DD-YYYY :  06-01-2020
YYYY-MM-DD :  2020-06-01
YYYY.MM.DD :  2020.06.01 10:10:46
YYYY#MM#DD {Special Character} :  2020#06#01
YYYY-MM-DD hh:mm:ss :  2020-06-01 10:10:46
Time with MicroSeconds:  2020-06-01 10:10:46.155173
Time with NanoSeconds:  2020-06-01 10:10:46.155173100
ShortNum Month :  2020-6-01
LongMonth :  2020-June-01
ShortMonth :  2020-Jun-01
ShortYear :  20-Jun-01
LongWeekDay :  2020-06-01 10:10:46 Monday
ShortWeek Day :  2020-06-01 Mon
ShortDay :  Mon 2020-06-1
Short Hour Minute Second:  2020-06-01 10:10:46
Short Hour Minute Second:  2020-06-01 10:10:46 AM
Short Hour Minute Second:  2020-06-01 10:10:46 am

func (Time) Sub

func (t Time) Sub(u Time) Duration

Sub returns the duration t-u. If the result exceeds the maximum (or minimum) value that can be stored in a Duration, the maximum (or minimum) duration will be returned. To compute t-d for a duration d, use t.Add(-d).

package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
	end := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)

	difference := end.Sub(start)
	fmt.Printf("difference = %v\n", difference)

}

输出

difference = 12h0m0s

Monotonic Time

在一些系统调用中需要指定时间是用CLOCK_MONOTONIC还是CLOCK_REALTIME,以前总是搞不太清楚它们之间的差别,现在终于有所理解了。
CLOCK_MONOTONIC是monotonic time,而CLOCK_REALTIME是wall time。***


​ monotonic time字面意思是单调时间,实际上它指的是系统启动以后流逝的时间,这是由变量jiffies来记录的。系统每次启动时jiffies初始化为0,每来一个timer interrupt,jiffies加1,也就是说它代表系统启动后流逝的tick数。jiffies一定是单调递增的,因为时间不够逆嘛!
wall time字面意思是挂钟时间,实际上就是指的是现实的时间,这是由变量xtime来记录的。系统每次启动时将CMOS上的RTC时间读入xtime,这个值是"自1970-01-01起经历的秒数、本秒中经历的纳秒数",每来一个timer interrupt,也需要去更新xtime。

​ 以前我一直想不明白,既然每个timer interrupt,jiffies和xtime都要更新,那么不都是单调递增的吗?那它们之间使用时有什么区别呢?昨天看到一篇文章,终于明白了,wall time不一定是单调递增的。

因为wall time是指现实中的实际时间,如果系统要与网络中某个节点时间同步、或者由系统管理员觉得这个wall time与现实时间不一致,有可能任意的改变这个wall time。最简单的例子是,我们用户可以去任意修改系统时间,这个被修改的时间应该就是wall time,即xtime,它甚至可以被写入RTC而永久保存。

一些应用软件可能就是用到了这个wall time,比如以前用vmware workstation,一启动提示试用期已过,但是只要把系统时间调整一下提前一年,再启动就不会有提示了,这很可能就是因为它启动时用gettimeofday去读wall time,然后判断是否过期,只要将wall time改一下,就可以欺骗过去了。

To address this potential problem, Go uses monotonic time to track elapsed time whenever a timer is set or a time.Time instance is created with time.Now. This support is invisible; timers use it automatically. The Sub method uses the montonic clock to calculate the time.Duration if both of the time.Time instances have it set.

Ticker

Ticker是周期性定时器,即周期性的触发一个事件,通过Ticker本身提供的管道将事件传递出去。

Ticker的数据结构与Timer完全一样

type Ticker struct {
    C <- chan Time
    r runtimeTimer
}

Ticker对外仅暴露一个channel,指定的时间到来时就往该channel中写入系统时间,也即一个事件。

在创建Ticker时会指定一个时间,作为事件触发的周期。这也是Ticker与Timer的最主要的区别。

另外,ticker的英文原意是钟表的”滴哒”声,钟表周期性的产生”滴哒”声,也即周期性的产生事件

2.1 简单定时任务

有时,我们希望定时执行一个任务,这时就可以使用ticker来完成。
下面代码演示,每隔1s记录一次日志:

package main

import (
	"fmt"
	"time"
)

func main()  {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	for v:= range ticker.C {
		fmt.Println("Ticker tick.",v)
	}


}

输出

Ticker tick. 2022-09-21 08:39:19.434585 +0800 CST m=+1.013378701
Ticker tick. 2022-09-21 08:39:20.4385999 +0800 CST m=+2.017393701
Ticker tick. 2022-09-21 08:39:21.4274743 +0800 CST m=+3.006268201
Ticker tick. 2022-09-21 08:39:22.4322175 +0800 CST m=+4.011011501
Ticker tick. 2022-09-21 08:39:23.4392088 +0800 CST m=+5.018002901
Ticker tick. 2022-09-21 08:39:24.4443991 +0800 CST m=+6.023193401

for range ticker.C会持续从管道中获取事件,收到事件后打印一行日志,如果管道中没有数据会阻塞等待事件,由于ticker会周期性的向管道中写入事件,所以上述程序会周期性的打印日志。

再来看一个例子:

package main

import (
	"fmt"
	"time"
)

func main()  {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	done := make(chan bool)
	go func() {
		time.Sleep(5 * time.Second)
		done <- true
	}()
	for {
		select {
		case <-done:
			fmt.Println("Done!")
			return
		case t := <-ticker.C:
			fmt.Println("Current time: ", t)
		}
	}

}

输出

Current time:  2022-09-21 08:41:46.69917 +0800 CST m=+1.005254301
Current time:  2022-09-21 08:41:47.705322 +0800 CST m=+2.011406301
Current time:  2022-09-21 08:41:48.7095801 +0800 CST m=+3.015664401
Current time:  2022-09-21 08:41:49.7139008 +0800 CST m=+4.019985101
Done!

3.1 创建定时器

使用NewTicker()方法就可以创建一个周期性定时器,函数原型如下

func NewTicker(d Duration) *Ticker

其中参数d即为定时器时间触发的周期

3.2 停止定时器

使用定时器对外暴露的 Stop 方法就可以停掉一个周期性定时器, 函数原型如下

func (t *Ticker)Stop()

需要注意的是, 该方法会停止计时, 意味著不会向定时器的管道中写入事件,但管道并不会被关闭。管道在使用完成后,生命周期结束后会自动释放。

Ticker在使用完后务必要释放,否则会产生资源泄露,进而会持续消耗CPU资源,最后会把CPU耗尽。

func (*Ticker) Reset added in go1.15

func (t *Ticker) Reset(d Duration)

Reset stops a ticker and resets its period to the specified duration. The next tick will arrive after the new period elapses. The duration d must be greater than zero; if not, Reset will panic.

Ticker与之前讲的Timer几乎完全相同,无论数据结构和内部实现机制都相同,唯一不同的是创建方式。

Timer创建时,不指定事件触发周期,事件触发后Timer自动销毁。而Ticker创建时会指定一个事件触发周期,事件会按照这个周期触发,如果不显式停止,定时器永不停止。


评论