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)
}
相关推荐
XHunter5 天前
Golang基础笔记十之goroutine和channel
golang基础笔记
XHunter7 天前
Golang基础笔记九之方法与接口
golang基础笔记
XHunter24 天前
Golang基础笔记三之数组和切片
golang基础笔记
XHunter24 天前
Golang基础笔记二之字符串及其操作
golang基础笔记