Skip to content

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在以下场景更加适用:

  1. 需要人性化时间差: 如"3小时前"、"明天"、"2周后"
  2. 需要复杂的日期边界计算: 如"本月最后一天"、"季度开始"
  3. 需要国际化的日期处理: 多语言支持
  4. 需要高级时间操作: 如星座、季节、无溢出的日期计算
  5. 需要更简洁的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())
}

Released under the MIT License.