时间处理:掌握 Go 的 time 包
时间是程序中最常用也是最容易出错的概念之一。时区转换、格式化、时间计算、定时任务……每一个都是坑。
幸运的是,Go 的 time 包设计得非常优雅和实用。今天我们就来全面学习它。
时间的表示:time.Time
在 Go 中,时间用 time.Time 类型表示。它包含了从公元前 1 年到公元 9999 年的任何时间点,精度达到纳秒级。
package main
import (
"fmt"
"time"
)
func main() {
// 当前时间
now := time.Now()
fmt.Println("当前时间:", now)
// 输出: 当前时间: 2021-03-06 10:15:00.123456789 +0800 CST m=+0.000000001
// 访问时间组件
fmt.Println("年:", now.Year()) // 2021
fmt.Println("月:", now.Month()) // March
fmt.Println("日:", now.Day()) // 6
fmt.Println("时:", now.Hour()) // 10
fmt.Println("分:", now.Minute()) // 15
fmt.Println("秒:", now.Second()) // 0
fmt.Println("纳秒:", now.Nanosecond()) // 123456789
// 星期几
fmt.Println("星期:", now.Weekday()) // Saturday
// Unix 时间戳
fmt.Println("Unix 秒:", now.Unix()) // 1614996900
fmt.Println("Unix 毫秒:", now.UnixMilli()) // 1614996900123
fmt.Println("Unix 纳秒:", now.UnixNano()) // 1614996900123456789
}
创建特定时间
// 创建指定时间(本地时区)
t1 := time.Date(2021, time.March, 6, 10, 15, 0, 0, time.Local)
fmt.Println(t1) // 2021-03-06 10:15:00 +0800 CST
// 创建指定时间(UTC)
t2 := time.Date(2021, time.March, 6, 10, 15, 0, 0, time.UTC)
fmt.Println(t2) // 2021-03-06 10:15:00 +0000 UTC
// 创建指定时间(自定义时区)
loc, _ := time.LoadLocation("America/New_York")
t3 := time.Date(2021, time.March, 6, 10, 15, 0, 0, loc)
fmt.Println(t3) // 2021-03-06 10:15:00 -0500 EST
注意:
- 月份用
time.January到time.December表示(也可以用数字 1-12) - 时区用
time.Local(本地)、time.UTC或time.LoadLocation()
时间格式化
Go 的时间格式化非常独特,它不用 YYYY-MM-DD 这种占位符,而是用一个固定的参考时间:
Mon Jan 2 15:04:05 MST 2006 (即 Unix 时间 1136239445)
这个时间包含了所有时间组件的代表值:
- 1(日)
- 2(小时的 12 小时制)
- 3(月,缩写)
- 4(分)
- 5(秒)
- 6(年)
- -7(时区偏移)
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// 常用格式
fmt.Println(now.Format("2006-01-02")) // 2021-03-06
fmt.Println(now.Format("2006-01-02 15:04:05")) // 2021-03-06 10:15:00
fmt.Println(now.Format("2006/01/02 15:04")) // 2021/03/06 10:15
fmt.Println(now.Format("Jan 2, 2006")) // Mar 6, 2021
fmt.Println(now.Format("Monday, January 2, 2006")) // Saturday, March 6, 2021
fmt.Println(now.Format("3:04 PM")) // 10:15 AM
fmt.Println(now.Format("2006-01-02T15:04:05Z07:00")) // RFC3339
// 预定义常量
fmt.Println(now.Format(time.RFC3339)) // 2021-03-06T10:15:00+08:00
fmt.Println(now.Format(time.RFC822)) // 06 Mar 21 10:15 CST
fmt.Println(now.Format(time.Kitchen)) // 10:15AM
fmt.Println(now.Format(time.Stamp)) // Mar 6 10:15:00
}
解析时间字符串
// 解析时间字符串
timeStr := "2021-03-06 10:15:00"
t, err := time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
log.Fatal("解析失败:", err)
}
fmt.Println(t) // 2021-03-06 10:15:00 +0000 UTC
// 解析带时区的时间
timeStr2 := "2021-03-06T10:15:00+08:00"
t2, err := time.Parse(time.RFC3339, timeStr2)
if err != nil {
log.Fatal("解析失败:", err)
}
fmt.Println(t2) // 2021-03-06 10:15:00 +0800 +0800
时间计算
时间的加减
now := time.Now()
// 加时间
tomorrow := now.Add(24 * time.Hour)
nextWeek := now.AddDate(0, 0, 7) // 年、月、日
nextMonth := now.AddDate(0, 1, 0)
nextYear := now.AddDate(1, 0, 0)
fmt.Println("明天:", tomorrow)
fmt.Println("下周:", nextWeek)
fmt.Println("下月:", nextMonth)
fmt.Println("明年:", nextYear)
// 减时间
yesterday := now.Add(-24 * time.Hour)
lastWeek := now.AddDate(0, 0, -7)
fmt.Println("昨天:", yesterday)
fmt.Println("上周:", lastWeek)
时间差计算
start := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2021, 3, 6, 10, 15, 0, 0, time.UTC)
// 计算时间差
duration := end.Sub(start)
fmt.Println("时间差:", duration) // 1546h15m0s
fmt.Println("小时数:", duration.Hours()) // 1546.25
fmt.Println("分钟数:", duration.Minutes()) // 92775
fmt.Println("秒数:", duration.Seconds()) // 5566500
// 检查时间先后
fmt.Println("end 在 start 之后:", end.After(start)) // true
fmt.Println("start 在 end 之前:", start.Before(end)) // true
fmt.Println("两个时间相等:", start.Equal(end)) // false
时间的截断和舍入
now := time.Now()
// 截断到小时
truncated := now.Truncate(time.Hour)
fmt.Println("截断到小时:", truncated) // 2021-03-06 10:00:00
// 截断到天
truncatedDay := now.Truncate(24 * time.Hour)
fmt.Println("截断到天:", truncatedDay)
// 舍入到最近的 30 分钟
rounded := now.Round(30 * time.Minute)
fmt.Println("舍入到30分钟:", rounded)
时区处理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("本地时间:", now)
// 转换到 UTC
utc := now.UTC()
fmt.Println("UTC 时间:", utc)
// 加载时区
shanghai, _ := time.LoadLocation("Asia/Shanghai")
newYork, _ := time.LoadLocation("America/New_York")
london, _ := time.LoadLocation("Europe/London")
tokyo, _ := time.LoadLocation("Asia/Tokyo")
// 转换时区
fmt.Println("上海:", now.In(shanghai))
fmt.Println("纽约:", now.In(newYork))
fmt.Println("伦敦:", now.In(london))
fmt.Println("东京:", now.In(tokyo))
// 获取时区名称和偏移
name, offset := now.Zone()
fmt.Printf("时区: %s, 偏移: %d 秒\n", name, offset)
}
处理夏令时
// 纽约在夏季使用 EDT(UTC-4),冬季使用 EST(UTC-5)
loc, _ := time.LoadLocation("America/New_York")
winter := time.Date(2021, 1, 15, 12, 0, 0, 0, loc)
summer := time.Date(2021, 7, 15, 12, 0, 0, 0, loc)
fmt.Println("冬季:", winter) // 2021-01-15 12:00:00 -0500 EST
fmt.Println("夏季:", summer) // 2021-07-15 12:00:00 -0400 EDT
Duration:时间间隔
time.Duration 表示两个时间点之间的间隔,底层是 int64,单位是纳秒。
package main
import (
"fmt"
"time"
)
func main() {
// 创建 Duration
d1 := 5 * time.Second
d2 := 100 * time.Millisecond
d3 := 2 * time.Hour
d4 := time.Hour + 30*time.Minute
fmt.Println(d1) // 5s
fmt.Println(d2) // 100ms
fmt.Println(d3) // 2h0m0s
fmt.Println(d4) // 1h30m0s
// 转换单位
fmt.Println(d3.Hours()) // 2
fmt.Println(d3.Minutes()) // 120
fmt.Println(d3.Seconds()) // 7200
// Duration 计算
sum := d1 + d2
diff := d3 - d1
multiple := d1 * 2
fmt.Println("和:", sum) // 5.1s
fmt.Println("差:", diff) // 1h59m55s
fmt.Println("倍数:", multiple) // 10s
// 解析 Duration 字符串
d5, _ := time.ParseDuration("1h30m")
d6, _ := time.ParseDuration("2.5h")
d7, _ := time.ParseDuration("100ms")
fmt.Println(d5) // 1h30m0s
fmt.Println(d6) // 2h30m0s
fmt.Println(d7) // 100ms
}
定时器
time.Sleep
最简单的等待:
fmt.Println("开始")
time.Sleep(2 * time.Second)
fmt.Println("2秒后")
time.Timer
一次性定时器:
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个 2 秒后触发的定时器
timer := time.NewTimer(2 * time.Second)
fmt.Println("等待定时器...")
// 等待定时器触发
<-timer.C
fmt.Println("定时器触发!")
// 停止定时器(如果还没触发)
timer2 := time.NewTimer(5 * time.Second)
if timer2.Stop() {
fmt.Println("定时器已停止")
}
// 重置定时器
timer2.Reset(1 * time.Second)
<-timer2.C
fmt.Println("重置后的定时器触发!")
}
time.After
time.After 是 time.NewTimer 的简化版:
fmt.Println("开始等待")
<-time.After(2 * time.Second)
fmt.Println("2秒后")
常用于超时控制:
select {
case result := <-doSomething():
fmt.Println("完成:", result)
case <-time.After(5 * time.Second):
fmt.Println("超时!")
}
time.Ticker
周期性定时器:
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Ticker 触发:", t.Format("15:04:05"))
}
}
}()
// 运行 5 秒后停止
time.Sleep(5 * time.Second)
done <- true
fmt.Println("Ticker 已停止")
}
time.Tick
time.Tick 是 time.NewTicker 的简化版,但无法停止,只适合用在不会结束的 goroutine 中:
for t := range time.Tick(1 * time.Second) {
fmt.Println(t)
}
实战:倒计时程序
让我们写一个倒计时程序,综合应用所学知识:
package main
import (
"fmt"
"time"
)
func countdown(seconds int) {
fmt.Printf("倒计时开始!%d 秒后发射...\n\n", seconds)
start := time.Now()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for remaining := seconds; remaining > 0; remaining-- {
<-ticker.C
elapsed := time.Since(start)
fmt.Printf("\r剩余: %2d 秒 | 已过去: %v", remaining, elapsed.Round(time.Second))
}
fmt.Println("\n\n🚀 发射!")
}
func main() {
countdown(10)
}
实战:性能计时器
package main
import (
"fmt"
"time"
)
type Timer struct {
start time.Time
name string
}
func NewTimer(name string) *Timer {
return &Timer{
start: time.Now(),
name: name,
}
}
func (t *Timer) Stop() {
elapsed := time.Since(t.start)
fmt.Printf("%s 耗时: %v\n", t.name, elapsed)
}
func slowFunction() {
defer NewTimer("slowFunction").Stop()
time.Sleep(2 * time.Second)
}
func main() {
slowFunction()
// 输出: slowFunction 耗时: 2.000123456s
}
小结
今天我们全面学习了 Go 的 time 包:
- 时间表示:
time.Time和时间组件访问 - 时间格式化:独特的参考时间格式
- 时间计算:加减、差值、截断
- 时区处理:
LoadLocation和In - Duration:时间间隔类型
- 定时器:Timer、Ticker、Sleep
时间处理看似简单,实则暗藏玄机。Go 的 time 包设计精妙,既强大又易用。
练习时间
- 写一个程序,计算你出生到今天一共活了多少天
- 实现一个函数,判断给定日期是否是工作日
- 创建一个定时任务调度器,支持 cron 表达式
- 写一个程序,显示世界主要城市的当前时间
我们下篇见!
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。