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
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创建时会指定一个事件触发周期,事件会按照这个周期触发,如果不显式停止,定时器永不停止。