Go 语言 time 包使用教程
Go 语言的 time 包提供了用于测量和显示时间的功能,包括时间点 (Time)、持续时间 (Duration)、定时器 (Timer) 和时区处理。
1. 基础概念
时间点 (Time)
time.Time结构体:表示一个具体的、固定的时间点,如 2025 年 10 月 31 日 11:31:18。
持续时间 (Duration)
time.Duration类型:表示两个时间点之间的时间间隔,本质上是int64类型,以纳秒为单位。- Go 语言提供了方便的常量来表示常用的时间单位:
time.Nanosecond、time.Microsecond、time.Millisecond、time.Second、time.Minute、time.Hour。
示例:Duration 的使用
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() 可以获取当前的本地时间点。
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 对象转换为指定的字符串格式。
// 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 对象。
// 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)。
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相等(纳秒级)。
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)) // false5. 实用技巧:测量代码执行耗时
在 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 异常退出)时执行指定的函数,非常适合用来计算代码块的执行耗时。
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:
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 暂停执行一段持续时间。
fmt.Println("开始等待...")
time.Sleep(2 * time.Second) // 暂停 2 秒
fmt.Println("等待结束。")单次定时器:time.NewTimer
创建一个在指定持续时间后触发的定时器。一旦触发,它会发送一个时间值到其内部的 Channel。
timer := time.NewTimer(3 * time.Second)
fmt.Println("单次定时器已启动,等待 3 秒...")
// 阻塞,直到定时器到期,接收到时间
t := <-timer.C
fmt.Println("定时器触发时间:", t)周期任务:time.NewTicker
创建一个周期性触发的任务,它会每隔一段持续时间发送一个时间值到其内部的 Channel。
// 每隔 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)。
// 尝试加载一个时区
loc, err := time.LoadLocation("America/New_York")
if err != nil {
// 如果系统没有时区信息文件,可能会失败
fmt.Println("加载时区失败:", err)
return
}
fmt.Println("加载的时区:", loc.String()) // America/New_York时区转换:In
将一个 time.Time 对象转换为另一个时区的时间。
// 假设现在获取了一个 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