文章目录
一、time 包简介
时间和日期是我们开发中经常会用到的,Go语言中的 time 包提供了 时间显示和测量 等所用的函数,本文介绍一下 time 包的基本用法。
二、原理
时间一般包含 时间值 和 时区 ,可以从Go语言中 time 包的 源码 中看出:
type Time struct {
// wall and ext encode the wall time seconds, wall time nanoseconds,
// and optional monotonic clock reading in nanoseconds.
//
// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
// The nanoseconds field is in the range [0, 999999999].
// If the hasMonotonic bit is 0, then the 33-bit field must be zero
// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
// unsigned wall seconds since Jan 1 year 1885, and ext holds a
// signed 64-bit monotonic clock reading, nanoseconds since process start.
wall uint64
ext int64
// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location
}
- wall:表示距离公元 1 年 1 月 1 日 00:00:00UTC 的秒数;
- ext:表示纳秒;
- loc:代表时区,主要处理偏移量,不同的时区,对应的时间不一样。
如何正确表示时间呢?
公认最准确的计算应该是使用 “原子震荡周期” 所计算的物理时钟了(Atomic Clock, 也被称为原子钟),这也被定义为标准时间(International Atomic Time)。
而我们常常看见的 UTC(Universal Time Coordinated,世界协调时间)就是利用这种 Atomic Clock 为基准所定义出来的正确时间。UTC 标准时间是以 GMT(Greenwich Mean Time,格林尼治时间)这个时区为主,所以本地时间与 UTC 时间的时差就是本地时间与 GMT 时间的时差。
UTC + 时区差 = 本地时间
国内一般使用的是北京时间,与 UTC 的时间关系如下:
UTC + 8 个小时 = 北京时间
在Go语言的 time 包里面有两个时区变量,如下:
time.UTC:UTC 时间
time.Local:本地时间
同时,Go语言还提供了 LoadLocation 方法和 FixedZone 方法来获取时区变量,如下:
FixedZone(name string, offset int) *Location
其中,name 为时区名称,offset 是与 UTC 之前的时差。
LoadLocation(name string) (*Location, error)
其中,name 为时区的名字。
三、时间的获取
1. 获取当前时间 – time.Now()
我们可以通过 time.Now()
函数来获取 当前的时间对象 ,然后通过事件对象来获取当前的时间信息。示例代码如下:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
fmt.Printf("current time:%v\n", now)
year := now.Year() //年
month := now.Month() //月
day := now.Day() //日
hour := now.Hour() //小时
minute := now.Minute() //分钟
second := now.Second() //秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
输出结果:
current time:2022-02-22 11:03:59.8710199 +0800 CST m=+0.004087501
2022-02-22 11:03:59
在时间对象上,可以通过调用 Year() 、Month()、Day()、Hour()、Minute()、Second()
等方法来获取具体的年、月、日、小时、分钟、秒等。
2. 获取时间戳 – Unix()、UnixNano()
时间戳是自 1970 年 1 月 1 日(08:00:00GMT)至当前时间的总毫秒数,它也被称为 Unix 时间戳(UnixTimestamp)。
基于时间对象获取时间戳的示例代码如下:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
timestamp1 := now.Unix() //时间戳
timestamp2 := now.UnixNano() //纳秒时间戳
fmt.Printf("现在的时间戳:%v\n", timestamp1)
fmt.Printf("现在的纳秒时间戳:%v\n", timestamp2)
}
输出结果:
现在的时间戳:1645499594
现在的纳秒时间戳:1645499594139315000
注意:
首先要通过 time.Now()
获取当前的时间对象,然后在时间对象上调用时间戳方法 Unix()
或 UnixNano()
来获取时间戳。
使用 time.Unix() 函数 可以 将时间戳转为时间格式(时间对象),示例代码如下:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //获取当前时间
timestamp := now.Unix() //时间戳
timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
fmt.Println(timeObj)
year := timeObj.Year() //年
month := timeObj.Month() //月
day := timeObj.Day() //日
hour := timeObj.Hour() //小时
minute := timeObj.Minute() //分钟
second := timeObj.Second() //秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
输出结果:
2022-02-22 11:17:09 +0800 CST
2022-02-22 11:17:09
通过 time.Unix() 函数将将时间戳转为时间对象后,就又可以在时间对象上调用其他方法啦。
3. 获取当前是星期几 – Weekday()
time 包中的 Weekday 方法 能够返回某个时间点所对应是一周中的周几,示例代码如下:
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now() //获取时间对象
fmt.Println(t.Weekday().String()) //在时间对象上调用Weekday()方法
}
输出结果:
Tuesday
四、时间操作函数
1. Add
我们在日常的开发过程中可能会遇到要求某个时间 + 时间间隔之类的需求,Go语言中的 Add 方法如下:
func (t Time) Add(d Duration) Time //在时间对象Time上定义的方法Add,返回一个时间对象
Add 函数可以返回 时间点 t + 时间间隔 d 的值。
要获取时间点 t – d(d 为 Duration),可以使用 t.Add(-d)
。
实例:求一个小时之后的时间
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
later := now.Add(time.Hour) // 当前时间加1小时后的时间
fmt.Println(later)
}
输出结果:
2022-02-22 11:28:38.3280421 +0800 CST m=+0.004958801
2022-02-22 12:28:38.3280421 +0800 CST m=+3600.004958801
2. Sub
求两个时间之间的差值:
func (t Time) Sub(u Time) Duration //在时间对象Time上定义的方法Sub,传入一个时间对象,返回两个时间对象间的时间间隔Duration
返回一个时间段 t - u
的值。如果结果超出了 Duration 可以表示的最大值或最小值,将返回最大值或最小值。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
later := now.Add(time.Hour) // 当前时间加1小时后的时间
fmt.Println(later)
d := later.Sub(now)
fmt.Println(d)
}
输出结果:
2022-02-22 11:34:28.383694 +0800 CST m=+0.004515101
2022-02-22 12:34:28.383694 +0800 CST m=+3600.004515101
1h0m0s
3. Equal
判断两个时间是否相同:
func (t Time) Equal(u Time) bool
Equal 函数会考虑时区的影响,因此不同时区标准的时间也可以正确比较,Equal 方法和用 t==u 不同,Equal 方法还会比较地点和时区信息。
4. Before
判断一个时间点是否在另一个时间点之前:
func (t Time) Before(u Time) bool
如果 t 代表的时间点在 u 之前,则返回真,否则返回假。
5. After
判断一个时间点是否在另一个时间点之后:
func (t Time) After(u Time) bool
如果 t 代表的时间点在 u 之后,则返回真,否则返回假。
五、定时器
1. NewTimer()
官方文档 :
NewTimer 实例化 Timer 结构体,在持续时间 d 之后发送当前时间至通道内。
time 包里的 NewTimer()
函数用于创建一个新的一次性计时器,该计时器将至少在持续时间 “d” 之后在其通道上传输实际时间。用法:
func NewTimer(d Duration) *Timer
这里,* Timer是指向计时器的指针。
返回值:它返回一个通道,通知计时器必须等待多长时间。
实例:
package main
import (
"fmt"
"time"
)
func main() {
//timer定时器,是到固定时间后,执行一次,然后结束
newtimer := time.NewTimer(5 * time.Second)
// Notifying the channel
<-newtimer.C //读取 Timer.C 得到 定时后的系统时间。并且完成一次 chan 的 读操作。如果感兴趣的话,可以打印一下这个值看看
// Printed after 5 seconds
fmt.Println("Timer is inactivated")
}
输出结果:
Timer is inactivated
打印字符串很简单,如果没有计时器,那么该程序一运行就应该立即打印出 Timer is inactivated;
然而,我们注意到,程序运行之后,至少经过五秒,才打印出 Timer is inactivated;
这是因为我们设定了一个五秒的计时器。程序在此阻塞了五秒之后才正常运行。
在运行代码5秒钟后,打印了上述输出,因为在该指定时间之后,该通道将被通知计时器已被禁用。
实际上,Timer 使用完后还可以再次启用它,方法是调用它的 Reset 方法。
2. NewTicker()
与 NewTimer() 创建的一次性计时器不同,NewTicker() 创建的计时器是周期性的,它每隔指定时间都会触发一次。
package main
import (
"fmt"
"time"
)
func main() {
//ticker只要定义完成,从此刻开始计时,不需要任何其他的操作,每隔固定时间都会触发(周期性计时器)。
t := time.NewTicker(time.Second * 2)
defer t.Stop()
tag := 0
for {
tag++
if tag == 10 {
break
}
<-t.C //每隔两秒都会接受一次NewTicker发来的时间
fmt.Println("Ticker running...")
}
}
输出结果:
Ticker running...
Ticker running...
Ticker running...
Ticker running...
Ticker running...
Ticker running...
Ticker running...
Ticker running...
Ticker running...
每隔两秒,都会弹出一个 Ticker running… ,我设置了tag,使程序执行九次之后结束。
源码:
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
}
3. time.Tick()
time.Tick 是对 time.NewTicker 的封装。最好不要使用该方法,除非你准备将 chan 作为返回结果并在程序的整个生命周期中继续使用它。 正如官方描述:
垃圾收集器无法恢复底层的 Ticker,出现 ” 泄漏 “. 请谨慎使用,如有疑问请改用 Ticker。
使用 time.Tick(时间间隔)
可以设置定时器,定时器的本质上是一个通道(channel),到时间就会将当前时间放入通道。time.Tick() 的功能和 NewTicker() 几乎一样:
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器,Tick是周期定时器
tag := 0
for i := range ticker { //遍历定时器 ticker
tag++
fmt.Println(i) //每秒都会执行的任务
if tag == 10 {
break
}
}
}
输出结果:
2022-02-22 11:40:04.0331504 +0800 CST m=+1.005876601
2022-02-22 11:40:05.0394583 +0800 CST m=+2.012184501
2022-02-22 11:40:06.0464968 +0800 CST m=+3.019223001
2022-02-22 11:40:07.0375901 +0800 CST m=+4.010316301
2022-02-22 11:40:08.0451185 +0800 CST m=+5.017844701
2022-02-22 11:40:09.0343462 +0800 CST m=+6.007072401
2022-02-22 11:40:10.0455835 +0800 CST m=+7.018309701
2022-02-22 11:40:11.0379969 +0800 CST m=+8.010723101
2022-02-22 11:40:12.0464238 +0800 CST m=+9.019150001
2022-02-22 11:40:13.0389829 +0800 CST m=+10.011709101
可见,定时器 ticker 相当于一个通道(channel),它每隔一秒,就产生一个时间对象。(这个通道每隔一秒就放进一个当时时间的时间对象)
4. Reset()
func (t *Timer) Reset(d Duration) bool
Reset 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期。如果调用时 t 还在等待中会返回 true;如果 t 已经到期或者被停止了会返回 false。
例如,你已经开启了一个计时器在运行,然后你在它上调用 Reset 方法,那么从你调用 Reset 这一刻开始,计时器就重新开始计时,并且在 Reset 指定的时间段 d 之后到期。
当你 Reset 一个计时器时,计时器可能有两种情况:一是计时器还在运行,二是计时器已经到期。那么在第一种情况下,Reset 令计时器重新开始计时并返回 true;如果计时器已经到期,Reset 令计时器重新开始计时并返回 false。
所以,Reset的返回值不代表重启定时器成功或失败,而是在表达定时器在重设前的状态:
- 当Timer已经停止或者超时,返回false。
- 当定时器未超时时,返回true。
比如你的定时器设置的是3秒,中间sleep 1秒 < 3,这时候如果 reset 的话返回的就是 true(因为定时器还在等待),如果你sleep 4秒 > 3,那么返回的就是 false。
实例:
reset 返回 true:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
fmt.Println("startTime:------", start)
timer := time.AfterFunc(2*time.Second, func() {
fmt.Println("after func callback, elaspe:", time.Now())
})
time.Sleep(1 * time.Second)
// time.Sleep(3*time.Second)
// Reset 在 Timer 还未触发时返回 true;触发了或 Stop 了,返回 false
if timer.Reset(4 * time.Second) {
fmt.Println("timer has not trigger!-----", time.Now())
} else {
fmt.Println("timer had expired or stop!----", time.Now())
}
// 保证上面的计时器时间到了能继续执行,不然会直接跳出这个函数,无法执行timer.after
time.Sleep(10 * time.Second)
fmt.Printf("end:-------", time.Now())
}
输出结果:
startTime:------ 2022-02-22 16:17:57.3695453 +0800 CST m=+0.004637201
timer has not trigger!----- 2022-02-22 16:17:58.3951618 +0800 CST m=+1.030253701
after func callback, elaspe: 2022-02-22 16:18:02.4001479 +0800 CST m=+5.035239801
end:-------%!(EXTRA time.Time=2022-02-22 16:18:08.4030159 +0800 CST m=+11.038107801)
reset 返回 false:
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
fmt.Println("startTime:------", start)
timer := time.AfterFunc(2*time.Second, func() {
//fmt.Println("after func callback, elaspe:", time.Now().Sub(start))
fmt.Println("after func callback, elaspe:", time.Now())
})
time.Sleep(3 * time.Second)
if timer.Reset(4 * time.Second) {
fmt.Println("timer has not trigger!-----", time.Now())
} else {
fmt.Println("timer had expired or stop!----", time.Now())
}
// 保证上面的计时器时间到了能继续执行,不然会直接跳出这个函数,无法执行timer.after
time.Sleep(10 * time.Second)
fmt.Printf("end:-------", time.Now())
}
输出结果:
startTime:------ 2022-02-22 16:15:02.1184045 +0800 CST m=+0.005092001
after func callback, elaspe: 2022-02-22 16:15:04.149014 +0800 CST m=+2.035701501
timer had expired or stop!---- 2022-02-22 16:15:05.1533177 +0800 CST m=+3.040005201
after func callback, elaspe: 2022-02-22 16:15:09.1587759 +0800 CST m=+7.045463401
end:-------%!(EXTRA time.Time=2022-02-22 16:15:15.1550596 +0800 CST m=+13.041747101)
Reset() 妙用:
前面介绍过,Timer 是一次性计时器,Ticker 是周期性计时器。
其实,我们可以用 Timer + Reset 实现周期性计时器。
5. 定时器总结
1. 一次性定时:
Timer:创建定时器,指定定时时长。定时到达后,系统会自动向定时器的成员 C 写入系统当前时间。 (对 chan 的写操作)
type Timer struct {
C <-chan Time
r runtimeTimer
}
常用操作:sleep()、NewTimer、After
读取 Timer.C 得到定时后的系统时间。并且完成一次 chan 的读操作。
time.After() 定时:
指定定时时长,定时到达后。 系统会自动向定时器的成员写入系统当前时间。
返回可读 chan 。 读取,可获得系统写入时间。
总结: Sleep、NewTimer、After —— time包
定时器的 停止、重置:
1) 创建定时器 myTimer := time.NewTimer(2 * time.Second)
2) 停止: myTimer.Stop —— 将定时器归零。 <-myTimer.C 会阻塞
3) 重置:myTimer.Reset(time.Second)
2. 周期定时:
type Ticker struct {
C <-chan Time
r runtimeTimer
}
1) 创建周期定时器 myTicker := time.NewTicker(time.Second)
定时时长到达后,系统会自动向 Ticker 的 C 中写入系统当前时间。
并且,每隔一个定时时长后,循环写入系统当前时间。
2) 在子 go 程中循环读取 C。获取系统写入的时间。
六、时间格式化 – Format()
时间类型有一个自带的 Format 方法 进行格式化。
提示:如果想将时间格式化为 12 小时格式,需指定 PM。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
//以下的代码实现将时间对象 now 按照指定的格式输出
// 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
fmt.Println(now.Format("2006年1月2号15点04分 Mon Jan"))
// 24小时制
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
}
输出结果:
2022-02-22 14:36:52.5137528 +0800 CST m=+0.004105901
2022年2月22号14点36分 Tue Feb
2022-02-22 14:36:52.513 Tue Feb
2022-02-22 02:36:52.513 PM Tue Feb
2022/02/22 14:36
14:36 2022/02/22
2022/02/22
通过在时间对象上使用 Format("字符串")
方法,可以将时间对象格式化为字符串指定的格式。
七、解析字符串格式的时间
Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。
func Parse(layout, value string) (Time, error)
其中,layout 的时间必须是”2006-01-02 15:04:05″这个时间,不管格式如何,时间点一定得是这个,如:“Jan 2, 2006 at 3:04pm (MST)”,”2006-Jan-02″等。如换一个时间解析出来的时间就不对了,要特别注意这一点。
与 Parse 函数类似的还有 ParseInLocation 函数。
func ParseInLocation(layout, value string, loc *Location) (Time, error)
ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处:
- 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
- 第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。
package main
import (
"fmt"
"time"
)
func main() {
var layout string = "2006-01-02 15:04:05"
var timeStr string = "2019-12-12 15:22:12"
timeObj1, _ := time.Parse(layout, timeStr)
fmt.Println(timeObj1)
timeObj2, _ := time.ParseInLocation(layout, timeStr, time.Local)
fmt.Println(timeObj2)
}
输出结果:
2019-12-12 15:22:12 +0000 UTC
2019-12-12 15:22:12 +0800 CST
参考链接
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/118997.html