Golang基础笔记十一之日期与时间处理

本文首发于公众号:Hunter后端

原文链接:Golang基础笔记十一之日期与时间处理

本篇笔记介绍 Golang 里日期与时间的处理,以下是本篇笔记目录:

  1. 当前日期与时间的获取
  2. 字符串与时间格式的互相转换
  3. 时间戳与时间格式的互相转换
  4. 日期与时间的加减
  5. 星期数的获取
  6. 定时器与计时器

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.DurationDuration 也是自定义的一个时间单位,一纳秒就是一个 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)
}
相关推荐
XHunter6 天前
Golang基础笔记十六之反射
golang基础笔记
XHunter8 天前
Golang基础笔记十五之sync
golang基础笔记
XHunter13 天前
Golang基础笔记十四之文件操作
golang基础笔记
XHunter15 天前
Golang基础笔记十三之context
golang基础笔记
XHunter20 天前
Golang基础笔记十二之defer、panic、error
golang基础笔记
XHunter1 个月前
Golang基础笔记十之goroutine和channel
golang基础笔记
XHunter1 个月前
Golang基础笔记九之方法与接口
golang基础笔记
XHunter1 个月前
Golang基础笔记三之数组和切片
golang基础笔记
XHunter2 个月前
Golang基础笔记二之字符串及其操作
golang基础笔记