导航菜单

时间与日期

time.Time 基础

time.Time

time.Time 是 Go 中表示时间点的核心类型,包含了年、月、日、时、分、秒、纳秒以及时区信息。它是值类型,可以安全地在函数间传递和比较。

获取当前时间

package main

import (
    "fmt"
    "time"
)

func main() {
    // Now — 获取当前本地时间
    now := time.Now()
    fmt.Println("当前时间:", now)
    // 当前时间: 2024-03-15 10:30:00.123456789 +0800 CST

    // UTC — 获取 UTC 时间
    utc := time.Now().UTC()
    fmt.Println("UTC 时间:", utc)
    // UTC 时间: 2024-03-15 02:30:00.123456789 +0000 UTC

    // Date — 创建指定日期的时间
    t := time.Date(2024, 3, 15, 10, 30, 0, 0, time.Local)
    fmt.Println("指定时间:", t)
    // 指定时间: 2024-03-15 10:30:00 +0800 CST
}

提取时间分量

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // 提取日期分量
    fmt.Println("年:", now.Year())         // 2024
    fmt.Println("月:", now.Month())        // March
    fmt.Println("日:", now.Day())          // 15
    fmt.Println("小时:", now.Hour())       // 10
    fmt.Println("分钟:", now.Minute())     // 30
    fmt.Println("秒:", now.Second())       // 0
    fmt.Println("纳秒:", now.Nanosecond()) // 123456789

    // Weekday — 星期几
    fmt.Println("星期:", now.Weekday())    // Friday

    // YearDay — 一年中的第几天
    fmt.Println("第几天:", now.YearDay())  // 75

    // Unix — Unix 时间戳(秒)
    fmt.Println("Unix 秒:", now.Unix())    // 1710473400

    // UnixMilli — 毫秒时间戳(Go 1.17+)
    fmt.Println("Unix 毫秒:", now.UnixMilli())

    // UnixMicro — 微秒时间戳(Go 1.17+)
    fmt.Println("Unix 微秒:", now.UnixMicro())

    // UnixNano — 纳秒时间戳
    fmt.Println("Unix 纳秒:", now.UnixNano())
}

时间创建与解析

Parse 与 ParseInLocation

package main

import (
    "fmt"
    "time"
)

func main() {
    // Parse — 解析时间字符串(使用默认时区 UTC)
    // 注意:Parse 的 layout 参数不是任意格式!
    t, err := time.Parse("2006-01-02", "2024-03-15")
    if err != nil {
        panic(err)
    }
    fmt.Println(t)  // 2024-03-15 00:00:00 +0000 UTC(注意时区为 UTC)

    // ParseInLocation — 在指定时区解析时间(推荐!)
    loc, _ := time.LoadLocation("Asia/Shanghai")
    t2, err := time.ParseInLocation("2006-01-02 15:04:05", "2024-03-15 10:30:00", loc)
    if err != nil {
        panic(err)
    }
    fmt.Println(t2)  // 2024-03-15 10:30:00 +0800 CST
}

常用 Layout 常量

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // time 包预定义的 Layout 常量
    fmt.Println(time.RFC3339)          // 2006-01-02T15:04:05Z07:00
    fmt.Println(time.RFC3339Nano)      // 2006-01-02T15:04:05.999999999Z07:00
    fmt.Println(time.RFC1123)          // Mon, 02 Jan 2006 15:04:05 MST
    fmt.Println(time.RFC850)           // Monday, 02-Jan-06 15:04:05 MST
    fmt.Println(time.RFC1123Z)         // Mon, 02 Jan 2006 15:04:05 -0700
    fmt.Println(time.Kitchen)          // 3:04PM
    fmt.Println(time.Stamp)            // Jan _2 15:04:05
    fmt.Println(time.DateOnly)         // 2006-01-02(Go 1.20+)
    fmt.Println(time.TimeOnly)         // 15:04:05(Go 1.20+)

    // 使用预定义常量格式化
    fmt.Println(now.Format(time.RFC3339))
    // 2024-03-15T10:30:00+08:00

    // 使用预定义常量解析
    t, _ := time.Parse(time.RFC3339, "2024-03-15T10:30:00+08:00")
    fmt.Println(t)
}

时间格式化

Format 方法

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // 自定义格式(必须使用 2006-01-02 15:04:05)
    fmt.Println(now.Format("2006-01-02"))                  // 2024-03-15
    fmt.Println(now.Format("2006/01/02 15:04:05"))         // 2024/03/15 10:30:00
    fmt.Println(now.Format("2006年01月02日 15时04分05秒"))    // 2024年03月15日 10时30分00秒
    fmt.Println(now.Format("Jan 02, 2006"))                 // Mar 15, 2024
    fmt.Println(now.Format("Monday, January 2, 2006"))      // Friday, March 15, 2024
    fmt.Println(now.Format("15:04:05.000"))                 // 10:30:00.123(毫秒)
    fmt.Println(now.Format("15:04:05.000000"))              // 10:30:00.123456(微秒)

    // 常见格式化模式
    fmt.Println(now.Format("02 Jan 2006 15:04 MST"))        // 15 Mar 2024 10:30 CST
    fmt.Println(now.Format("2006-01-02T15:04:05Z07:00"))    // 2024-03-15T10:30:00+08:00

    // 带星期
    fmt.Println(now.Format("2006-01-02 Monday"))            // 2024-03-15 Friday

    // 时间戳
    fmt.Println(now.Unix())           // 1710473400
    fmt.Println(now.UnixMilli())      // 1710473400123
}

时间运算

加减、比较

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // Add — 增加时间
    fmt.Println(now.Add(1 * time.Hour))        // 1 小时后
    fmt.Println(now.Add(30 * time.Minute))     // 30 分钟后
    fmt.Println(now.Add(-24 * time.Hour))      // 24 小时前(负数表示减少)

    // AddDate — 按日历单位增加
    // AddDate(years, months, days)
    fmt.Println(now.AddDate(1, 0, 0))   // 1 年后
    fmt.Println(now.AddDate(0, 1, 0))   // 1 个月后
    fmt.Println(now.AddDate(0, 0, 7))   // 7 天后
    fmt.Println(now.AddDate(-1, -2, -3)) // 1年2个月3天前

    // Sub — 计算两个时间点的差值
    t1 := time.Date(2024, 3, 15, 10, 0, 0, 0, time.Local)
    t2 := time.Date(2024, 3, 10, 8, 0, 0, 0, time.Local)
    diff := t1.Sub(t2)
    fmt.Println("差值:", diff)           // 125h0m0s
    fmt.Println("差值小时:", diff.Hours()) // 125
    fmt.Println("差值天数:", diff/24/time.Hour) // 5

    // Before / After / Equal
    fmt.Println(t1.After(t2))    // true
    fmt.Println(t1.Before(t2))   // false
    fmt.Println(t1.Equal(t2))    // false
    fmt.Println(t1.Equal(t1))    // true
}

Duration 类型

time.Duration

time.Duration 类型表示两个时间点之间的时间段,底层类型是 int64,单位为纳秒。Go 提供了丰富的常量简化 Duration 的创建。

package main

import (
    "fmt"
    "time"
)

func main() {
    // Duration 常量
    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
    d := 2*time.Hour + 30*time.Minute + 15*time.Second
    fmt.Println(d)              // 2h30m15s
    fmt.Println(d.Hours())      // 2.504166...
    fmt.Println(d.Minutes())    // 150.25
    fmt.Println(d.Seconds())    // 9015
    fmt.Println(d.Milliseconds()) // 9015000

    // Duration 运算
    d1 := 1 * time.Hour
    d2 := 30 * time.Minute
    fmt.Println(d1 + d2)    // 1h30m0s
    fmt.Println(d1 - d2)    // 30m0s
    fmt.Println(d1 * 2)     // 2h0m0s
    fmt.Println(d1 / d2)    // 2(整数除法)

    // 比较
    fmt.Println(d1 > d2)    // true

    // ParseDuration — 从字符串解析 Duration
    d3, _ := time.ParseDuration("1h30m")
    fmt.Println(d3)          // 1h30m0s
    d4, _ := time.ParseDuration("2.5s")
    fmt.Println(d4)          // 2.5s
    d5, _ := time.ParseDuration("100ms")
    fmt.Println(d5)          // 100ms
}

定时器

time.Timer

time.Timer

time.Timer 表示一个单次定时器,在指定时间后向通道发送当前时间。time.After 是其简化版本,time.AfterFunc 在定时触发时执行回调函数。

package main

import (
    "fmt"
    "time"
)

func main() {
    // Timer — 创建单次定时器
    timer := time.NewTimer(2 * time.Second)

    // 阻塞等待定时器触发
    <-timer.C
    fmt.Println("2 秒后触发")

    // Timer 可以 Stop 和 Reset
    timer2 := time.NewTimer(5 * time.Second)

    go func() {
        <-timer2.C
        fmt.Println("5 秒后触发")
    }()

    // 提前停止
    stopped := timer2.Stop()
    fmt.Println("停止成功:", stopped)  // true

    // Reset — 重置定时器
    timer3 := time.NewTimer(10 * time.Second)
    timer3.Reset(1 * time.Second)  // 重置为 1 秒后
    <-timer3.C
    fmt.Println("1 秒后触发(重置后)")
    timer3.Stop()

    // After — 便捷函数,返回只读通道
    <-time.After(1 * time.Second)
    fmt.Println("After: 1 秒后触发")

    // AfterFunc — 定时触发回调
    timer4 := time.AfterFunc(2*time.Second, func() {
        fmt.Println("AfterFunc: 回调被执行")
    })
    defer timer4.Stop()

    time.Sleep(3 * time.Second)
}

time.Ticker

time.Ticker

time.Ticker 表示一个周期性定时器,每隔固定时间向通道发送一次当前时间。适合实现心跳、定期轮询、限速等场景。

package main

import (
    "fmt"
    "time"
)

func main() {
    // Ticker — 创建周期性定时器
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()

    // 限制执行次数
    count := 0
    for t := range ticker.C {
        count++
        fmt.Printf("第 %d 次触发: %v\n", count, t.Format("15:04:05.000"))
        if count >= 5 {
            break
        }
    }
    // 第 1 次触发: 10:30:00.501
    // 第 2 次触发: 10:30:01.001
    // 第 3 次触发: 10:30:01.501
    // ...
}

// 实际应用:定期检查状态
func periodicCheck() {
    ticker := time.NewTicker(3 * time.Second)
    defer ticker.Stop()

    timeout := time.After(15 * time.Second)

    for {
        select {
        case <-ticker.C:
            fmt.Println("执行定期检查...")
            // 执行检查逻辑
        case <-timeout:
            fmt.Println("超时,停止检查")
            return
        }
    }
}

// 实际应用:限速器
func rateLimiter() {
    // 每秒最多处理 3 个请求
    limiter := time.NewTicker(333 * time.Millisecond) // ~3次/秒
    defer limiter.Stop()

    requests := 10
    processed := 0

    for i := 0; i < requests; i++ {
        <-limiter.C  // 等待下一个时间窗口
        processed++
        fmt.Printf("处理请求 %d\n", i+1)
    }
    fmt.Printf("共处理 %d 个请求\n", processed)
}

时区处理

package main

import (
    "fmt"
    "time"
)

func main() {
    // time.Local — 本地时区
    now := time.Now()
    fmt.Println("本地:", now)              // 2024-03-15 10:30:00 +0800 CST
    fmt.Println("UTC:", now.UTC())          // 2024-03-15 02:30:00 +0000 UTC

    // LoadLocation — 加载指定时区
    // 时区名称来自 IANA 时区数据库
    loc, err := time.LoadLocation("America/New_York")
    if err != nil {
        panic(err)
    }
    nyTime := now.In(loc)
    fmt.Println("纽约:", nyTime)           // 2024-03-14 22:30:00 -0400 EDT

    // 常用时区
    tokyo, _ := time.LoadLocation("Asia/Tokyo")
    london, _ := time.LoadLocation("Europe/London")
    fmt.Println("东京:", now.In(tokyo))     // 2024-03-15 03:30:00 +0900 JST
    fmt.Println("伦敦:", now.In(london))    // 2024-03-15 02:30:00 +0000 GMT

    // In — 将时间转换到指定时区
    utc := time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)
    fmt.Println("UTC:", utc)
    fmt.Println("北京:", utc.In(time.FixedZone("CST", 8*3600)))

    // FixedZone — 自定义固定偏移时区
    // 例如,某些地区不遵循夏令时,使用固定偏移
    cst := time.FixedZone("CST", 8*3600)  // UTC+8
    fmt.Println("固定 UTC+8:", utc.In(cst))

    // 时区转换的实际应用
    fmt.Println("\n--- 全球会议时间 ---")
    fmt.Printf("北京: %s\n", now.In(time.FixedZone("UTC+8", 8*3600)).Format("15:04"))
    fmt.Printf("东京: %s\n", now.In(time.FixedZone("UTC+9", 9*3600)).Format("15:04"))
    fmt.Printf("伦敦: %s\n", now.In(time.FixedZone("UTC+0", 0)).Format("15:04"))
    fmt.Printf("纽约: %s\n", now.In(time.FixedZone("UTC-5", -5*3600)).Format("15:04"))
}

性能计时

package main

import (
    "fmt"
    "time"
)

func main() {
    // time.Since — 计算从某个时间点到现在的时间差
    start := time.Now()

    // 模拟耗时操作
    time.Sleep(500 * time.Millisecond)

    elapsed := time.Since(start)
    fmt.Printf("耗时: %v\n", elapsed)           // 耗时: 500.123ms
    fmt.Printf("耗时(毫秒): %.2f\n", float64(elapsed)/float64(time.Millisecond))

    // time.Until — 计算从现在到某个时间点的时间差
    deadline := time.Now().Add(10 * time.Second)
    remaining := time.Until(deadline)
    fmt.Printf("剩余: %v\n", remaining)          // 剩余: 10s

    // 更精确的计时(使用 time.Now().Sub)
    start2 := time.Now()
    // ... 执行代码 ...
    elapsed2 := time.Now().Sub(start2)
    fmt.Printf("精确耗时: %v\n", elapsed2)

    // 实际应用:测量函数执行时间
    measureFunc(func() {
        time.Sleep(200 * time.Millisecond)
    })
}

// 测量函数执行时间的辅助函数
func measureFunc(f func()) {
    start := time.Now()
    f()
    fmt.Printf("函数执行时间: %v\n", time.Since(start))
}

练习题

练习 1:倒计时器

编写一个函数 Countdown(duration time.Duration),每秒打印剩余时间,最后打印 “时间到!”。

参考答案

解题思路:使用 time.NewTicker 每秒触发,记录截止时间,每次触发计算剩余时间。

代码

package main

import (
    "fmt"
    "time"
)

func Countdown(duration time.Duration) {
    deadline := time.Now().Add(duration)
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for {
        remaining := time.Until(deadline)
        if remaining <= 0 {
            fmt.Println("时间到!")
            return
        }

        select {
        case <-ticker.C:
            seconds := int(remaining.Seconds())
            fmt.Printf("剩余: %02d:%02d:%02d\n",
                seconds/3600, (seconds%3600)/60, seconds%60)
        }
    }
}

func main() {
    fmt.Println("10 秒倒计时开始:")
    Countdown(10 * time.Second)
}

输出

10 秒倒计时开始:
剩余: 00:00:09
剩余: 00:00:08
...
剩余: 00:00:01
剩余: 00:00:00
时间到!

关键点:使用 time.Until(deadline) 而非简单的计数器,避免定时器精度偏差导致的累积误差。

练习 2:时间格式转换器

编写一个函数 ConvertTimeFormat(input, fromLayout, toLayout, tz string) (string, error),将一个时区的字符串时间转换为另一个时区的格式化输出。

参考答案

解题思路:使用 ParseInLocation 在源时区解析,然后 In 转换到目标时区,最后 Format 格式化输出。

代码

package main

import (
    "fmt"
    "time"
)

func ConvertTimeFormat(input, fromLayout, toLayout, tz string) (string, error) {
    // 加载目标时区
    loc, err := time.LoadLocation(tz)
    if err != nil {
        return "", fmt.Errorf("无效时区 %q: %w", tz, err)
    }

    // 在源时区解析时间
    t, err := time.ParseInLocation(fromLayout, input, loc)
    if err != nil {
        return "", fmt.Errorf("解析时间失败: %w", err)
    }

    // 格式化输出
    return t.Format(toLayout), nil
}

func main() {
    // 北京时间 → 纽约时间
    result, err := ConvertTimeFormat(
        "2024-03-15 22:00:00",
        "2006-01-02 15:04:05",
        time.RFC3339,
        "America/New_York",
    )
    if err != nil {
        panic(err)
    }
    fmt.Println("纽约时间:", result)
    // 纽约时间: 2024-03-15T10:00:00-04:00

    // 纽约时间 → 东京时间
    result2, err := ConvertTimeFormat(
        "2024-03-15 10:00:00",
        "2006-01-02 15:04:05",
        "2006年01月02日 15时04分",
        "Asia/Tokyo",
    )
    if err != nil {
        panic(err)
    }
    fmt.Println("东京时间:", result2)
    // 东京时间: 2024年03月15日 23时04分
}

关键点ParseInLocation 确保在正确的时区解析时间字符串,避免时区歧义。

练习 3:任务调度器

实现一个简单的定时任务调度器,支持:

  • 在指定延迟后执行任务(AfterTask
  • 定期执行任务(EveryTask
  • 取消任务(CancelTask
参考答案

解题思路:封装 time.AfterFunctime.Ticker,使用 context.Context 支持取消。

代码

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type Scheduler struct {
    wg sync.WaitGroup
}

// AfterTask — 延迟执行一次性任务
func (s *Scheduler) AfterTask(delay time.Duration, task func()) {
    s.wg.Add(1)
    time.AfterFunc(delay, func() {
        defer s.wg.Done()
        task()
    })
}

// EveryTask — 定期执行任务,返回取消函数
func (s *Scheduler) EveryTask(interval time.Duration, task func()) (cancel func()) {
    ctx, cancel := context.WithCancel(context.Background())
    ticker := time.NewTicker(interval)

    s.wg.Add(1)
    go func() {
        defer s.wg.Done()
        defer ticker.Stop()

        for {
            select {
            case <-ticker.C:
                task()
            case <-ctx.Done():
                fmt.Println("定期任务已取消")
                return
            }
        }
    }()

    return cancel
}

// Wait — 等待所有任务完成
func (s *Scheduler) Wait() {
    s.wg.Wait()
}

func main() {
    sched := Scheduler{}

    // 一次性延迟任务
    sched.AfterTask(1*time.Second, func() {
        fmt.Println("延迟任务 1 完成")
    })
    sched.AfterTask(2*time.Second, func() {
        fmt.Println("延迟任务 2 完成")
    })

    // 定期任务
    cancel := sched.EveryTask(500*time.Millisecond, func() {
        fmt.Println("定期任务执行:", time.Now().Format("15:04:05.000"))
    })

    // 3 秒后取消定期任务
    sched.AfterTask(3*time.Second, func() {
        cancel()
        fmt.Println("已发送取消信号")
    })

    sched.Wait()
    fmt.Println("所有任务完成")
}

输出

延迟任务 1 完成
定期任务执行: 10:30:01.501
定期任务执行: 10:30:02.001
延迟任务 2 完成
定期任务执行: 10:30:02.501
已发送取消信号
定期任务已取消
所有任务完成

关键点:使用 context.WithCancel 实现优雅取消,sync.WaitGroup 确保所有任务完成后再退出主 goroutine。

搜索