Skip to content

Go 语言 time 包使用教程

Go 语言的 time 包提供了用于测量和显示时间的功能,包括时间点 (Time)、持续时间 (Duration)、定时器 (Timer) 和时区处理。

1. 基础概念

时间点 (Time)

  • time.Time 结构体:表示一个具体的、固定的时间点,如 2025 年 10 月 31 日 11:31:18。

持续时间 (Duration)

  • time.Duration 类型:表示两个时间点之间的时间间隔,本质上是 int64 类型,以纳秒为单位。
  • Go 语言提供了方便的常量来表示常用的时间单位:time.Nanosecondtime.Microsecondtime.Millisecondtime.Secondtime.Minutetime.Hour

示例:Duration 的使用

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // 定义一个持续时间
    d := 2 * time.Hour + 30 * time.Minute + 15 * time.Second
    fmt.Println("持续时间 (原始):", d) // 2h30m15s

    // 将 Duration 转换为其他单位的浮点数
    fmt.Printf("持续时间 (秒): %.2f s\n", d.Seconds()) // 9015.00 s
    fmt.Printf("持续时间 (小时): %.2f h\n", d.Hours()) // 2.50 h
}

2. 获取当前时间

使用 time.Now() 可以获取当前的本地时间点。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("当前时间点 (Time 对象):", now) // e.g., 2025-10-31 11:31:18.123456789 +0900 JST

    // 获取时间点的特定部分
    fmt.Printf("年: %d, 月: %s, 日: %d\n", now.Year(), now.Month(), now.Day())
    fmt.Printf("时: %d, 分: %d, 秒: %d\n", now.Hour(), now.Minute(), now.Second())

    // 获取 Unix 时间戳 (从 1970-01-01 00:00:00 UTC 开始的秒数)
    fmt.Println("Unix 时间戳 (秒):", now.Unix())
    fmt.Println("Unix 时间戳 (纳秒):", now.UnixNano())
}

3. 时间的格式化与解析

Go 语言在处理时间字符串时有一个非常独特的机制:必须使用固定的参考时间(2006-01-02 15:04:05.999999999 MST)来定义格式布局(Layout)

格式化(Time -> String)

time.Time 对象转换为指定的字符串格式。

go
// time.Time -> string
now := time.Now()

// 常见的日期格式
layout1 := "2006-01-02"
fmt.Println("格式化 1 (日期):", now.Format(layout1)) // 2025-10-31

// 常见的完整格式
layout2 := "2006-01-02 15:04:05" // 15: 小时 (24 小时制), 04: 分, 05: 秒
fmt.Println("格式化 2 (完整):", now.Format(layout2)) // 2025-10-31 11:31:18

// Go 语言中预定义的格式布局常量
fmt.Println("RFC3339 格式:", now.Format(time.RFC3339))

解析(String -> Time)

将符合特定格式布局的字符串解析为 time.Time 对象。

go
// string -> time.Time
timeStr := "2023-11-20 08:30:00"
layout := "2006-01-02 15:04:05" // 格式必须与 timeStr 严格匹配

t, err := time.Parse(layout, timeStr)
if err != nil {
    fmt.Println("时间解析错误:", err)
    return
}
fmt.Println("解析后的时间:", t) // 2023-11-20 08:30:00 +0000 UTC

注意: 如果解析时未指定时区,Go 默认将其视为 UTC 时间

Go 语言的时间布局常量

参考值 (Go Format)含义
2006
01
02
15小时 (24小时制)
03小时 (12小时制)
04
05
PM / pm上午/下午
Mon星期几
MST时区名 (Local)
-0700时区偏移量

4. 时间的加减与比较

时间点加减

  • Add(d Duration): 返回当前时间点加上一个持续时间 d 后的新时间点。
  • Sub(t Time): 返回两个时间点之间的持续时间 (Duration)。
go
now := time.Now()

// 1. 加法:当前时间加 3 小时 30 分钟
futureTime := now.Add(3*time.Hour + 30*time.Minute)
fmt.Println("未来时间:", futureTime.Format("15:04:05"))

// 2. 减法:计算两个时间点之间的时间间隔
duration := futureTime.Sub(now)
fmt.Println("时间间隔 (Duration):", duration) // 3h30m0s

// 3. 减法:计算当前时间减去一个持续时间
pastTime := now.Add(-24 * time.Hour) // 相当于减去一天
fmt.Println("昨天时间:", pastTime.Format("2006-01-02"))

时间点比较

  • Before(t Time): 当前时间是否在 t 之前。
  • After(t Time): 当前时间是否在 t 之后。
  • Equal(t Time): 当前时间是否与 t 相等(纳秒级)。
go
t1 := time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC)

fmt.Println("t1 在 t2 之前吗?", t1.Before(t2)) // true
fmt.Println("t2 在 t1 之后吗?", t2.After(t1))   // true
fmt.Println("t1 和 t2 相等吗?", t1.Equal(t2))  // false

5. 实用技巧:测量代码执行耗时

在 Go 语言中,测量一段代码的执行时间非常简单,通常利用 time.Now() 获取起始时间,并在代码块结束时计算时间差。结合 defer 关键字可以实现非常优雅的耗时统计。

核心 API

API作用
time.Now()返回当前的本地时间点 (time.Time)。
time.Since(t)这是一个便捷函数,等价于 time.Now().Sub(t),用于计算从起始时间 t 到当前时间点的持续时间 (time.Duration)。
time.Duration.String()将持续时间格式化为易读的字符串,如 "25.68ms"

使用 defer 优雅地测量耗时

使用 defer 可以在函数执行完毕(包括正常返回、panic 异常退出)时执行指定的函数,非常适合用来计算代码块的执行耗时。

go
package main

import (
    "fmt"
    "time"
)

// timeTrack 函数用于开始计时,并返回一个函数。
// 当这个返回的函数被 defer 调用时,它会计算并打印耗时。
func timeTrack(start time.Time, name string) {
    // time.Since(start) 相当于 time.Now().Sub(start)
    elapsed := time.Since(start) 
    fmt.Printf("【%s】执行完毕,总耗时:%s\n", name, elapsed)
}

func longRunningFunction() {
    // 1. 使用 defer 调用 timeTrack,并传入 time.Now() 作为起始时间。
    // defer 会在 longRunningFunction 执行结束后调用 timeTrack(start, "任务A")。
    defer timeTrack(time.Now(), "任务A")

    // 模拟一段耗时的代码
    sum := 0
    for i := 0; i < 1000000000; i++ {
        sum += 1 // 简单的加法操作
    }
    fmt.Println("任务A内部完成计算,结果:", sum)
    
    // timeTrack 将在此处被调用执行
}

func main() {
    longRunningFunction()
}

运行结果示例:

任务A内部完成计算,结果: 1000000000
【任务A】执行完毕,总耗时:421.234ms

简单场景下的直接计算

对于简单的代码块,也可以直接使用 time.Now()time.Sub() API:

go
start := time.Now() // 记录开始时间

// --- 待测量的代码块 ---
for i := 0; i < 100000; i++ {
    // ... 业务逻辑 ...
}
// --- 待测量的代码块 ---

elapsed := time.Since(start) // 或 time.Now().Sub(start)
fmt.Printf("代码块执行耗时:%s\n", elapsed)

5. 定时器与周期任务

time 包提供了三种控制程序等待和执行周期任务的机制。

延迟执行:time.Sleep

让当前 Goroutine 暂停执行一段持续时间。

go
fmt.Println("开始等待...")
time.Sleep(2 * time.Second) // 暂停 2 秒
fmt.Println("等待结束。")

单次定时器:time.NewTimer

创建一个在指定持续时间后触发的定时器。一旦触发,它会发送一个时间值到其内部的 Channel。

go
timer := time.NewTimer(3 * time.Second)

fmt.Println("单次定时器已启动,等待 3 秒...")

// 阻塞,直到定时器到期,接收到时间
t := <-timer.C 

fmt.Println("定时器触发时间:", t)

周期任务:time.NewTicker

创建一个周期性触发的任务,它会每隔一段持续时间发送一个时间值到其内部的 Channel。

go
// 每隔 1 秒发送一次信号
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() // 确保在不需要时停止 Ticker,释放资源

i := 0
// 循环接收 Ticker 的信号
for t := range ticker.C {
    i++
    fmt.Printf("第 %d 次执行,时间: %s\n", i, t.Format("15:04:05"))
    if i >= 3 {
        break // 执行 3 次后退出
    }
}
fmt.Println("Ticker 已停止。")

6. 时区处理

默认情况下,time.Now() 返回的是系统本地时间,而 time.Parse() 返回的是 UTC 时间(除非布局中包含时区信息)。

加载时区:time.LoadLocation

使用时区名(如 "Asia/Shanghai""America/New_York")加载时区对象 (*time.Location)。

go
// 尝试加载一个时区
loc, err := time.LoadLocation("America/New_York")
if err != nil {
    // 如果系统没有时区信息文件,可能会失败
    fmt.Println("加载时区失败:", err)
    return
}
fmt.Println("加载的时区:", loc.String()) // America/New_York

时区转换:In

将一个 time.Time 对象转换为另一个时区的时间。

go
// 假设现在获取了一个 UTC 时间点
utcTime := time.Date(2025, time.January, 1, 8, 0, 0, 0, time.UTC) // 1月1日 UTC 8点

// 1. 加载北京时区 (Asia/Shanghai)
locBeijing, _ := time.LoadLocation("Asia/Shanghai")

// 2. 将 UTC 时间转换为北京时间
beijingTime := utcTime.In(locBeijing) // 北京时间比 UTC 快 8 小时 (UTC+8)
fmt.Println("UTC 时间:", utcTime)         // 2025-01-01 08:00:00 +0000 UTC
fmt.Println("北京时间:", beijingTime)       // 2025-01-01 16:00:00 +0800 CST

// 3. 如果需要将时间解析为特定时区,可以使用 time.ParseInLocation
timeStr := "2025-01-01 16:00:00"
layout := "2006-01-02 15:04:05"

// 将字符串解析为北京时区的时间
tInLoc, _ := time.ParseInLocation(layout, timeStr, locBeijing)
fmt.Println("ParseInLocation 结果:", tInLoc) // 2025-01-01 16:00:00 +0800 CST