【Go语言学习系列16】标准库探索(三):时间与日期

golang标准库中文手册,仅供学习 golang标准库中文手册,仅供学习,仅供学习,仅供学习 立即下载

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第16篇,当前位于第二阶段(基础巩固篇)

🚀 第二阶段:基础巩固篇
  1. 13-包管理深入理解
  2. 14-标准库探索(一):io与文件操作
  3. 15-标准库探索(二):字符串处理
  4. 16-标准库探索(三):时间与日期 👈 当前位置
  5. 17-标准库探索(四):JSON处理
  6. 18-标准库探索(五):HTTP客户端
  7. 19-标准库探索(六):HTTP服务器
  8. 20-单元测试基础
  9. 21-基准测试与性能剖析入门
  10. 22-反射机制基础
  11. 23-Go中的面向对象编程
  12. 24-函数式编程在Go中的应用
  13. 25-context包详解
  14. 26-依赖注入与控制反转
  15. 27-第二阶段项目实战:RESTful API服务

📚 查看完整Go语言学习系列导航

📖 文章导读

在本文中,您将了解:

  • Go语言time包的核心概念与设计理念
  • 时间的表示、格式化与解析方法
  • 日期计算、时区处理与时间比较
  • 定时器和计时器的使用技巧
  • 高效处理时间相关操作的最佳实践

几乎所有应用程序都需要处理时间:记录日志、计算时间差、实现定时任务、处理用户输入的日期等。本文将带您全面了解如何在Go中高效处理时间相关操作。


标准库探索(三):时间与日期

1. 时间的表示

在Go中,时间通过time.Time类型表示,这是一个包含日期和时间信息的结构体。

1.1 获取当前时间

import (
    "fmt"
    "time"
)

func main() {
    // 获取当前本地时间
    now := time.Now()
    fmt.Println("当前时间:", now)
    
    // 获取UTC时间(协调世界时)
    utcNow := time.Now().UTC()
    fmt.Println("UTC时间:", utcNow)
}

输出示例:

当前时间: 2023-05-20 15:30:45.123456789 +0800 CST
UTC时间: 2023-05-20 07:30:45.123456789 +0000 UTC

1.2 创建特定时间

// 使用time.Date创建特定时间
birthDay := time.Date(1990, time.May, 15, 12, 30, 0, 0, time.Local)
fmt.Println("生日:", birthDay)

// 参数依次为:年、月、日、时、分、秒、纳秒、时区
deadline := time.Date(2023, time.December, 31, 23, 59, 59, 0, time.UTC)
fmt.Println("截止日期:", deadline)

1.3 时间组成部分的获取

now := time.Now()

// 获取日期部分
year := now.Year()       // 年份,如2023
month := now.Month()     // 月份,如time.May
day := now.Day()         // 日,如20
weekday := now.Weekday() // 星期几,如time.Saturday

// 获取时间部分
hour := now.Hour()       // 小时,如15
minute := now.Minute()   // 分钟,如30
second := now.Second()   // 秒,如45
nano := now.Nanosecond() // 纳秒,如123456789

fmt.Printf("日期: %d年%d月%d日 %s\n", year, month, day, weekday)
fmt.Printf("时间: %02d:%02d:%02d.%09d\n", hour, minute, second, nano)

1.4 Unix时间戳

Unix时间戳表示从1970年1月1日UTC零点至今的秒数或纳秒数:

now := time.Now()

// 获取Unix时间戳(秒)
unixTime := now.Unix()
fmt.Println("Unix时间戳(秒):", unixTime)

// 获取Unix时间戳(毫秒)
unixMilli := now.UnixMilli()
fmt.Println("Unix时间戳(毫秒):", unixMilli)

// 获取Unix时间戳(纳秒)
unixNano := now.UnixNano()
fmt.Println("Unix时间戳(纳秒):", unixNano)

// 从Unix时间戳创建time.Time
timestamp := int64(1621500000)
timeFromUnix := time.Unix(timestamp, 0)
fmt.Println("从时间戳创建时间:", timeFromUnix)

2. 时间的格式化与解析

Go语言的时间格式化与其他语言有很大不同。Go使用一个特定的参考时间(2006-01-02 15:04:05)作为格式化模板,而不是像许多语言使用的YYYY-MM-DD这样的格式字符串。

2.1 时间格式化

now := time.Now()

// 基本格式化
fmt.Println(now.Format("2006-01-02 15:04:05"))       // 2023-05-20 15:30:45
fmt.Println(now.Format("2006/01/02 15:04"))          // 2023/05/20 15:30
fmt.Println(now.Format("15:04:05"))                  // 15:30:45
fmt.Println(now.Format("2006年01月02日"))              // 2023年05月20日

// 包含星期和时区
fmt.Println(now.Format("2006-01-02 15:04:05 Monday MST")) // 2023-05-20 15:30:45 Saturday CST

// 使用预定义的常量
fmt.Println(now.Format(time.RFC3339))     // 2023-05-20T15:30:45+08:00
fmt.Println(now.Format(time.RFC822))      // 20 May 23 15:30 CST
fmt.Println(now.Format(time.Kitchen))     // 3:30PM

参考时间说明:

  • 2006: 四位数年份
  • 01: 两位数月份(01-12)
  • 02: 两位数日期(01-31)
  • 15: 两位数小时(24小时制,00-23)
  • 04: 两位数分钟(00-59)
  • 05: 两位数秒(00-59)
  • Monday: 星期几的英文表示
  • MST: 时区的缩写

这些数字并不是随机选择的,它们按照美国的日期表示方式排列:月/日/年 时:分:秒,即01/02/2006 15:04:05。这个日期是2006年1月2日下午3点4分5秒,记忆方法是01/02 03:04:05PM '06。

2.2 时间解析

// 基本时间解析
timeStr := "2023-05-20 15:30:45"
t, err := time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
    fmt.Println("解析错误:", err)
} else {
    fmt.Println("解析结果:", t)
}

// 带时区的解析
timeWithZone := "2023-05-20T15:30:45+08:00"
t, err = time.Parse(time.RFC3339, timeWithZone)
if err != nil {
    fmt.Println("解析错误:", err)
} else {
    fmt.Println("解析结果:", t)
}

// 在当前位置的时区解析(默认解析为UTC)
localTime, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-05-20 15:30:45", time.Local)
if err != nil {
    fmt.Println("解析错误:", err)
} else {
    fmt.Println("本地解析结果:", localTime)
}

需要注意的是,time.Parse默认将解析后的时间视为UTC时间。如果你想将字符串解析为本地时间,应该使用time.ParseInLocation。

2.3 常见格式化与解析错误

  1. 错误的格式化占位符
// 错误:使用YYYY-MM-DD这样的格式
wrongFormat := now.Format("YYYY-MM-DD") // 这将输出"YYYY-MM-DD"而不是预期的年月日

// 正确:使用2006-01-02
correctFormat := now.Format("2006-01-02") // 输出如"2023-05-20"
  1. 解析时未考虑时区
// 解析没有明确时区信息的时间字符串
t, _ := time.Parse("2006-01-02 15:04:05", "2023-05-20 15:30:45")
fmt.Println(t) // 默认为UTC时间

// 如果要解析为本地时间,应使用ParseInLocation
t, _ = time.ParseInLocation("2006-01-02 15:04:05", "2023-05-20 15:30:45", time.Local)
fmt.Println(t) // 使用本地时区

3. 时间操作和计算

3.1 时间的加减

now := time.Now()

// 时间加法
future := now.Add(24 * time.Hour)
fmt.Println("明天这个时候:", future)

// 时间减法
past := now.Add(-72 * time.Hour)
fmt.Println("三天前:", past)

// 使用Sub计算时间差
diff := future.Sub(now)
fmt.Printf("时间差: %.2f 小时\n", diff.Hours())

// 添加特定单位的时间
tomorrow := now.AddDate(0, 0, 1)  // 加1天
nextMonth := now.AddDate(0, 1, 0) // 加1个月
nextYear := now.AddDate(1, 0, 0)  // 加1年

fmt.Println("明天:", tomorrow.Format("2006-01-02"))
fmt.Println("下个月:", nextMonth.Format("2006-01-02"))
fmt.Println("明年:", nextYear.Format("2006-01-02"))

3.2 时间的比较

t1 := time.Date(2023, time.May, 20, 15, 30, 0, 0, time.UTC)
t2 := time.Date(2023, time.May, 21, 15, 30, 0, 0, time.UTC)

// 比较两个时间
fmt.Println("t1 等于 t2:", t1.Equal(t2))        // false
fmt.Println("t1 在 t2 之前:", t1.Before(t2))   // true
fmt.Println("t1 在 t2 之后:", t1.After(t2))     // false

// 计算时间差
duration := t2.Sub(t1)
fmt.Printf("t1和t2相差: %v (%.2f 小时)\n", duration, duration.Hours())

3.3 时间的截断和舍入

now := time.Now()
fmt.Println("当前时间:", now.Format("15:04:05.000000"))

// 截断到分钟
truncated := now.Truncate(time.Minute)
fmt.Println("截断到分钟:", truncated.Format("15:04:05.000000"))

// 截断到小时
truncated = now.Truncate(time.Hour)
fmt.Println("截断到小时:", truncated.Format("15:04:05.000000"))

// 舍入到最近的分钟
rounded := now.Round(time.Minute)
fmt.Println("舍入到分钟:", rounded.Format("15:04:05.000000"))

// 截断到一天的开始(0点)
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
fmt.Println("一天的开始:", startOfDay.Format("15:04:05.000000"))

// 截断到一天的结束(23:59:59.999999999)
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location())
fmt.Println("一天的结束:", endOfDay.Format("15:04:05.000000"))

3.4 时区处理

now := time.Now()
fmt.Println("本地时间:", now)

// 获取不同时区
loc, err := time.LoadLocation("America/New_York")
if err != nil {
    fmt.Println("加载时区错误:", err)
} else {
    // 将时间转换为纽约时间
    nyTime := now.In(loc)
    fmt.Println("纽约时间:", nyTime)
}

// 常用时区
utcTime := now.UTC()
fmt.Println("UTC时间:", utcTime)

// 创建固定偏移的时区
fixedZone := time.FixedZone("UTC+8", 8*60*60)
beijingTime := now.In(fixedZone)
fmt.Println("北京时间(固定时区):", beijingTime)

// 获取系统本地时区
localZone, offset := now.Zone()
fmt.Printf("本地时区: %s, 偏移量: %d秒\n", localZone, offset)

4. 定时器与计时器

Go语言的time包提供了定时器和计时器功能,用于实现延迟执行、重复执行和性能测量。

4.1 一次性定时器 (Timer)

// 创建一个2秒后触发的定时器
timer := time.NewTimer(2 * time.Second)
fmt.Println("定时器开始:", time.Now().Format("15:04:05"))

// 等待定时器触发
<-timer.C
fmt.Println("定时器触发:", time.Now().Format("15:04:05"))

// 使用time.After简化一次性等待
fmt.Println("开始等待:", time.Now().Format("15:04:05"))
<-time.After(1 * time.Second)
fmt.Println("等待结束:", time.Now().Format("15:04:05"))

// 取消定时器(如果需要)
timer = time.NewTimer(10 * time.Second)
cancelled := timer.Stop()
fmt.Println("定时器被取消:", cancelled)

4.2 周期性定时器 (Ticker)

// 创建一个每隔1秒触发一次的周期定时器
ticker := time.NewTicker(1 * time.Second)
fmt.Println("周期计时器开始:", time.Now().Format("15:04:05"))

// 使用for循环处理周期事件
count := 0
for {
    <-ticker.C
    count++
    fmt.Printf("第%d次触发: %s\n", count, time.Now().Format("15:04:05"))
    
    if count >= 5 {
        ticker.Stop() // 停止周期计时器
        break
    }
}
fmt.Println("周期计时器结束")

4.3 性能测量与计时

// 使用time.Since测量时间
start := time.Now()

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

// 计算经过的时间
elapsed := time.Since(start)
fmt.Printf("操作耗时: %v (%.2f 毫秒)\n", elapsed, float64(elapsed)/float64(time.Millisecond))

// 也可以直接比较两个时间
start = time.Now()
time.Sleep(200 * time.Millisecond)
end := time.Now()
duration := end.Sub(start)
fmt.Printf("操作耗时: %v\n", duration)

5. 实用示例

下面通过一些实际场景中的示例,展示如何使用time包解决常见的时间处理问题。

5.1 日期范围遍历

需要按天遍历一个日期范围是常见的需求,例如生成报表或计算工作日:

package main

import (
    "fmt"
    "time"
)

// 遍历日期范围
func iterateDateRange(start, end time.Time) []time.Time {
    var dates []time.Time
    
    // 确保start不晚于end
    if start.After(end) {
        start, end = end, start
    }
    
    // 将时间调整到每天的开始(0点)
    current := time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
    endDay := time.Date(end.Year(), end.Month(), end.Day(), 0, 0, 0, 0, end.Location())
    
    // 遍历每一天
    for !current.After(endDay) {
        dates = append(dates, current)
        current = current.AddDate(0, 0, 1) // 加一天
    }
    
    return dates
}

// 统计工作日(周一至周五)
func countWorkDays(start, end time.Time) int {
    dates := iterateDateRange(start, end)
    workDays := 0
    
    for _, date := range dates {
        weekday := date.Weekday()
        if weekday != time.Saturday && weekday != time.Sunday {
            workDays++
        }
    }
    
    return workDays
}

func main() {
    // 定义日期范围
    start := time.Date(2023, time.May, 1, 0, 0, 0, 0, time.Local)
    end := time.Date(2023, time.May, 15, 0, 0, 0, 0, time.Local)
    
    // 遍历日期
    dates := iterateDateRange(start, end)
    fmt.Println("日期范围内的天数:", len(dates))
    
    // 打印每一天
    for _, date := range dates {
        fmt.Printf("%s (%s)\n", date.Format("2006-01-02"), date.Weekday())
    }
    
    // 计算工作日
    workDays := countWorkDays(start, end)
    fmt.Printf("从%s到%s共有%d个工作日\n", 
        start.Format("2006-01-02"), 
        end.Format("2006-01-02"), 
        workDays)
}

5.2 实现限流器

使用time包实现一个简单的令牌桶限流器:

package main

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

// TokenBucket 令牌桶限流器
type TokenBucket struct {
    rate       float64    // 令牌生成速率(个/秒)
    capacity   float64    // 桶容量
    tokens     float64    // 当前令牌数
    lastRefill time.Time  // 上次令牌补充时间
    mu         sync.Mutex // 并发安全锁
}

// NewTokenBucket 创建新的令牌桶限流器
func NewTokenBucket(rate, capacity float64) *TokenBucket {
    return &TokenBucket{
        rate:       rate,
        capacity:   capacity,
        tokens:     capacity,
        lastRefill: time.Now(),
    }
}

// refill 根据经过时间补充令牌
func (tb *TokenBucket) refill() {
    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.lastRefill = now
    
    // 计算新添加的令牌
    newTokens := elapsed * tb.rate
    
    // 更新令牌数,但不超过容量
    tb.tokens += newTokens
    if tb.tokens > tb.capacity {
        tb.tokens = tb.capacity
    }
}

// Allow 检查是否允许请求通过
func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()
    
    tb.refill()
    
    if tb.tokens >= 1 {
        tb.tokens -= 1
        return true
    }
    
    return false
}

func main() {
    // 创建限流器:每秒2个请求
    limiter := NewTokenBucket(2, 5)
    
    // 模拟请求
    for i := 1; i <= 10; i++ {
        if limiter.Allow() {
            fmt.Printf("[%s] 请求 %d: 通过\n", time.Now().Format("15:04:05"), i)
        } else {
            fmt.Printf("[%s] 请求 %d: 被限流\n", time.Now().Format("15:04:05"), i)
        }
        
        // 控制请求速率
        if i%3 == 0 {
            time.Sleep(1 * time.Second)
        } else {
            time.Sleep(200 * time.Millisecond)
        }
    }
}

5.3 日志轮转时间计算

实现基于时间的日志轮转功能,例如每天0点创建新的日志文件:

package main

import (
    "fmt"
    "time"
)

// 计算下一个轮转时间
func nextRotationTime(now time.Time, interval string) time.Time {
    var next time.Time
    
    switch interval {
    case "hourly":
        // 下一个整点
        next = time.Date(
            now.Year(), now.Month(), now.Day(),
            now.Hour()+1, 0, 0, 0,
            now.Location(),
        )
        
    case "daily":
        // 明天0点
        next = time.Date(
            now.Year(), now.Month(), now.Day()+1,
            0, 0, 0, 0,
            now.Location(),
        )
        
    case "weekly":
        // 计算到下周一的天数
        daysUntilMonday := int(time.Monday - now.Weekday())
        if daysUntilMonday <= 0 {
            daysUntilMonday += 7
        }
        
        next = time.Date(
            now.Year(), now.Month(), now.Day()+daysUntilMonday,
            0, 0, 0, 0,
            now.Location(),
        )
        
    case "monthly":
        // 下个月1号
        next = time.Date(
            now.Year(), now.Month()+1, 1,
            0, 0, 0, 0,
            now.Location(),
        )
    }
    
    return next
}

func main() {
    now := time.Now()
    fmt.Println("当前时间:", now.Format("2006-01-02 15:04:05"))
    
    // 计算不同间隔的下次轮转时间
    hourlyNext := nextRotationTime(now, "hourly")
    dailyNext := nextRotationTime(now, "daily")
    weeklyNext := nextRotationTime(now, "weekly")
    monthlyNext := nextRotationTime(now, "monthly")
    
    fmt.Println("下次小时轮转:", hourlyNext.Format("2006-01-02 15:04:05"))
    fmt.Println("下次每日轮转:", dailyNext.Format("2006-01-02 15:04:05"))
    fmt.Println("下次每周轮转:", weeklyNext.Format("2006-01-02 15:04:05"))
    fmt.Println("下次每月轮转:", monthlyNext.Format("2006-01-02 15:04:05"))
    
    // 计算等待时间
    waitDuration := dailyNext.Sub(now)
    fmt.Printf("距离下次每日轮转还有: %v (%.2f 小时)\n", 
        waitDuration, 
        waitDuration.Hours())
}

6. 时间格式的本地化

Go的time包本身不提供完整的日期时间本地化功能,但可以通过一些技巧实现基本的本地化:

package main

import (
    "fmt"
    "time"
)

// 简单的中文月份映射
var chineseMonths = map[time.Month]string{
    time.January:   "一月",
    time.February:  "二月",
    time.March:     "三月",
    time.April:     "四月",
    time.May:       "五月",
    time.June:      "六月",
    time.July:      "七月",
    time.August:    "八月",
    time.September: "九月",
    time.October:   "十月",
    time.November:  "十一月",
    time.December:  "十二月",
}

// 中文星期几映射
var chineseWeekdays = map[time.Weekday]string{
    time.Sunday:    "星期日",
    time.Monday:    "星期一",
    time.Tuesday:   "星期二",
    time.Wednesday: "星期三",
    time.Thursday:  "星期四",
    time.Friday:    "星期五",
    time.Saturday:  "星期六",
}

// 格式化为中文日期
func formatChineseDate(t time.Time) string {
    year := t.Year()
    month := chineseMonths[t.Month()]
    day := t.Day()
    weekday := chineseWeekdays[t.Weekday()]
    
    return fmt.Sprintf("%d年%s%d日 %s", year, month, day, weekday)
}

// 格式化为中文时间
func formatChineseDateTime(t time.Time) string {
    date := formatChineseDate(t)
    
    hour := t.Hour()
    minute := t.Minute()
    second := t.Second()
    
    return fmt.Sprintf("%s %02d时%02d分%02d秒", date, hour, minute, second)
}

func main() {
    now := time.Now()
    
    fmt.Println("标准格式:", now.Format("2006-01-02 15:04:05"))
    fmt.Println("中文日期:", formatChineseDate(now))
    fmt.Println("中文日期时间:", formatChineseDateTime(now))
    
    // 显示相对时间
    pastTime := now.Add(-36 * time.Hour)
    fmt.Println("过去时间:", formatRelativeTime(pastTime, now))
    
    futureTime := now.Add(15 * time.Minute)
    fmt.Println("未来时间:", formatRelativeTime(futureTime, now))
}

// 格式化为相对时间描述(如"3小时前","2天后"等)
func formatRelativeTime(t time.Time, reference time.Time) string {
    diff := reference.Sub(t)
    isFuture := diff < 0
    if isFuture {
        diff = -diff
    }
    
    var result string
    switch {
    case diff < time.Minute:
        result = "刚刚"
    case diff < time.Hour:
        minutes := int(diff.Minutes())
        result = fmt.Sprintf("%d分钟", minutes)
    case diff < 24*time.Hour:
        hours := int(diff.Hours())
        result = fmt.Sprintf("%d小时", hours)
    case diff < 30*24*time.Hour:
        days := int(diff.Hours() / 24)
        result = fmt.Sprintf("%d天", days)
    case diff < 365*24*time.Hour:
        months := int(diff.Hours() / 24 / 30)
        result = fmt.Sprintf("%d个月", months)
    default:
        years := int(diff.Hours() / 24 / 365)
        result = fmt.Sprintf("%d年", years)
    }
    
    if isFuture {
        return result + "后"
    }
    return result + "前"
}

对于更复杂的本地化需求,可以考虑使用第三方库,如golang.org/x/text。

7. 性能考虑和最佳实践

7.1 时间操作的性能优化

  1. 重用时间对象:避免频繁创建新的time.Time对象
// 低效:每次循环都创建新的time.Time
for i := 0; i < 1000; i++ {
    now := time.Now()
    // 使用now...
}

// 更高效:只在需要时更新时间
now := time.Now()
for i := 0; i < 1000; i++ {
    if i % 100 == 0 {  // 每100次迭代才更新一次时间
        now = time.Now()
    }
    // 使用now...
}
  1. 避免不必要的格式化与解析:这些操作相对较慢
// 低效:重复格式化相同的时间
startTime := time.Now()
for i := 0; i < 1000; i++ {
    timeStr := startTime.Format("2006-01-02")
    // 使用timeStr...
}

// 更高效:只格式化一次
startTime := time.Now()
timeStr := startTime.Format("2006-01-02")
for i := 0; i < 1000; i++ {
    // 使用timeStr...
}
  1. 使用适当的时间比较方法:优先使用Equal、Before、After而非比较时间戳
t1 := time.Now()
t2 := t1.Add(time.Second)

// 低效且不可靠(可能有精度问题)
if t1.Unix() == t2.Unix() {
    // ...
}

// 更好的方式
if t1.Equal(t2) {
    // ...
}

7.2 时间处理的最佳实践

  1. 始终考虑时区:尤其在处理用户输入和展示时
// 存储时使用UTC
storedTime := time.Now().UTC()

// 展示给用户时转换为用户时区
userLocation, _ := time.LoadLocation("Asia/Shanghai")
userTime := storedTime.In(userLocation)
  1. 避免基于整数的日期计算:使用AddDate而非手动计算天数
// 错误:手动计算30天后
thirtyDaysLater := time.Now().Add(30 * 24 * time.Hour) // 这并不总是准确的(考虑闰年、夏令时等)

// 正确:使用AddDate
thirtyDaysLater := time.Now().AddDate(0, 0, 30)
  1. 谨慎处理月末日期
// 注意这种行为
jan31 := time.Date(2023, time.January, 31, 0, 0, 0, 0, time.UTC)
// 2月没有31日,所以这会变成3月3日
march3 := jan31.AddDate(0, 1, 0)
fmt.Println(march3.Format("2006-01-02")) // 2023-03-03
  1. 测试时使用固定时间:避免因时间变化导致测试不稳定
// 测试代码中使用固定时间
fixedTime := time.Date(2023, time.January, 15, 12, 0, 0, 0, time.UTC)

// 可以通过依赖注入或mock替换time.Now函数
  1. 正确处理超时和定时器
// 记得停止不再需要的定时器
timer := time.NewTimer(5 * time.Second)
defer timer.Stop() // 避免资源泄露

// 对于time.After,无法取消,所以在可能提前返回的情况下谨慎使用

8. 总结

通过本文,我们深入探讨了Go语言标准库中的时间与日期处理功能。从基本的时间表示到高级的定时器应用,Go的time包提供了丰富而强大的API来满足各种时间处理需求。

掌握这些知识,您将能够:

  • 高效处理各种时间相关操作
  • 正确处理时区和时间格式化
  • 实现定时任务和限流功能
  • 避免常见的时间处理陷阱

在下一篇文章中,我们将探索Go标准库中的JSON处理功能,包括encoding/json包的使用方法和最佳实践。


👨‍💻 关于作者与Gopher部落

"Gopher部落"专注于Go语言技术分享,提供从入门到精通的完整学习路线。

🌟 为什么关注我们?

  1. 系统化学习路径:从入门基础到高级特性,循序渐进掌握Go开发
  2. 实战驱动教学:理论结合实践,每篇文章都有可操作的代码示例
  3. 持续更新内容:定期分享最新Go生态技术动态与大厂实践经验
  4. 专业技术社区:加入我们的技术交流群,与众多Go开发者共同成长

📱 关注方式

  1. 微信公众号:搜索 “Gopher部落” 或 “GopherTribe”
  2. 优快云专栏:点击页面右上角"关注"按钮

💡 读者福利

关注公众号回复 “时间处理” 即可获取:

  • 完整示例代码
  • 时间处理性能优化指南
  • 定时任务最佳实践清单

期待与您在Go语言的学习旅程中共同成长!

AT32F403A ADC通道管理与转换模式实战解析 本文深入解析AT32F403A微控制器的ADC模块,重点阐述其通道管理机制与四种转换模式(序列模式、抢占自动转换模式、反复模式、分割模式)的实战应用。通过详细的配置步骤与代码示例,帮助开发者高效实现模拟数字转换,优化数据采集性能与系统响应。 阅读详情

相关推荐

S32K144之芯片锁死解锁——关闭debug端口

S32K144之芯片锁死及解锁 经常捣鼓单片机就会发现,时常会因为某些操作不当导致芯片锁死情况的发生,通过Jlink我们可以读到芯片的ID,但是无法进行Debug,这很是让人苦恼。 基于S32K144的芯片锁死,一般有如下几种可能: 1、时钟配置异常,过高的配置时钟频率以及不配置时钟,都有可能造成芯片保护锁死,我就曾经因为不注意把时钟配置代码注释掉,导致芯片锁死。 2、我是微软雅黑烧写断电,如果在...

weixin_41935674的博客 1万+

Golang 标准库 time包日常用法详解

时间日期是编程中经常会用到的一种数据类型,在Golang中提供time包来处理时间相关的操作。本教程将详细介绍time包中常用函数和方法的示例

多年后端开发经验,专注于Golang和PHP语言的开发 713

ROS相机内参标定详细步骤指南

相机标定的目的是为了确定相机内部参数和外部参数,以便修正图像并将图像坐标映射到世界坐标,或者从世界坐标映射到图像坐标。这个过程是计算机视觉和三维感知任务的关键步骤之一,它有以下主要目的: 1. **减小畸变**:相机镜头和传感器可能引入径向和切向畸变,使得图像中的直线变得弯曲或者物体的形状不准确。通过标定相机,可以矫正这些畸变,使图像更准确。

m0_56661101的博客 1万+

Go 字符串时间等相互转化以及时间加减等操作

1.获取当前时间的第二天零点时间,以及获取当前的23:59:59 的时间戳和date类型: package main import ( "fmt" "time" ) func main(){ timeStr := time.Now().Format("2006-01-02") fmt.Println(timeStr) //使用Parse 默认获取为UTC时区 需要获取本地时区 所以使用ParseInLocation t2, _ := time.ParseInLocation("2006-

大灰狼的专栏 3690

医疗大模型实战——从零构建MedicalGPT全流程解析

本文详细解析了从零构建医疗大模型MedicalGPT的全流程实战。基于Qwen-7B模型与约195万条中文医疗数据,完整复现了增量预训练、监督微调与偏好对齐三大核心环节,并分享了如何在有限硬件资源下实现高效训练与部署,为开发者提供了可复现的医疗AI项目实战指南。

weixin_29193259的博客 555

西门子1500 PLC与巴鲁夫RFID实战:从硬件接线到数据读写的完整流程

本文详细介绍了西门子S7-1500 PLC与巴鲁夫RFID系统集成的完整实战流程。内容涵盖从硬件选型、接线、PROFINET网络配置,到在TIA Portal中安装GSD文件、组态设备、分配地址,再到使用标准功能块(如FB31)进行程序架构设计、实现数据读写以及错误诊断。文章重点分享了硬件连接要点、软件配置步骤、数据块管理技巧以及调试中常见问题的解决方案,为工业自动化工程师实施RFID项目提供了清晰的指导。

weixin_29161785的博客 386

Go语言time库,时间和日期相关的操作方法

Go语言time库用于处理时间、日期和时区的核心库。在实际开发中,常常需要与时间打交道,例如记录日志、处理时间差、计算时间间隔等等。

汉阳Hann's Home 6226

第一弹:llama.cpp编译

1.编译llama.cpp命令行(电脑版本);2.交叉编译安卓命令行版本。

joedan0104的专栏 1万+

日期和时间类【Date】【Calendar日历类】【LocalDate】Date-Time API详解

本文先介绍时间相关的基础知识:GMT - 格林尼治标准时间(Greenwich Mean Time)、UTC - 协调世界时(Universal Time Coordinated),以及时区等常识。然后重点介绍了:日期和时间类【Date】【Calendar日历类】【LocalDate】,并对Date-Time API作了详细介绍。

weixin_42369079的博客 1834

UR5双臂Gazebo仿真(Python)

近来在Gazebo上搭了一个ur5双臂仿真平台。 差不多就是以下这样。 使用rostopic list查看一下话题,如下 显然,采用/left_arm_controller/follow_joint_trajectory/goal和/right_arm_controller/follow_joint_trajectory/goal两个话题来控制双臂的运动。 现在的问题是,如何保证双臂运动的一致性,即如何能保证双臂能同时接受到以上两个消息。 思路一:设置/left_arm_controlle

qq_42823342的博客 2233

【Altium Designer】Off grid pin解决方法

​今天我画完原理图编译时,出现了太多的黄色(出现warning警告提示)Off grid pin警告,这个警告并不是原理图电气连接出问题了,而是元器件或器件PIN脚没有和栅格对齐造成的原因。再编译还是出现这个警告。 最后经过不断翻阅网上经验和实践,才最终解决,本经验详细记录了解决该警告的有效方法。 解决方案:以下是问题解决步骤... ​

夜半少年的博客 7550

golang time 时间 加减法

time包中的Add和Sub的用法,Add用于计算某个时间之前和之后的时间点,Sub用于计算两个时间差 实例如下: package main import ( "fmt" "strings" "time" ) func main() { // Add 时间相加 now := time.Now() // ParseDuration parses a duration string. // A duration string is a possibly signed sequence o

whatday的专栏 1万+

ElectrolyteGPT:首个电池电解液逆向设计的生成式 AI 系统 —— 算法原理与复现解析

数据驱动的重要性:1100 万→100 万的精心筛选,比单纯增大模型更有效领域适配:fLine 表示法、正则分词、化学感知的数据增强(SMILES randomization)都是关键小模型的力量:6M 参数在特定化学空间的表现,远超通用大模型在相同任务上的零样本能力条件生成的极限:10 维条件导致模型崩溃,提示我们需要更好的条件编码策略(如 diffusion guidance、RLHF-style 偏好优化)未来方向配方实验验证。

ASKCOS博客 123

【Go语言学习系列14】标准库探索(一):io与文件操作

深入讲解Go语言io包与文件操作,从Reader/Writer接口到实际文件处理案例,全面掌握Go的IO处理机制

GopherTribe的博客 882

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

【Go语言学习系列15】标准库探索(二):字符串处理

深入讲解Go语言io包与文件操作,从Reader/本文深入探讨Go语言标准库中的字符串处理功能,包括strings、strconv及unicode包的使用方法。通过实例讲解常见字符串操作,帮助读者全面掌握Go中的文本处理能力,提高字符串处理效率和代码质量。Writer接口到实际文件处理案例,全面掌握Go的IO处理机制

GopherTribe的博客 1088

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【Go语言学习系列17】标准库探索(四):JSON处理

本文深入介绍Go语言标准库中的JSON处理功能,包括基本序列化与反序列化、自定义编码/解码、处理复杂数据结构等高级特性。通过丰富的实例讲解如何高效处理JSON数据,帮助开发者在Go项目中灵活应用JSON技术。

GopherTribe的博客 1114

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【Go语言学习系列00】Go语言学习系列导航

本文是Go语言学习系列的导航文档,提供了从入门到精通的学习路径,包含各阶段文章链接和内容概述,帮助读者系统化学习Go语言。

GopherTribe的博客 1575

【Go语言学习系列18】标准库探索(五):HTTP客户端

本文详细介绍Go语言标准库中HTTP客户端的使用,包括基本请求方法、自定义客户端配置、处理响应、文件上传下载和性能优化。通过丰富的实例讲解如何构建高效可靠的HTTP客户端,帮助开发者在Go项目中轻松实现网络通信需求。

GopherTribe的博客 1072

【Go语言学习系列22】反射机制基础

深入解析Go语言反射机制,从基础概念到高级应用,全面掌握reflect包的使用技巧与性能注意事项

GopherTribe的博客 978

【Go语言学习系列19】标准库探索(六):HTTP服务器

全面解析Go语言HTTP服务器开发,从基础路由到高级中间件,掌握高性能Web服务构建技巧

GopherTribe的博客 1158

golang常用标准库

context.Background函数。

qq_24428851的博客 987

Carbon for Golang:让Go时间处理变得简单高效的终极扩展库

Carbon for Golang 是一个基于 PHP Carbon 库开发的 Go 语言时间扩展库,它为开发者提供了简洁、高效的时间处理解决方案。无论是日期解析、比较、格式化还是时区转换,Carbon 都能让这些操作变得简单直观,极大地提升开发效率。 ## 🚀 为什么选择 Carbon for Golang? 在 Go 语言开发中,标准库的 time 包虽然功能强大,但在处理复杂日期时间操

gitblog_00331的博客 799

Golang标准库

Go标准库的基础使用说明,借鉴GitHub相关文档,可以当做知识字典使用

码刀攻城 2362

【GoLang基础】Go常用时间和日期函数详解

Go 语言标准库中的time包提供了丰富的时间和日期处理函数和类型。

玦尘的博客 1046

【Go语言学习系列20】单元测试基础

掌握Go语言单元测试技巧,从基础语法到高级模式,全面提升测试质量与代码可靠性

GopherTribe的博客 848

【Go语言学习系列13】包管理深入理解

深入解析Go Modules的工作原理,掌握go.mod与go.sum文件管理,学习私有模块配置和依赖版本控制的最佳实践

GopherTribe的博客 1143
上一篇: 【Go语言学习系列14】标准库探索(一):io与文件操作
下一篇: 【Go语言学习系列17】标准库探索(四):JSON处理
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值