【Go】time 包

本文介绍了Go语言time包的基础用法,包括获取当前时间、时间戳、日期解析,时间操作函数如Add和Sub,定时器的NewTimer、NewTicker和Tick,以及时间格式化和解析技巧。适合开发者深入理解Go的时间处理和定时任务实现。
摘要由CSDN通过智能技术生成


一、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
}
  1. wall:表示距离公元 1 年 1 月 1 日 00:00:00UTC 的秒数;
  2. ext:表示纳秒;
  3. 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
20222221436分 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 函数类似,但有两个重要的不同之处:

  1. 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
  2. 第二,当时间字符串提供了时区偏移量信息时,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

参考链接

  1. Go语言time包:时间和日期
  2. go语言 timer.reset分析
  3. go语言time.Timer定时器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值