本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十一之日期与时间处理
本篇笔记介绍 Golang 里日期与时间的处理,以下是本篇笔记目录:
- 当前日期与时间的获取
- 字符串与时间格式的互相转换
- 时间戳与时间格式的互相转换
- 日期与时间的加减
- 星期数的获取
- 定时器与计时器
1、当前日期与时间的获取
在 Golang 里,日期和时间处理都通过 time 包来实现。
如果我们想获取当前时间,我们可以使用 time.Now() 来操作:
go
now := time.Now()
fmt.Println(now) // 2025-06-29 12:29:16.112605 +0800 CST m=+0.000154626
如果我们想获取单独的年月日时分秒字段,可以使用下面的操作:
go
now := time.Now()
year, month, day := now.Date()
hour, minute, second := now.Clock()
fmt.Println(year, month, day) // 2025 June 29
fmt.Println(hour, minute, second) // 15 29 50
可以看到输出的月份是 June
,但实际上月份是一个自定义的类型 Month
,本质上也是一个 int 型数据,其源代码如下:
go
type Month int
const (
January Month = 1 + iota
February
March
April
May
June
July
August
September
October
November
December
)
所以,对于这里的年月日时分秒的单个变量,我们想将其组合输出为一般的 %Y-%m-%d %H:%M:%S
格式,可以如下操作:
go
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
我们也可以分别单独获取对应的年月日时分秒数据:
go
now := time.Now()
fmt.Println(now.Year())
fmt.Println(now.Month())
fmt.Println(now.Day())
fmt.Println(now.Hour())
fmt.Println(now.Minute())
fmt.Println(now.Second())
2、字符串与时间格式的互相转换
1. 时间格式转字符串
在其他计算机语言中,如果想将时间字段转化为字符串,格式化的操作比如 Python,一般是类似于 %Y-%m-%d %H:%M:%S
这种,但是 Golang 里是一个特殊的格式化字段,为 2006-01-02 15:04:05
。
Go 里对时间字段格式化的函数为 Format()
,下面是将时间格式转为字符串的操作为:
go
now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2025-06-29 22:45:11
当然,格式化操作也可以单独针对日期,或者时间,连接的符号也可以自定义:
go
now := time.Now()
fmt.Println(now.Format("2006/01/02")) // 2025/06/29
fmt.Println(now.Format("15:04:05")) // 23:22:06
2. 字符串转时间格式
字符串转时间格式使用 time.Parse()
函数,以下是一个测试:
go
timeStr := "2025/06/29"
t, err := time.Parse("2006/01/02", timeStr)
if err != nil {
fmt.Println("str to time error: ", err)
} else {
fmt.Println("str to time is: ", t)
}
// str to time is: 2025-06-29 00:00:00 +0000 UTC
在这里 time.Parse() 返回两个字段,一个是转换后的时间字段,一个是转换过程中的错误。
上面是转换日期,转换时间也是一样的操作:
go
timeStr = "20:24:24"
t, err := time.Parse("15:04:05", timeStr)
if err != nil {
fmt.Println("str to time error: ", err)
} else {
fmt.Println("str to time is: ", t)
}
// str to time is: 0000-01-01 20:24:24 +0000 UTC
而如果提供了错误的时间字符串,返回的 err 字段则不会为空,比如下面这个示例:
go
timeStr := "2025/13/29"
t, err := time.Parse("2006/01/02", timeStr)
if err != nil {
fmt.Println("str to time error: ", err)
} else {
fmt.Println("str to time is: ", t)
}
// str to time error: parsing time "2025/13/29": month out of range
3、时间戳与时间格式的互相转换
另一个在时间函数中常用到的用于转换的数据是时间戳,下面介绍一下时间戳与时间的互相转换。
1. 时间格式转换为时间戳
下面是时间格式转换为秒级的时间戳:
go
now := time.Now()
fmt.Println(now.Unix()) // 1751211429
还有转换为毫秒,纳秒级的操作:
go
now := time.Now()
fmt.Println(now.UnixMilli()) // 1751211522339
fmt.Println(now.UnixNano()) // 1751211522339955000
2. 时间戳转换为时间格式
时间戳转换为时间格式的函数为 time.Unix()
:
go
timestamp := 1751211429
targetTime := time.Unix(int64(timestamp), 0)
fmt.Println(targetTime) // 2025-06-29 23:37:09 +0800 CST
这里需要注意,输入的时间戳需要是 int64 类型,输入的第二个参数为纳秒值,如果不需要那么精细的话,传值为 0 即可。
4、日期与时间的加减
介绍日期与时间的加减,这里分为两部分来介绍,一部分是时分秒,一部分是年月日,他们分别用到的函数是 Add()
和 AddDate()
。
1. Add()-时分秒的加减
time.Duration
Add()
函数的参数类型是 time.Duration
,Duration
也是自定义的一个时间单位,一纳秒就是一个 Duration
,它的类型是 int64
,范围是:
go
const (
minDuration Duration = -1 << 63
maxDuration Duration = 1<<63 - 1
)
而时分秒也都根据其转换关系定义好了各自的字段:
go
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
所以我们在使用 Add()
函数的时候可以直接使用对应的单位。
时分秒的加减
go
now := time.Now()
threeHoursLater := now.Add(3 * time.Hour)
thirtyMinutesAgo := now.Add(-30 * time.Minute)
twoDaysLater := now.Add(48 * time.Hour)
fmt.Println("now is: ", now.Format("2006-01-02 15:04:05"))
fmt.Println("three hours later is: ", threeHoursLater.Format("2006-01-02 15:04:05"))
fmt.Println("thirty minutes ago is: ", thirtyMinutesAgo.Format("2006-01-02 15:04:05"))
fmt.Println("two days later is: ", twoDaysLater.Format("2006-01-02 15:04:05"))
输出结果如下:
go
now is: 2025-06-30 22:58:38
three hours later is: 2025-07-01 01:58:38
thirty minutes ago is: 2025-06-30 22:28:38
two days later is: 2025-07-02 22:58:38
2. AddDate()-年月日的加减
AddDate()
函数接收三个参数,分别是 years、months、days,表示需要在当前时间需要增加的年数、月数和天数。
如果是想要回溯过去的日期,在对应的参数前加上负号 -
即可。
如果是不需要指定的参数设为 0 即可。
比如想要获取今天前一个月的日期,以及今天往后三天的日期,可以如下操作:
go
now := time.Now()
lastMonth := now.AddDate(0, -1, 0)
latestThreeDays := now.AddDate(0, 0, 3)
fmt.Println("last month is: ", lastMonth.Format("2006-01-02 15:04:05"))
fmt.Println("latest three days is: ", latestThreeDays.Format("2006-01-02 15:04:05"))
3. Add() 和 AddDate() 使用示例
这里分别使用 Add()
和 AddDate()
两个函数打印出之后七天的日期,其操作如下:
使用 Add()
函数:
go
now := time.Now()
for i := range 7 {
targetDate := now.Add(time.Duration(24*(i+1)) * time.Hour)
fmt.Println(targetDate.Format("2006-01-02"))
}
使用 AddDate()
函数:
go
now := time.Now()
for i := range 7 {
targetDate := now.AddDate(0, 0, i+1)
fmt.Println(targetDate.Format("2006-01-02"))
}
4. 两个时间点的差值
1) Sub()
如果我们想获取两个时间点之间差值,比如用于测试某个函数执行的时间,可以使用 Sub()
函数,返回的结果也是 time.Duration
:
go
t1 := time.Now()
time.Sleep(3 * time.Second)
t2 := time.Now()
subResult := t2.Sub(t1)
fmt.Println(subResult) // 3.00144925s
我们可以将 subResult
直接转换成我们想要的单位,比如毫秒和分钟:
go
fmt.Println("use millseconds: ", subResult.Milliseconds()) // use millseconds: 3001
fmt.Println("use minutes: ", subResult.Minutes()) // use minutes: 0.05002786111666667
2) time.Since()
如果我们想获取现在距离某个时间点的差值,也可以直接使用 time.Since() 函数,其使用示例如下:
go
t1 := time.Now()
time.Sleep(2 * time.Second)
result := time.Since(t1)
fmt.Println(result) // 2.001462166s
5、星期数的获取
我们也可以根据日期来获取对应的星期数,比如今天是星期几:
go
now := time.Now()
fmt.Println(now.Weekday()) // Monday
输出的信息是 Monday
。
星期数的底层数据也是 int 型,但是 Golang 将其包了一层,自定义了一个 Weekday
的类型:
go
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
从周日开始到周六,分别是从 0 到 6,我们可以打印一下:
go
now := time.Now()
fmt.Printf("%s, %d\n", now.Weekday(), now.Weekday()) // Monday, 1
6、定时器与计时器
下面介绍一下 Golang 里 time 模块的定时器和计时器如何使用。
1. 定时器
定时器有两个写法,一个是 time.NewTimer()
,一个是 time.After()
,接收的参数类型都是 time.Duration
。
下面直接用代码示例来介绍如何使用。
1) time.NewTimer
有一个需求,我们需要调用某个函数,但是函数的执行时长是不定的,而整体执行的时长是有限的,我们希望能在指定的时间内返回数据,如果这个函数执行超时就不希望它再执行了,能够立即获取其超时状态。
针对这个需求,我们就可以使用定时器来完成。
首先,我们有一个需要执行的函数,这个函数可能是调用某个接口,可能是从 Redis 或者 MySQL 中读数据,但是其执行时长是不定的,我们用 RandomTimeWork()
函数来替代,并且在其中设置一个随机休息的时间用来模拟不定的执行时长:
go
func RandomTimeWork() int {
sleepSeconds := rand.Intn(10)
time.Sleep(time.Duration(sleepSeconds) * time.Second)
return sleepSeconds
}
然后我们需要一个中间函数使用通道来传递其返回值:
go
func CallFunc(ch chan int) {
result := RandomTimeWork()
ch <- result
}
接下来就是主函数的操作,我们需要先设置一个定时器,这里我们设置为 5 秒的超时:
go
timeout := time.NewTimer(5 * time.Second)
然后设置一个通道用于传输数据,并且使用 goroutine 来调用:
go
ch := make(chan int)
go CallFunc(ch)
最后就是使用 select 操作来进行等待,看是通道先返回数据,还是定时器先计时完毕:
go
select {
case result := <-ch:
fmt.Println("call func success, sleep seconds: ", result)
case <-timeout.C:
fmt.Println("call func timeout")
}
其整体代码如下:
go
package main
import (
"fmt"
"math/rand"
"time"
)
func RandomTimeWork() int {
sleepSeconds := rand.Intn(10)
time.Sleep(time.Duration(sleepSeconds) * time.Second)
return sleepSeconds
}
func CallFunc(ch chan int) {
result := RandomTimeWork()
ch <- result
}
func main() {
timeout := time.NewTimer(5 * time.Second)
ch := make(chan int)
go CallFunc(ch)
select {
case result := <-ch:
fmt.Println("call func success, sleep seconds: ", result)
case <-timeout.C:
fmt.Println("call func timeout")
}
}
在上面的操作中,如果待执行的函数先执行完毕,而定时器却没有结束,我们可以手动执行停止定时器操作。
实际上,如果不手动停止,默认等待定时器触发结束或者程序完毕也可以,但是在高并发场景下,如果有很多未完成的定时器会造成内存占用增加,且增加程序的 GC 负担,因此,我们可以选择手动提前停止定时器。
停止操作也很简单,在获取到函数执行的结果后,我们可以如下操作:
go
select {
case result := <-ch:
fmt.Println("call func success, sleep seconds: ", result)
timeout.Stop()
case <-timeout.C:
fmt.Println("call func timeout")
}
2) time.After
除了 time.NewTimer()
,我们可以使用更简单的 time.After()
函数来执行一个定时器,相对上面的完整示例,我们只改动 main
函数里代码如下:
go
func main() {
ch := make(chan int)
go CallFunc(ch)
select {
case result := <-ch:
fmt.Println("call func success, sleep seconds: ", result)
case <-time.After(5 * time.Second):
fmt.Println("call func timeout")
}
}
注意 :使用 time.After()
有个问题就是不可以提前手动结束定时器。
2. 计时器
计时器常用于定时任务,比如每隔多长时间执行某个动作,用到的函数是 time.NewTicker
,传入的参数是 time.Duration
。
比如我们想将某个函数设置为每隔三秒钟执行一次,我们可以如下操作:
go
func TargetFunc() {
fmt.Println("call target func at: ", time.Now().Format("2006-01-02 15:04:05"))
}
func CallFuncEntrance(ticker *time.Ticker) {
// for t := range ticker.C {
for range ticker.C {
TargetFunc()
}
}
func main() {
ticker := time.NewTicker(3 * time.Second)
go CallFuncEntrance(ticker)
time.Sleep(10 * time.Second)
ticker.Stop()
}
上面的示例中,开启了一个 goroutine 并将计时器作为参数传入,每隔三秒钟触发一次目标函数 TargetFunc()
。
并且在最后执行了计时器的停止操作 ticker.Stop()
。
在这里如果我们想重置计时器的间隔时间,可以使用 Reset() 操作:
go
func main() {
ticker := time.NewTicker(3 * time.Second)
go CallFuncEntrance(ticker)
time.Sleep(10 * time.Second)
ticker.Reset(1 * time.Second)
time.Sleep(4 * time.Second)
ticker.Stop()
}
执行 main 函数可以看到目标函数执行的时间间隔会从 3s 变成 1s。
定时任务的其他实现方式
除了这个操作用来执行定时任务外,我们还可以使用 for{}
和 time.Sleep()
操作来实现定时任务,其示例如下:
go
func TargetFunc() {
fmt.Println("call target func at: ", time.Now().Format("2006-01-02 15:04:05"))
}
func CallFuncEntrance() {
for {
TargetFunc()
time.Sleep(3 * time.Second)
}
}
func main() {
go CallFuncEntrance()
time.Sleep(10 * time.Second)
}