时间处理:掌握 Go 的 time 包

深入理解 Go 的 time 包,学会时间表示、格式化、计算和定时任务

时间处理:掌握 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.Januarytime.December 表示(也可以用数字 1-12)
  • 时区用 time.Local(本地)、time.UTCtime.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.Aftertime.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.Ticktime.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 包:

  1. 时间表示time.Time 和时间组件访问
  2. 时间格式化:独特的参考时间格式
  3. 时间计算:加减、差值、截断
  4. 时区处理LoadLocationIn
  5. Duration:时间间隔类型
  6. 定时器:Timer、Ticker、Sleep

时间处理看似简单,实则暗藏玄机。Go 的 time 包设计精妙,既强大又易用。

练习时间

  1. 写一个程序,计算你出生到今天一共活了多少天
  2. 实现一个函数,判断给定日期是否是工作日
  3. 创建一个定时任务调度器,支持 cron 表达式
  4. 写一个程序,显示世界主要城市的当前时间

我们下篇见!

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页