← json 包 | io 包 →

time 包 - 时间处理详解

time 包提供了时间的显示、测量、格式化等功能。掌握 time 包是开发定时任务、日志记录、性能监控等场景的基础。

📌 核心概念

Time

时间点

time.Now()
⏱️

Duration

时间间隔

time.Second
⏲️

Timer

定时器

time.NewTimer
🔄

Ticker

周期触发器

time.NewTicker

Time 时间点

📝 获取和操作时间

package main

import (
    "fmt"
    "time"
)

func main() {
    // 当前时间
    now := time.Now()
    fmt.Printf("Now: %v\n", now)
    
    // 时间分量
    fmt.Printf("Year: %d\n", now.Year())
    fmt.Printf("Month: %d\n", now.Month())
    fmt.Printf("Day: %d\n", now.Day())
    fmt.Printf("Hour: %d\n", now.Hour())
    fmt.Printf("Minute: %d\n", now.Minute())
    fmt.Printf("Second: %d\n", now.Second())
    fmt.Printf("Weekday: %v\n", now.Weekday())
    
    // 时间计算
    tomorrow := now.Add(24 * time.Hour)
    fmt.Printf("Tomorrow: %v\n", tomorrow)
    
    yesterday := now.AddDate(0, 0, -1)
    fmt.Printf("Yesterday: %v\n", yesterday)
    
    // 时间比较
    fmt.Printf("Before: %v\n", now.Before(tomorrow))
    fmt.Printf("After: %v\n", now.After(tomorrow))
    fmt.Printf("Equal: %v\n", now.Equal(now))
    
    // 时间差
    diff := tomorrow.Sub(now)
    fmt.Printf("Diff: %v hours\n", diff.Hours())
}

Duration 时间间隔

📝 Duration 操作

package main

import (
    "fmt"
    "time"
)

func main() {
    // 内置常量
    fmt.Println(time.Nanosecond)   // 1ns
    fmt.Println(time.Microsecond)  // 1µs
    fmt.Println(time.Millisecond)  // 1ms
    fmt.Println(time.Second)       // 1s
    fmt.Println(time.Minute)       // 1m0s
    fmt.Println(time.Hour)         // 1h0m0s
    
    // 创建 Duration
    d1 := 5 * time.Second
    d2 := 2 * time.Minute
    d3 := time.Hour + 30 * time.Minute
    
    // Duration 计算
    fmt.Printf("d1 + d2: %v\n", d1+d2)
    fmt.Printf("d2 - d1: %v\n", d2-d1)
    fmt.Printf("d1 * 2: %v\n", d1*2)
    fmt.Printf("d2 / 2: %v\n", d2/2)
    
    // 转换为不同单位
    fmt.Printf("d1 Nanoseconds: %d\n", d1.Nanoseconds())
    fmt.Printf("d1 Microseconds: %d\n", d1.Microseconds())
    fmt.Printf("d1 Milliseconds: %d\n", d1.Milliseconds())
    fmt.Printf("d1 Seconds: %f\n", d1.Seconds())
    fmt.Printf("d1 Minutes: %f\n", d1.Minutes())
    fmt.Printf("d1 Hours: %f\n", d1.Hours())
    
    // 解析 Duration 字符串
    d4, _ := time.ParseDuration("1h30m45s")
    fmt.Printf("Parsed: %v\n", d4)
}

时间格式化

📝 Format 和 Parse

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    
    // Go 的参考时间:2006-01-02 15:04:05
    // 记忆口诀:1 2 3 4 5 6 (美国格式)
    
    // 常用格式
    fmt.Println(now.Format("2006-01-02 15:04:05"))
    fmt.Println(now.Format("2006/01/02"))
    fmt.Println(now.Format("15:04:05"))
    fmt.Println(now.Format("2006-01-02T15:04:05Z07:00")) // RFC3339
    
    // 自定义格式
    fmt.Println(now.Format("Jan 2, 2006 at 3:04pm"))
    fmt.Println(now.Format("Monday, January 2, 2006"))
    
    // 解析时间字符串
    t1, _ := time.Parse("2006-01-02", "2024-03-15")
    fmt.Printf("Parsed: %v\n", t1)
    
    t2, _ := time.Parse(time.RFC3339, "2024-03-15T10:30:00Z")
    fmt.Printf("RFC3339: %v\n", t2)
    
    // 常用布局常量
    fmt.Println(now.Format(time.RFC822))
    fmt.Println(now.Format(time.RFC1123))
    fmt.Println(now.Format(time.DateTime))
}

💡 格式化要点

  • 参考时间: Go 使用 2006-01-02 15:04:05 作为参考格式
  • 记忆方法: 1 2 3 4 5 6 (美国日期格式)
  • 时区: 使用 Z07:00MST 表示时区
  • 布局常量: time.RFC3339、time.RFC822 等

Timer 和 Ticker

Timer 一次性定时器

📝 Timer 使用

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建 Timer
    timer := time.NewTimer(time.Second * 2)
    
    // 等待 Timer
    <-timer.C
    fmt.Println("2 seconds elapsed")
    
    // 使用 AfterFunc
    time.AfterFunc(time.Second, func() {
        fmt.Println("1 second later (async)")
    })
    
    // 简单等待
    <-time.After(time.Millisecond * 500)
    fmt.Println("500ms elapsed")
    
    // 停止 Timer
    timer2 := time.NewTimer(time.Minute)
    if timer2.Stop() {
        fmt.Println("Timer stopped before firing")
    }
    
    time.Sleep(time.Millisecond * 100)
}

Ticker 周期触发器

📝 Ticker 使用

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建 Ticker
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    
    // 周期执行
    done := make(chan bool)
    
    go func() {
        count := 0
        for {
            select {
            case <-ticker.C:
                count++
                fmt.Printf("Tick %d\n", count)
                if count >= 5 {
                    done <- true
                    return
                }
            }
        }
    }()
    
    <-done
    fmt.Println("Ticker stopped")
}

性能分析

📊 使用 time 进行性能测试

package main

import (
    "fmt"
    "time"
)

func main() {
    // 方法 1: 使用 Sub
    start := time.Now()
    doWork()
    elapsed := time.Since(start)
    fmt.Printf("Elapsed: %v\n", elapsed)
    
    // 方法 2: 使用 defer
    timedFunction()
    
    // 方法 3: 使用 AfterFunc 超时
    done := make(chan bool)
    timeout := time.AfterFunc(time.Second, func() {
        fmt.Println("Timeout!")
    })
    
    go func() {
        doWork()
        done <- true
    }()
    
    <-done
    timeout.Stop()
}

func doWork() {
    time.Sleep(time.Millisecond * 100)
}

func timedFunction() {
    defer func(start time.Time) {
        fmt.Printf("Function took: %v\n", time.Since(start))
    }(time.Now())
    
    doWork()
}

时区处理

📝 时区转换

package main

import (
    "fmt"
    "time"
)

func main() {
    // 本地时间
    local := time.Now()
    fmt.Printf("Local: %v (%v)\n", local, local.Location())
    
    // UTC 时间
    utc := local.UTC()
    fmt.Printf("UTC: %v\n", utc)
    
    // 加载时区
    loc, _ := time.LoadLocation("Asia/Shanghai")
    shanghai := local.In(loc)
    fmt.Printf("Shanghai: %v\n", shanghai)
    
    loc2, _ := time.LoadLocation("America/New_York")
    ny := local.In(loc2)
    fmt.Printf("New York: %v\n", ny)
    
    // 固定偏移时区
    fixedZone := time.FixedZone("CST", 8*3600)
    fmt.Printf("Fixed: %v\n", local.In(fixedZone))
}

最佳实践

✅ Time 使用建议

  • 存储 UTC: 数据库中存储 UTC 时间
  • 显示本地化: 展示时转换为本地时间
  • 使用常量: time.Second、time.Minute 等
  • 停止 Ticker: 使用 defer ticker.Stop()
  • 超时控制: 使用 time.After 或 context
  • 避免泄漏: Timer/Ticker 用后及时停止

🚨 常见陷阱

  • Timer 泄漏: 不调用 Stop() 会导致资源泄漏
  • Ticker 泄漏: 必须调用 Stop() 停止
  • 时区问题: 跨时区系统应统一使用 UTC
  • 时间比较: 使用 Equal() 而非 ==
  • 格式化错误: 记住参考时间是 2006 不是 1970

总结

✅ 核心要点

  • Time: 时间点,使用 time.Now() 获取
  • Duration: 时间间隔,纳秒为单位
  • Format/Parse: 使用参考时间 2006-01-02 15:04:05
  • Timer: 一次性定时器,用后 Stop()
  • Ticker: 周期触发器,用后 Stop()
  • 时区: 存储 UTC,显示本地化