Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言time库用法

Go语言time库核心用法与避坑指南

作者:女王大人万岁

文章介绍了Go语言的time库,该库提供全面的时间处理功能,文章详细说明了time库的核心类型Time、常用方法、格式化与解析、时区管理以及定时器的使用,感兴趣的朋友跟随小编一起看看吧

一、核心定位与设计亮点

1.1 核心定位

time库是Go语言内置的时间处理基础库,提供时间获取、运算、格式化、时区管理及定时任务等全链路能力,是业务开发(日志记录、任务调度、时效校验、缓存过期控制)与底层开发(并发控制、性能统计)中不可或缺的工具,能够高效解决跨时区、高精度时间计算、并发定时等常见痛点。

1.2 设计亮点

二、核心功能与用法

2.1 核心类型:Time

Time类型用于表示一个具体的时间点,包含从Unix纪元(1970-01-01 00:00:00 UTC)开始的秒数、纳秒偏移量及时区信息,是time库所有操作的核心载体。

常用方法

基础示例

package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now() // 获取本地时区当前时间,包含完整时区信息
    fmt.Printf("当前时间(本地时区):%v\n", now)
    fmt.Printf("当前时间(UTC时区):%v\n", now.In(time.UTC))
    fmt.Printf("秒级时间戳:%d\n", now.Unix())
    fmt.Printf("纳秒级时间戳:%d\n", now.UnixNano())
    fmt.Printf("是否为零时间:%v\n", time.Time{}.IsZero())
    // 时间截断至小时(分、秒、纳秒归零)
    truncatedHour := now.Truncate(time.Hour)
    fmt.Printf("截断至小时:%v\n", truncatedHour)
}

2.2 时间获取与计算

时间获取

时间计算(依赖Duration类型)

time.Duration表示时间间隔,支持纳秒(ns)、微秒(µs)、毫秒(ms)、秒(s)、分(m)、时(h)等单位,核心用于时间加减与差值计算,需注意其范围限制(±292年,超出会溢出)。

时间与TTL互转(缓存/超时场景核心)

TTL(Time To Live)本质是Duration类型,表示从当前时间到目标时间的间隔,广泛应用于缓存过期、任务超时控制,互转时需校验目标时间有效性。

package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    // 1. TTL转时间:计算缓存过期时间(TTL=30分钟)
    cacheTTL := 30 * time.Minute
    cacheExpire := now.Add(cacheTTL)
    fmt.Printf("缓存过期时间:%v\n", cacheExpire.Format("2006-01-02 15:04:05"))
    // 2. 时间转TTL:计算剩余存活时间
    remainingTTL := cacheExpire.Sub(now)
    fmt.Printf("缓存剩余TTL:%v\n", remainingTTL)
    // 3. 有效性校验:避免负TTL导致逻辑异常
    invalidTime := now.Add(-10 * time.Minute) // 过去的时间
    if !invalidTime.After(now) {
        fmt.Println("目标时间已过期,TTL为负")
        return
    }
}

2.3 格式化与解析

time库的格式化与解析不支持yyyy-MM-dd等常规占位符,必须使用固定参考时间2006-01-02 15:04:05(记忆口诀:1月2日3点4分5秒6年)作为布局,布局字符串与目标时间的格式、符号、空格必须完全匹配。

常用布局字符

示例代码:

package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    // 1. 时间格式化(Time → 字符串)
    layout1 := "2006-01-02 15:04:05"
    fmt.Printf("标准格式:%v\n", now.Format(layout1)) // 2026-01-28 14:30:00
    layout2 := "2006年01月02日 15时04分05秒 时区:Z07:00"
    fmt.Printf("带时区格式:%v\n", now.Format(layout2)) // 2026年01月28日 14时30分00秒 时区:+08:00
    layout3 := "01/02/2006 3:04 PM"
    fmt.Printf("12小时制格式:%v\n", now.Format(layout3)) // 01/28/2006 2:30 PM
    // 2. 时间解析(字符串 → Time)
    strTime := "2026-03-10 14:30:00"
    // Parse默认解析为UTC时区
    utcTime, err := time.Parse(layout1, strTime)
    if err != nil {
        fmt.Printf("解析失败:%v\n", err)
        return
    }
    fmt.Printf("解析结果(UTC):%v\n", utcTime)
    // 解析为本地时区(推荐,适配业务场景)
    localTime, err := time.ParseInLocation(layout1, strTime, time.Local)
    if err != nil {
        fmt.Printf("本地时区解析失败:%v\n", err)
        return
    }
    fmt.Printf("解析结果(本地):%v\n", localTime)
}

2.4 时区管理

跨时区业务的核心是保证时间一致性,建议统一存储UTC时间,展示时根据用户所在时区转换,避免因时区混乱导致数据偏差。

跨时区转换示例

package main
import (
    "fmt"
    "time"
)
func main() {
    // 获取上海时区(Asia/Shanghai,UTC+8)
    shLoc, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        fmt.Printf("加载上海时区失败:%v\n", err)
        return
    }
    shTime := time.Now().In(shLoc)
    fmt.Printf("上海时间:%v\n", shTime.Format("2006-01-02 15:04:05"))
    // 转换为纽约时间(America/New_York,UTC-5/UTC-4,自动适配夏令时)
    nyLoc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Printf("加载纽约时区失败:%v\n", err)
        return
    }
    nyTime := shTime.In(nyLoc)
    fmt.Printf("纽约时间:%v\n", nyTime.Format("2006-01-02 15:04:05"))
}

2.5 定时器(Timer/Ticker)

定时器基于goroutine与通道实现,是并发场景下延迟执行、周期性任务的核心工具,需注意资源释放,避免内存泄漏。

单次定时器(Timer)

延迟指定时间后触发一次任务,适用于延迟通知、接口超时控制等场景,支持Stop(停止)与Reset(重置)操作。

package main
import (
    "fmt"
    "time"
)
func main() {
    // 场景1:延迟1秒执行任务
    timer1 := time.NewTimer(1 * time.Second)
    go func() {
        <-timer1.C // 阻塞等待定时器触发(通道接收时间)
        fmt.Println("1秒后执行单次任务")
    }()
    time.Sleep(1500 * time.Millisecond) // 等待任务执行完成
    // 场景2:停止定时器(避免未触发的定时器占用资源)
    timer2 := time.NewTimer(2 * time.Second)
    go func() {
        <-timer2.C
        fmt.Println("该任务不会执行")
    }()
    stopped := timer2.Stop()
    if stopped {
        fmt.Println("定时器已成功停止")
    }
    // 场景3:重置定时器(修改触发时间)
    timer3 := time.NewTimer(3 * time.Second)
    timer3.Reset(1 * time.Second) // 重置为1秒后触发
    <-timer3.C
    fmt.Println("定时器重置后,1秒触发")
}

周期性定时器(Ticker)

每隔指定间隔重复触发任务,适用于周期性日志打印、数据同步、心跳检测等场景,必须调用Stop()停止,否则内部goroutine会持续运行导致内存泄漏。

package main
import (
    "fmt"
    "time"
)
func main() {
    // 创建每秒触发一次的定时器
    ticker := time.NewTicker(1 * time.Second)
    done := make(chan struct{}) // 用于控制任务停止的信号通道
    go func() {
        for {
            select {
            case <-done:
                ticker.Stop() // 停止定时器,释放资源
                fmt.Println("周期性任务已停止")
                return
            case t := <-ticker.C:
                fmt.Printf("周期性任务执行:%v\n", t.Format("15:04:05"))
            }
        }
    }()
    // 运行5秒后停止任务
    time.Sleep(5 * time.Second)
    close(done) // 关闭通道,发送停止信号
    time.Sleep(100 * time.Millisecond) // 等待goroutine退出
}

与Context结合实现超时控制

Context与定时器结合可实现多goroutine间的超时信号同步,便于统一释放资源(如关闭数据库连接、终止网络请求),是业务开发中更规范的并发控制方式。

package main
import (
    "context"
    "fmt"
    "time"
)
// 模拟耗时接口请求(随机耗时500-1200毫秒)
func fetchData(ctx context.Context, url string) (string, error) {
    delay := time.Duration(500 + time.Now().UnixNano()%700) * time.Millisecond
    select {
    case <-time.After(delay):
        // 任务正常完成,返回结果
        return fmt.Sprintf("成功获取[%s]的核心数据", url), nil
    case <-ctx.Done():
        // 感知超时/取消信号,释放资源并返回错误
        return "", fmt.Errorf("请求[%s]失败:%v", url, ctx.Err())
    }
}
func main() {
    // 创建带1秒超时的Context(父Context为背景Context)
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // 确保函数退出时释放Context资源,避免泄漏
    // 启动goroutine执行请求任务
    resultChan := make(chan string)
    errChan := make(chan error)
    go func() {
        res, err := fetchData(ctx, "https://example.com/api")
        if err != nil {
            errChan <- err
            return
        }
        resultChan <- res
    }()
    // 等待结果或超时
    select {
    case res := <-resultChan:
        fmt.Printf("任务成功:%v\n", res)
    case err := <-errChan:
        fmt.Printf("任务失败:%v\n", err) // 超时会打印:请求失败:context deadline exceeded
    }
}

三、实战案例

3.1 接口超时控制与耗时统计

结合Timer与time.Since实现接口请求的超时控制与耗时统计,同时处理接口异常,适配生产环境中的容错与性能监控需求。

package main
import (
    "fmt"
    "time"
)
// 模拟实际接口请求,可能返回错误
func requestAPI(url string) (string, error) {
    // 模拟接口耗时(100-1200毫秒)
    delay := time.Duration(100 + time.Now().UnixNano()%1100) * time.Millisecond
    time.Sleep(delay)
    // 模拟接口异常(耗时超过800毫秒视为超时错误)
    if delay > 800*time.Millisecond {
        return "", fmt.Errorf("接口响应超时(耗时:%v)", delay)
    }
    return fmt.Sprintf("请求[%s]成功,返回数据长度:1024", url), nil
}
// 带超时控制的接口请求封装
func requestWithTimeout(url string, timeout time.Duration) (string, error, time.Duration) {
    start := time.Now()
    ch := make(chan string)
    errCh := make(chan error)
    // 启动goroutine执行接口请求
    go func() {
        res, err := requestAPI(url)
        if err != nil {
            errCh <- err
            return
        }
        ch <- res
    }()
    // 等待结果、异常或超时
    select {
    case res := <-ch:
        cost := time.Since(start)
        return res, nil, cost
    case err := <-errCh:
        cost := time.Since(start)
        return "", err, cost
    case <-time.After(timeout):
        cost := time.Since(start)
        return "", fmt.Errorf("请求超时(设定超时时间:%v)", timeout), cost
    }
}
func main() {
    url := "https://example.com/user/api"
    timeout := 800 * time.Millisecond
    res, err, cost := requestWithTimeout(url, timeout)
    if err != nil {
        fmt.Printf("请求失败:%v,总耗时:%v\n", err, cost)
        return
    }
    fmt.Printf("请求成功:%v,总耗时:%v\n", res, cost)
}

3.2 获取未来零点时间(日/月)

在定时任务、统计周期划分等场景中,常需获取明天零点、下月初零点等时间点,通过AddDate与Truncate组合实现,自动适配大小月、闰年,比手动计算更可靠。

package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    fmt.Printf("当前时间:%v\n", now.Format("2006-01-02 15:04:05"))
    // 1. 获取明天零点(当前时间+1天,再截断至天精度)
    tomorrowZero := now.AddDate(0, 0, 1).Truncate(24 * time.Hour)
    fmt.Printf("明天零点:%v\n", tomorrowZero.Format("2006-01-02 15:04:05"))
    // 2. 获取下月初零点(当前时间+1个月,构造当月1号0点)
    nextMonth := now.AddDate(0, 1, 0)
    nextMonthZero := time.Date(
        nextMonth.Year(),
        nextMonth.Month(),
        1,          // 每月1号
        0, 0, 0, 0, // 时、分、秒、纳秒归零
        now.Location(), // 保持与当前时间时区一致
    )
    fmt.Printf("下月初零点:%v\n", nextMonthZero.Format("2006-01-02 15:04:05"))
    // 扩展:获取本月最后一天23:59:59(下月初1号减1秒)
    thisMonthLastDay := nextMonthZero.Add(-1 * time.Second)
    fmt.Printf("本月最后一天(23:59:59):%v\n", thisMonthLastDay.Format("2006-01-02 15:04:05"))
}

四、避坑指南

解决方案:计算TTL前通过After(now)校验目标时间有效性,若为过去时间则直接判定为过期,避免负TTL参与业务逻辑。

五、总结与建议

到此这篇关于Go语言time库核心用法与避坑指南的文章就介绍到这了,更多相关Go语言time库用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文