Go 语言中的日期时间处理
标准库
标准库常用函数
最顶层的工具函数: time 包提供
| 内置函数 | 说明 |
|---|---|
| time.Tick | 返回一个定时器管道 |
| time.After | 返回一个超时器管道 |
| time.Sleep | 让程序挂起指定时间 |
| time.Now | 返回当前本地时间 |
| time.Parse | 根据格式解析时间字符串 |
| time.ParseInLocation | 根据格式和时区解析时间字符串 |
| time.Unix | 从Unix时间戳创建时间 |
| time.UnixMilli | 从毫秒级Unix时间戳创建时间 |
| time.UnixMicro | 从微秒级Unix时间戳创建时间 |
go
package main
import (
"fmt"
"time"
)
func main() {
// time.Now() - 获取当前时间
now := time.Now()
fmt.Printf("当前时间: %v\n", now)
// time.Sleep() - 暂停执行
fmt.Println("开始睡眠...")
time.Sleep(1 * time.Second)
fmt.Println("睡眠结束")
// time.Tick() - 创建一个定时器,每500毫秒触发一次
fmt.Println("\nTick示例 - 每500毫秒触发一次,共3次:")
ticker := time.NewTicker(500 * time.Millisecond)
counter := 0
for range ticker.C {
counter++
fmt.Printf("Tick %d: %v\n", counter, time.Now())
if counter >= 3 {
ticker.Stop()
break
}
}
// time.After() - 创建一个在2秒后触发的定时器
fmt.Println("\nAfter示例 - 2秒后触发:")
select {
case t := <-time.After(2 * time.Second):
fmt.Printf("After触发: %v\n", t)
}
// time.Parse() - 解析时间字符串
timeStr := "2023-07-15 14:30:45"
layout := "2006-01-02 15:04:05"
parsedTime, err := time.Parse(layout, timeStr)
if err != nil {
fmt.Printf("解析失败: %v\n", err)
} else {
fmt.Printf("\nParse示例:\n原始字符串: %s\n解析后时间: %v\n", timeStr, parsedTime)
}
// time.ParseInLocation() - 在指定时区解析时间
location, _ := time.LoadLocation("Asia/Shanghai")
parsedTimeInLocation, err := time.ParseInLocation(layout, timeStr, location)
if err != nil {
fmt.Printf("解析失败: %v\n", err)
} else {
fmt.Printf("\nParseInLocation示例:\n原始字符串: %s\n解析后时间(上海时区): %v\n", timeStr, parsedTimeInLocation)
}
// Unix时间戳相关函数
unixTime := time.Date(2023, 7, 15, 14, 30, 45, 0, time.UTC)
fmt.Printf("\nUnix时间戳示例:\n原始时间: %v\n", unixTime)
// time.Unix() - 从秒级时间戳创建
seconds := unixTime.Unix()
timeFromSeconds := time.Unix(seconds, 0)
fmt.Printf("秒级时间戳: %d\n从秒级时间戳创建: %v\n", seconds, timeFromSeconds)
// time.UnixMilli() - 从毫秒级时间戳创建
milliseconds := unixTime.UnixMilli()
timeFromMilli := time.UnixMilli(milliseconds)
fmt.Printf("毫秒级时间戳: %d\n从毫秒级时间戳创建: %v\n", milliseconds, timeFromMilli)
// time.UnixMicro() - 从微秒级时间戳创建
microseconds := unixTime.UnixMicro()
timeFromMicro := time.UnixMicro(microseconds)
fmt.Printf("微秒级时间戳: %d\n从微秒级时间戳创建: %v\n", microseconds, timeFromMicro)
}Duration 类型相关函数
| 内置函数 | 说明 |
|---|---|
| time.Duration.Hours | 转换为小时数 |
| time.Duration.Microseconds | 转换为微秒数 |
| time.Duration.Milliseconds | 转换为毫秒数 |
| time.Duration.Minutes | 转换为分钟数 |
| time.Duration.Round | 四舍五入到最近的持续时间 |
| time.Duration.Seconds | 转换为秒数 |
| time.Duration.String | 返回字符串表示 |
go
package main
import (
"fmt"
"math"
"time"
)
func main() {
// 创建一个持续时间
duration := 2*time.Hour + 30*time.Minute + 15*time.Second + 500*time.Millisecond
fmt.Printf("原始持续时间: %v\n", duration)
// 转换为不同的时间单位
fmt.Printf("\n时间单位转换:\n")
fmt.Printf("小时: %.2f\n", duration.Hours())
fmt.Printf("分钟: %.2f\n", duration.Minutes())
fmt.Printf("秒: %.2f\n", duration.Seconds())
fmt.Printf("毫秒: %d\n", duration.Milliseconds())
fmt.Printf("微秒: %d\n", duration.Microseconds())
// 持续时间的字符串表示
fmt.Printf("\n字符串表示:\n")
fmt.Printf("String(): %s\n", duration.String())
// 四舍五入
fmt.Printf("\n四舍五入:\n")
fmt.Printf("原始值: %v\n", duration)
fmt.Printf("四舍五入到最近的小时: %v\n", duration.Round(time.Hour))
fmt.Printf("四舍五入到最近的30分钟: %v\n", duration.Round(30*time.Minute))
fmt.Printf("四舍五入到最近的15分钟: %v\n", duration.Round(15*time.Minute))
fmt.Printf("四舍五入到最近的10秒: %v\n", duration.Round(10*time.Second))
// 计算时间差
fmt.Printf("\n计算时间差示例:\n")
start := time.Now()
// 模拟一些操作
time.Sleep(1250 * time.Millisecond)
end := time.Now()
elapsed := end.Sub(start)
fmt.Printf("开始时间: %v\n", start)
fmt.Printf("结束时间: %v\n", end)
fmt.Printf("经过时间: %v\n", elapsed)
fmt.Printf("经过时间(秒): %.2f\n", elapsed.Seconds())
fmt.Printf("经过时间(毫秒): %d\n", elapsed.Milliseconds())
// 四舍五入经过的时间
fmt.Printf("四舍五入到最近的秒: %v\n", elapsed.Round(time.Second))
// 负持续时间示例
negativeDuration := -45 * time.Second
fmt.Printf("\n负持续时间示例:\n")
fmt.Printf("原始值: %v\n", negativeDuration)
fmt.Printf("绝对值: %v\n", negativeDuration.Abs())
fmt.Printf("小时(负): %.2f\n", negativeDuration.Hours())
}Location 类型相关函数
| 内置函数 | 说明 |
|---|---|
| time.Location.String | 返回时区名称 |
| time.FixedZone | 创建固定偏移量的时区 |
| time.LoadLocation | 加载指定名称的时区 |
| time.LoadLocationFromTZData | 从TZData加载时区 |
go
package main
import (
"fmt"
"time"
)
func main() {
// time.LoadLocation() - 加载系统时区
fmt.Println("=== 时区加载示例 ===")
// 加载上海时区
shanghai, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Printf("加载上海时区失败: %v\n", err)
} else {
fmt.Printf("成功加载时区: %s\n", shanghai)
// 使用该时区创建时间
shanghaiTime := time.Date(2023, 7, 15, 14, 30, 0, 0, shanghai)
fmt.Printf("上海时间: %v\n", shanghaiTime)
// 转换为UTC时间
utcTime := shanghaiTime.UTC()
fmt.Printf("对应的UTC时间: %v\n", utcTime)
// 获取时区名称和偏移
zonename, offset := shanghaiTime.Zone()
fmt.Printf("时区名称: %s, 偏移(秒): %d (即%d小时)\n", zonename, offset, offset/3600)
}
// time.FixedZone() - 创建固定偏移量的时区
fmt.Println("\n=== 固定时区示例 ===")
// 创建UTC+8时区 (东八区,如北京时间)
east8 := time.FixedZone("UTC+8", 8*60*60)
// 创建UTC-5时区 (西五区,如美国东部时间)
west5 := time.FixedZone("UTC-5", -5*60*60)
now := time.Now()
// 在不同时区显示当前时间
fmt.Printf("本地时间: %s\n", now.Format(time.RFC3339))
fmt.Printf("UTC+8时间: %s\n", now.In(east8).Format(time.RFC3339))
fmt.Printf("UTC-5时间: %s\n", now.In(west5).Format(time.RFC3339))
// 比较两个时区的时间
fmt.Printf("\n固定时区比较:\n")
timeA := time.Date(2023, 7, 15, 14, 30, 0, 0, east8)
timeB := time.Date(2023, 7, 15, 14, 30, 0, 0, west5)
fmt.Printf("UTC+8: %v\n", timeA)
fmt.Printf("UTC-5: %v\n", timeB)
fmt.Printf("两个时间的差值: %v\n", timeB.Sub(timeA))
// time.Local - 系统本地时区
fmt.Println("\n=== 本地时区示例 ===")
localTime := time.Now()
fmt.Printf("本地时区: %s\n", localTime.Location())
fmt.Printf("本地时间: %v\n", localTime)
// 转换为UTC
utcTime := localTime.UTC()
fmt.Printf("UTC时区: %s\n", utcTime.Location())
fmt.Printf("UTC时间: %v\n", utcTime)
// 转换回本地
backToLocal := utcTime.Local()
fmt.Printf("转换回本地时间: %v\n", backToLocal)
// 检查是否为DST(夏令时)
fmt.Printf("\n夏令时检查:\n")
fmt.Printf("本地时间是否在夏令时: %v\n", localTime.IsDST())
// 计算时区边界
fmt.Printf("\n时区边界示例 (以上海时间2023-03-12为例):\n")
mar12 := time.Date(2023, 3, 12, 10, 0, 0, 0, shanghai)
fmt.Printf("时间: %v\n", mar12)
if start, end := mar12.ZoneBounds(); !start.IsZero() && !end.IsZero() {
fmt.Printf("时区开始时间: %v\n", start)
fmt.Printf("时区结束时间: %v\n", end)
} else {
fmt.Printf("无法获取时区边界\n")
}
}Tick/Timer
- Tick: 定时器(周期性触发)
- Timer: 超时器(单次触发)
| 内置函数 | 说明 |
|---|---|
| time.NewTicker | 创建一个新的Ticker |
| time.Ticker.Reset | 重置Ticker的周期 |
| time.Ticker.Stop | 停止Ticker,释放资源 |
| time.NewTimer | 创建一个新的Timer |
| time.AfterFunc | 在指定时间后执行函数 |
| time.Timer.Reset | 重置Timer的触发时间 |
| time.Timer.Stop | 停止Timer |
go
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个2秒后触发的定时器
timer := time.NewTimer(2 * time.Second)
// 等待定时器触发
<-timer.C
fmt.Println("定时器触发,已经过了2秒")
// 重置定时器,再等待3秒
timer.Reset(3 * time.Second)
<-timer.C
fmt.Println("定时器再次触发,已经过了3秒")
// 使用AfterFunc,在2秒后执行函数
time.AfterFunc(2*time.Second, func() {
fmt.Println("2秒后执行的函数")
})
// 防止主函数提前退出
time.Sleep(3 * time.Second)
}go
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个每秒触发一次的Ticker
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
done := make(chan bool)
// 启动一个goroutine处理ticker事件
go func() {
for {
select {
case t := <-ticker.C:
fmt.Println("Tick at", t)
case <-done:
return
}
}
}()
// 5秒后停止ticker
time.Sleep(5 * time.Second)
done <- true
fmt.Println("Ticker stopped")
}Time
Time 类型是时间的基本数据类型, 它包含了时间的年月日时分秒等信息
| 内置函数 | 说明 |
|---|---|
| time.Time.Add | 添加一个时间段 |
| time.Time.AddDate | 添加年、月、日 |
| time.Time.After | 检查时间是否在指定时间之后 |
| time.Time.Before | 检查时间是否在指定时间之前 |
| time.Time.Location | 获取时间的时区 |
| time.Time.Zone | 获取时区名称和偏移量 |
| time.Time.Year | 获取年份 |
| time.Time.Month | 获取月份 |
| time.Time.Date | 获取年月日 |
| time.Time.Day | 获取日期(月中的第几天) |
| time.Time.Clock | 获取时分秒 |
| time.Time.Hour | 获取小时(24小时制) |
| time.Time.Minute | 获取分钟 |
| time.Time.Second | 获取秒 |
| time.Time.Compare | 比较两个时间 |
| time.Time.Equal | 检查两个时间是否相等 |
| time.Time.Format | 格式化时间为字符串 |
| time.Time.String | 返回时间的字符串表示 |
go
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间
now := time.Now()
fmt.Printf("当前时间: %v\n", now)
// 格式化时间
formatted := now.Format("2006-01-02 15:04:05")
fmt.Printf("格式化时间: %s\n", formatted)
// 解析时间字符串
t, err := time.Parse("2006-01-02 15:04:05", "2023-07-15 14:30:00")
if err != nil {
fmt.Println("解析时间失败:", err)
return
}
fmt.Printf("解析后的时间: %v\n", t)
// 获取时间组件
year, month, day := t.Date()
hour, minute, second := t.Clock()
fmt.Printf("年: %d, 月: %d, 日: %d\n", year, month, day)
fmt.Printf("时: %d, 分: %d, 秒: %d\n", hour, minute, second)
// 时间计算
tomorrow := now.AddDate(0, 0, 1)
fmt.Printf("明天: %v\n", tomorrow)
nextWeek := now.Add(7 * 24 * time.Hour)
fmt.Printf("下周同一时间: %v\n", nextWeek)
// 时间比较
if now.Before(tomorrow) {
fmt.Println("现在在明天之前")
}
// 时区处理
utcTime := now.UTC()
fmt.Printf("UTC时间: %v\n", utcTime)
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println("加载时区失败:", err)
return
}
shanghaiTime := now.In(location)
fmt.Printf("上海时间: %v\n", shanghaiTime)
// 时间戳
timestamp := now.Unix()
fmt.Printf("Unix时间戳(秒): %d\n", timestamp)
timestampMilli := now.UnixMilli()
fmt.Printf("Unix时间戳(毫秒): %d\n", timestampMilli)
timestampMicro := now.UnixMicro()
fmt.Printf("Unix时间戳(微秒): %d\n", timestampMicro)
}注意
格式化日期必须是 2006-01-02 15:04:05 这个固定时间点格式
go
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
ymdhis := now.Format("2006-01-02 15:04:05") // 2026-01-01 12:30:15
ymd := now.Format("2006-01-02") // 2026-01-01
his := now.Format("15:04:05") // 12:30:15
fmt.Println(ymdhis, ymd, his)
}尼玛, 这设计纯属是恶心人... 你要设计什么彩蛋方便你讲故事宣传, 放到源码的注释里就好了, 把占位符设计成一个固定的时间点, 这不是故意恶心人 🤬 增加记忆成本? 其他语言都是 YYYY 或者 Y% 这种方便记忆的占位符, 就你杀马特非主流, 整这些恶心人的玩意儿, 活该你干不过后起之秀 Rust 😡
Carbon 工具包
Carbon 是一个简单、语义化且对开发者友好的Golang时间处理包, 提供了比标准库更丰富的功能和更直观的API, 它由Dromara社区维护, 具有以下特点:
- 语义化API:API命名非常直观,易于理解和使用
- 功能丰富:提供标准库没有的功能,如星座、季节、人性化时间差等
- 时区支持:简化时区处理
- 链式调用:支持流畅的链式API调用
- 边界计算:提供如
StartOfDay(),EndOfMonth()等边界计算功能 - 国际化支持:支持多语言
- JSON序列化:内置JSON序列化支持
Carbon 与标准库对比
标准库适合基础需求,而Carbon在以下场景更加适用:
- 需要人性化时间差: 如"3小时前"、"明天"、"2周后"
- 需要复杂的日期边界计算: 如"本月最后一天"、"季度开始"
- 需要国际化的日期处理: 多语言支持
- 需要高级时间操作: 如星座、季节、无溢出的日期计算
- 需要更简洁的API: 链式调用,语义化的函数名
Carbon在保持高性能的同时,极大地提升了开发效率和代码可读性,特别是在处理复杂的日期时间逻辑时
安装
go
// Go 1.17+(推荐)
go get -u github.com/dromara/carbon/v2
// Go < 1.17
go get -u github.com/golang-module/carbon使用示例
go
package main
import (
"fmt"
"github.com/dromara/carbon/v2"
)
func main() {
// 设置全局默认值
carbon.SetDefault(carbon.Default{
Layout: carbon.DateTimeLayout,
Timezone: carbon.Local,
WeekStartsAt: carbon.Sunday,
Locale: "zh-CN",
})
// 基本时间操作
now := carbon.Now()
fmt.Printf("当前时间: %s\n", now.ToDateTimeString())
// 昨天、今天、明天
fmt.Printf("昨天: %s\n", carbon.Yesterday().ToDateString())
fmt.Printf("今天: %s\n", carbon.Now().ToDateString())
fmt.Printf("明天: %s\n", carbon.Tomorrow().ToDateString())
// 从不同来源创建时间
// 从时间戳
timestamp := carbon.Now().Timestamp()
timeFromTimestamp := carbon.CreateFromTimestamp(timestamp)
fmt.Printf("从时间戳创建: %s\n", timeFromTimestamp.ToDateTimeString())
// 从字符串解析
parsedTime := carbon.Parse("2023-07-15 14:30:15")
fmt.Printf("从字符串解析: %s\n", parsedTime.ToDateTimeString())
// 从日期和时间组件创建
createdTime := carbon.CreateFromDateTime(2023, 7, 15, 14, 30, 15)
fmt.Printf("从日期时间组件创建: %s\n", createdTime.ToDateTimeString())
// 时间计算
// 增加1天
tomorrow := carbon.Now().AddDay()
fmt.Printf("明天: %s\n", tomorrow.ToDateTimeString())
// 增加1周
nextWeek := carbon.Now().AddWeek()
fmt.Printf("下周: %s\n", nextWeek.ToDateTimeString())
// 增加1个月,并避免溢出
nextMonth := carbon.Parse("2023-01-31").AddMonthNoOverflow()
fmt.Printf("下个月(无溢出): %s\n", nextMonth.ToDateString())
// 时间边界
// 今天的开始和结束
startOfDay := carbon.Now().StartOfDay()
endOfDay := carbon.Now().EndOfDay()
fmt.Printf("今天开始: %s\n", startOfDay.ToDateTimeString())
fmt.Printf("今天结束: %s\n", endOfDay.ToDateTimeString())
// 本月的开始和结束
startOfMonth := carbon.Now().StartOfMonth()
endOfMonth := carbon.Now().EndOfMonth()
fmt.Printf("本月开始: %s\n", startOfMonth.ToDateTimeString())
fmt.Printf("本月结束: %s\n", endOfMonth.ToDateTimeString())
// 时区处理
tokyoTime := carbon.Now("Asia/Tokyo")
fmt.Printf("东京时间: %s\n", tokyoTime.ToDateTimeString())
newYorkTime := carbon.Now("America/New_York")
fmt.Printf("纽约时间: %s\n", newYorkTime.ToDateTimeString())
// 时间差
// 人性化的时间差
diff := carbon.Now().AddHours(2).DiffForHumans()
fmt.Printf("时间差(人性化): %s\n", diff)
// 精确的时间差
hoursDiff := carbon.Now().DiffInHours(carbon.Now().AddHours(3))
fmt.Printf("时间差(小时): %d\n", hoursDiff)
// 比较时间
if carbon.Now().IsBefore(tomorrow) {
fmt.Println("现在在明天之前")
}
if carbon.Now().IsToday() {
fmt.Println("今天")
}
// 获取时间组件
year := carbon.Now().Year()
month := carbon.Now().Month()
day := carbon.Now().Day()
hour, minute, second := carbon.Now().Clock()
fmt.Printf("年: %d, 月: %d, 日: %d\n", year, month, day)
fmt.Printf("时: %d, 分: %d, 秒: %d\n", hour, minute, second)
// 星座和季节
fmt.Printf("星座: %s\n", carbon.Now().Constellation())
fmt.Printf("季节: %s\n", carbon.Now().Season())
// JSON序列化
type Event struct {
Name string `json:"name"`
StartTime carbon.DateTime `json:"start_time"`
EndTime carbon.Date `json:"end_date"`
}
event := Event{
Name: "Go Conference",
StartTime: carbon.Now().ToDateTimeStruct(),
EndTime: carbon.Now().AddDays(3).ToDateStruct(),
}
// 使用encoding/json可以正常序列化和反序列化
// data, _ := json.Marshal(event)
// fmt.Println(string(data))
}go
package main
import (
"fmt"
"github.com/dromara/carbon/v2"
)
func main() {
// 值比较
c1 := carbon.Parse("2023-01-01")
c2 := carbon.Parse("2023-06-15")
c3 := carbon.Parse("2023-12-31")
min := carbon.Min(c1, c2, c3)
max := carbon.Max(c1, c2, c3)
fmt.Printf("最早日期: %s\n", min.ToDateString())
fmt.Printf("最晚日期: %s\n", max.ToDateString())
// 持续时间操作
duration := carbon.Now().AddHours(2).DiffInDuration(carbon.Now())
fmt.Printf("持续时间: %s\n", duration.String())
// 边界计算
startOfYear := carbon.Now().StartOfYear()
endOfQuarter := carbon.Now().EndOfQuarter()
fmt.Printf("今年开始: %s\n", startOfYear.ToDateTimeString())
fmt.Printf("本季度结束: %s\n", endOfQuarter.ToDateTimeString())
// 旅游者(时间旅行)
// 添加3年,不导致月份溢出
futureDate := carbon.Parse("2020-02-29").AddYearsNoOverflow(3)
fmt.Printf("3年后的日期(无溢出): %s\n", futureDate.ToDateString())
// 减去1个季度,不导致月份溢出
pastDate := carbon.Parse("2020-05-31").SubQuarterNoOverflow()
fmt.Printf("1季度前的日期(无溢出): %s\n", pastDate.ToDateString())
// 格式化和解析
// 使用自定义格式解析
customFormatTime := carbon.ParseByFormat("2023|07|15 14|30|15", "Y|m|d H|i|s")
fmt.Printf("自定义格式解析: %s\n", customFormatTime.ToDateTimeString())
// 使用布局格式化
formatted := carbon.Now().Layout("2006年01月02日 15时04分05秒")
fmt.Printf("自定义布局格式化: %s\n", formatted)
// 人性化输出
fmt.Printf("1天前: %s\n", carbon.Now().SubDay().DiffForHumans())
fmt.Printf("2小时后: %s\n", carbon.Now().AddHours(2).DiffForHumans())
fmt.Printf("3个月前: %s\n", carbon.Now().SubMonths(3).DiffForHumans())
// 季节和星座
fmt.Printf("当前星座: %s\n", carbon.Now().Constellation())
fmt.Printf("是否是夏天: %v\n", carbon.Now().IsSummer())
fmt.Printf("季节开始: %s\n", carbon.Now().StartOfSeason().ToDateString())
// 国际化
enTime := carbon.Now().SetLocale("en")
fmt.Printf("英文时间差: %s\n", enTime.AddHours(1).DiffForHumans())
zhTime := carbon.Now().SetLocale("zh-CN")
fmt.Printf("中文时间差: %s\n", zhTime.AddHours(1).DiffForHumans())
}