1 介绍
1.1 定义
01.Carbon简介
a.功能说明
Carbon是Golang生态中最流行的时间日期处理库,对标PHP中的Carbon和JavaScript中的Moment.js。它基于time.Time进行封装,提供了更简洁直观的API来处理日期时间相关操作。Carbon通过链式调用和丰富的格式化选项,大大简化了时间计算、格式转换、时区处理等常见任务。该库支持农历转换、生肖星座计算等中国特色功能,是处理时间日期的首选工具。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 创建Carbon实例
now := carbon.Now()
fmt.Println("当前时间:", now.ToString())
// 时间运算示例
tomorrow := now.AddDay()
fmt.Println("明天:", tomorrow.ToDateString())
// 格式化输出
fmt.Println("标准格式:", now.ToDateTimeString())
fmt.Println("自定义格式:", now.Format("Y年m月d日 H:i:s"))
// 时间比较
if now.Lt(tomorrow) {
fmt.Println("现在早于明天")
}
// 解析时间字符串
parsed := carbon.Parse("2024-01-15 14:30:00")
fmt.Println("解析的时间:", parsed.ToDateTimeString())
// 时区处理
tokyo := now.SetTimezone(carbon.Tokyo)
fmt.Println("东京时间:", tokyo.ToDateTimeString())
}
---
1.2 核心概念
01.Carbon实例
a.不可变性
Carbon采用不可变设计模式,每次操作都返回新的Carbon实例而不修改原实例。这种设计避免了副作用,使代码更安全可预测。例如AddDay()不会修改原时间对象,而是返回新的Carbon实例表示明天的时间。这与time.Time的可变性形成对比,大大降低了并发场景下的数据竞争风险。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 不可变性演示
original := carbon.Parse("2024-01-15 10:00:00")
fmt.Println("原始时间:", original.ToDateTimeString())
// 执行加法操作
modified := original.AddHours(5)
fmt.Println("修改后:", modified.ToDateTimeString())
fmt.Println("原始未变:", original.ToDateTimeString()) // 原实例不受影响
// 链式调用
result := carbon.Now().
AddDays(7).
StartOfDay().
AddHours(9)
fmt.Println("链式结果:", result.ToDateTimeString())
// 多次使用原实例
tomorrow := original.AddDay()
nextWeek := original.AddWeek()
fmt.Println("明天:", tomorrow.ToDateString())
fmt.Println("下周:", nextWeek.ToDateString())
}
---
02.时区感知
a.功能说明
Carbon内置全球时区支持,可以轻松在不同时区之间转换。每个Carbon实例都关联一个时区,默认使用系统时区。通过SetTimezone()可以切换时区,时间值会自动调整。Carbon预定义了常用时区常量如carbon.Tokyo、carbon.London等,也支持IANA时区数据库标准名称如"Asia/Shanghai"。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取当前时间(系统时区)
local := carbon.Now()
fmt.Println("本地时间:", local.ToString())
// 转换到不同时区
tokyo := local.SetTimezone(carbon.Tokyo)
london := local.SetTimezone(carbon.London)
ny := local.SetTimezone(carbon.NewYork)
fmt.Println("东京时间:", tokyo.ToDateTimeString())
fmt.Println("伦敦时间:", london.ToDateTimeString())
fmt.Println("纽约时间:", ny.ToDateTimeString())
// 使用IANA时区名称
shanghai := local.SetTimezone("Asia/Shanghai")
fmt.Println("上海时间:", shanghai.ToDateTimeString())
// 创建时指定时区
specificTime := carbon.Parse("2024-01-15 14:00:00", carbon.PRC)
fmt.Println("指定时区:", specificTime.ToString())
}
---
03.语言本地化
a.功能说明
Carbon支持多语言本地化,可以将月份、星期等输出为不同语言。内置支持中文、英文、日文等数十种语言。通过SetLanguage()设置语言后,ToMonthString()、ToDayOfWeekString()等方法会自动使用对应语言。这对于国际化应用非常有用,无需手动维护翻译映射表。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 英语输出
now.SetLanguage("en")
fmt.Println("English Month:", now.ToMonthString())
fmt.Println("English Day:", now.ToDayOfWeekString())
// 中文输出
now.SetLanguage("zh-CN")
fmt.Println("中文月份:", now.ToMonthString())
fmt.Println("中文星期:", now.ToDayOfWeekString())
// 日语输出
now.SetLanguage("ja")
fmt.Println("日本語月:", now.ToMonthString())
fmt.Println("日本語曜日:", now.ToDayOfWeekString())
// 在格式化中使用本地化
now.SetLanguage("zh-CN")
fmt.Println("完整日期:", now.ToFormattedDateString())
fmt.Println("相对时间:", now.DiffForHumans())
}
---
1.3 优缺点
01.核心优势
a.API简洁性
Carbon相比标准库time包提供了更直观的API。例如时间加减,time包需要使用time.Add(24*time.Hour),而Carbon只需AddDay()。格式化时间,time包使用晦涩的"2006-01-02 15:04:05"魔法数字,Carbon则使用PHP风格的"Y-m-d H:i:s"更易记忆。链式调用支持使代码更流畅,一行代码即可完成复杂的时间计算。
b.代码对比
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
// 标准库方式(复杂)
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
formatted := tomorrow.Format("2006-01-02 15:04:05")
fmt.Println("标准库:", formatted)
// Carbon方式(简洁)
carbonResult := carbon.Now().AddDay().ToDateTimeString()
fmt.Println("Carbon:", carbonResult)
// 复杂计算对比
// 标准库:下周一早上9点
std := time.Now()
daysUntilMonday := (8 - int(std.Weekday())) % 7
if daysUntilMonday == 0 {
daysUntilMonday = 7
}
nextMonday := std.Add(time.Duration(daysUntilMonday) * 24 * time.Hour)
result := time.Date(nextMonday.Year(), nextMonday.Month(), nextMonday.Day(),
9, 0, 0, 0, nextMonday.Location())
fmt.Println("标准库下周一:", result.Format("2006-01-02 15:04:05"))
// Carbon:同样的需求
carbonMonday := carbon.Now().Next(carbon.Monday).StartOfDay().AddHours(9)
fmt.Println("Carbon下周一:", carbonMonday.ToDateTimeString())
}
---
c.丰富功能
Carbon提供了大量开箱即用的功能,包括时间差值计算、相对时间显示、时间范围判断、农历转换、生肖星座等。这些功能在标准库中需要手动实现,而Carbon直接提供了经过充分测试的实现。特别是中国特色功能如农历支持,填补了Golang生态的空白。
d.功能演示
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 相对时间(人性化显示)
past := carbon.Now().SubDays(3)
fmt.Println("相对时间:", past.DiffForHumans()) // "3天前"
// 时间范围判断
start := carbon.Parse("2024-01-01")
end := carbon.Parse("2024-12-31")
if now.Between(start, end) {
fmt.Println("在2024年范围内")
}
// 农历转换
lunar := now.Lunar()
fmt.Printf("农历: %d年%s%d月%d日\n",
lunar.Year(), lunar.LeapMonth(), lunar.Month(), lunar.Day())
// 生肖星座
fmt.Println("生肖:", now.Animal())
fmt.Println("星座:", now.Constellation())
// 工作日判断
if now.IsWeekday() {
fmt.Println("今天是工作日")
}
// 季节判断
fmt.Println("季节:", now.Season())
}
---
02.潜在限制
a.性能开销
Carbon对time.Time进行了封装,引入了轻微的性能开销。每次操作创建新实例会增加内存分配。对于高频时间操作(如每秒数百万次),可能产生可感知的性能影响。但对于绝大多数应用,这点开销相比开发效率提升是值得的。性能敏感场景建议��准测试后决定。
b.性能对比
---
package main
import (
"fmt"
"time"
"testing"
"github.com/golang-module/carbon/v2"
)
// 标准库基准测试
func BenchmarkStdLib(b *testing.B) {
for i := 0; i < b.N; i++ {
now := time.Now()
_ = now.Add(24 * time.Hour)
}
}
// Carbon基准测试
func BenchmarkCarbon(b *testing.B) {
for i := 0; i < b.N; i++ {
now := carbon.Now()
_ = now.AddDay()
}
}
func main() {
// 性能测试示例
start := time.Now()
for i := 0; i < 1000000; i++ {
_ = time.Now().Add(24 * time.Hour)
}
stdDuration := time.Since(start)
start = time.Now()
for i := 0; i < 1000000; i++ {
_ = carbon.Now().AddDay()
}
carbonDuration := time.Since(start)
fmt.Printf("标准库耗时: %v\n", stdDuration)
fmt.Printf("Carbon耗时: %v\n", carbonDuration)
fmt.Printf("性能比: %.2f%%\n", float64(carbonDuration)/float64(stdDuration)*100)
}
---
c.学习曲线
虽然Carbon API简洁,但需要学习新的方法名和格式化语法。团队成员需要熟悉Carbon特有的概念如不可变性、链式调用等。对于只做简单时间操作的项目,引入额外依赖可能过度设计。建议根据项目复杂度和团队技术栈选择,避免为了使用而使用。
1.4 使用场景
01.Web应用开发
a.场景说明
Web应用中大量涉及时间处理,如用户注册时间、文章发布时间、评论时间戳等。Carbon简化了时间格式化、时区转换、相对时间显示等常见需求。例如显示"3小时前"、将UTC时间转为用户本地时区、计算会员到期剩余天数等。与ORM配合使用时,可以优雅地处理created_at、updated_at字段。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// 用户模型
type User struct {
ID int
Username string
CreatedAt carbon.Carbon
LastLogin carbon.Carbon
}
func main() {
user := User{
ID: 1,
Username: "alice",
CreatedAt: carbon.Parse("2023-01-15 10:30:00"),
LastLogin: carbon.Now().SubHours(3),
}
// 显示注册时间(相对时间)
fmt.Printf("注册于: %s\n", user.CreatedAt.DiffForHumans())
// 显示最后登录(人性化)
fmt.Printf("最后登录: %s\n", user.LastLogin.DiffForHumans())
// 计算账号年龄
age := user.CreatedAt.DiffInYears(carbon.Now())
fmt.Printf("账号年龄: %d年\n", age)
// 时区转换(假设用户在东京)
userLocalTime := user.LastLogin.SetTimezone(carbon.Tokyo)
fmt.Printf("用户本地时间: %s\n", userLocalTime.ToDateTimeString())
// 判断是否新用户(注册不超过7天)
if user.CreatedAt.Gt(carbon.Now().SubDays(7)) {
fmt.Println("新用户标识")
}
}
---
02.日志系统
a.场景说明
日志系统需要统一的时间格式和精确的时间戳。Carbon可以轻松实现日志时间的标准化、不同格式的时间解析、日志文件按日期归档等功能。支持毫秒级时间戳、UTC时间存储、本地时区显示等需求。特别适合分布式系统中统一时间处理逻辑。
b.代码示例
---
package main
import (
"fmt"
"os"
"github.com/golang-module/carbon/v2"
)
// 日志记录器
type Logger struct {
file *os.File
}
func (l *Logger) Log(level, message string) {
now := carbon.Now(carbon.UTC)
// 毫秒级时间戳
timestamp := now.ToTimestampWithMillisecond()
// 标准格式
formatted := now.ToDateTimeString()
// ISO8601格式
iso := now.ToRfc3339String()
logEntry := fmt.Sprintf("[%s] [%s] %s (timestamp: %d)\n",
formatted, level, message, timestamp)
fmt.Print(logEntry)
// l.file.WriteString(logEntry) // 实际写入文件
}
func main() {
logger := &Logger{}
// 记录不同级别日志
logger.Log("INFO", "应用启动")
logger.Log("WARN", "连接池达到80%")
logger.Log("ERROR", "数据库查询失败")
// 日志文件命名(按日期)
filename := carbon.Now().ToShortDateString() + ".log"
fmt.Printf("日志文件: %s\n", filename)
// 解析不同格式的日志时间
logs := []string{
"2024-01-15 10:30:45",
"2024/01/15 14:20:30",
"15-Jan-2024 16:45:00",
}
for _, log := range logs {
parsed := carbon.Parse(log)
if !parsed.IsZero() {
fmt.Printf("解析成功: %s -> %s\n",
log, parsed.ToDateTimeString())
}
}
}
---
03.任务调度
a.场景说明
定时任务、cron作业、延迟队列等场景需要精确的时间计算。Carbon可以计算下次执行时间、判断是否到达触发时刻、处理时区差异等。支持复杂的时间规则如"每周一早上9点"、"每月最后一天"、"工作日每2小时"等。与调度器配合使用可以构建强大的任务系统。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
// 定时任务
type Task struct {
Name string
Schedule string
NextRun carbon.Carbon
}
func (t *Task) CalculateNextRun() {
now := carbon.Now()
switch t.Schedule {
case "daily":
// 每天凌晨执行
t.NextRun = now.AddDay().StartOfDay()
case "weekly":
// 每周一早上9点
t.NextRun = now.Next(carbon.Monday).StartOfDay().AddHours(9)
case "monthly":
// 每月1号中午12点
t.NextRun = now.AddMonth().StartOfMonth().StartOfDay().AddHours(12)
case "hourly":
// 每小时整点
t.NextRun = now.AddHour().StartOfHour()
}
}
func (t *Task) ShouldRun() bool {
return carbon.Now().Gte(t.NextRun)
}
func main() {
tasks := []Task{
{Name: "数据备份", Schedule: "daily"},
{Name: "周报生成", Schedule: "weekly"},
{Name: "账单结算", Schedule: "monthly"},
{Name: "健康检查", Schedule: "hourly"},
}
// 计算所有任务的下次执行时间
for i := range tasks {
tasks[i].CalculateNextRun()
fmt.Printf("任务: %s, 下次执行: %s (%s)\n",
tasks[i].Name,
tasks[i].NextRun.ToDateTimeString(),
tasks[i].NextRun.DiffForHumans())
}
// 模拟任务调度器
fmt.Println("\n开始任务调度...")
for _, task := range tasks {
if task.ShouldRun() {
fmt.Printf("执行任务: %s\n", task.Name)
} else {
remaining := carbon.Now().DiffInSeconds(task.NextRun)
fmt.Printf("任务 %s 还需等待 %d 秒\n", task.Name, remaining)
}
}
}
---
04.数据分析
a.场景说明
数据分析中常需要按时间维度聚合统计,如日活、周活、月度报表等。Carbon可以轻松获取时间范围的开始和结束、遍历时间序列、计算时间跨度等。支持各种时间粒度的分组,如按小时、按天、按周、按月统计。与数据库查询结合可以高效生成时间维度报表。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// 数据点
type DataPoint struct {
Timestamp carbon.Carbon
Value float64
}
func main() {
// 生成本周每天的时间范围
fmt.Println("本周日期范围:")
startOfWeek := carbon.Now().StartOfWeek()
endOfWeek := carbon.Now().EndOfWeek()
current := startOfWeek
for current.Lte(endOfWeek) {
fmt.Printf(" %s (%s)\n",
current.ToShortDateString(),
current.ToShortDayOfWeekString())
current = current.AddDay()
}
// 生成过去7天的日期列表(用于查询)
fmt.Println("\n过去7天:")
dates := make([]string, 0, 7)
for i := 6; i >= 0; i-- {
date := carbon.Now().SubDays(i)
dates = append(dates, date.ToShortDateString())
fmt.Printf(" %s\n", date.ToShortDateString())
}
// 本月每周的统计范围
fmt.Println("\n本月周范围:")
monthStart := carbon.Now().StartOfMonth()
monthEnd := carbon.Now().EndOfMonth()
weekStart := monthStart.StartOfWeek()
weekNum := 1
for weekStart.Lt(monthEnd) {
weekEnd := weekStart.EndOfWeek()
if weekEnd.Gt(monthEnd) {
weekEnd = monthEnd
}
fmt.Printf(" 第%d周: %s 至 %s\n",
weekNum,
weekStart.ToShortDateString(),
weekEnd.ToShortDateString())
weekStart = weekStart.AddWeek()
weekNum++
}
// 按时间段聚合数据
fmt.Println("\n时间段分组:")
now := carbon.Now()
periods := map[string]carbon.Carbon{
"今日": now.StartOfDay(),
"本周": now.StartOfWeek(),
"本月": now.StartOfMonth(),
"本季": now.StartOfQuarter(),
"今年": now.StartOfYear(),
}
for name, start := range periods {
days := start.DiffInDays(now)
fmt.Printf(" %s: %s (已过%d天)\n",
name,
start.ToShortDateString(),
days)
}
}
---
1.5 设计理念
01.流畅接口
a.设计思想
Carbon采用流畅接口(Fluent Interface)设计模式,所有方法都返回Carbon实例本身或新的Carbon实例,支持链式调用。这种设计使代码读起来像自然语言,大大提高了代码的可读性和表达力。例如Now().AddDays(7).StartOfDay()清晰表达了"从现在开始,加7天后,取那天的开始时刻"。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 链式调用示例
result := carbon.Now().
AddMonths(3). // 3个月后
EndOfMonth(). // 该月的最后时刻
StartOfDay(). // 当天的开始
AddHours(9). // 加9小时
AddMinutes(30) // 加30分钟
fmt.Println("计算结果:", result.ToDateTimeString())
// 复杂的时间计算
deadline := carbon.Parse("2024-12-31 23:59:59").
SubMonths(1). // 往前推1个月
StartOfMonth(). // 月初
Next(carbon.Monday). // 下个周一
AddWeeks(2). // 再加2周
SetHour(18). // 设置为18点
SetMinute(0). // 分钟归零
SetSecond(0) // 秒钟归零
fmt.Println("截止日期:", deadline.ToDateTimeString())
// 条件链式调用
t := carbon.Now()
if t.IsWeekend() {
t = t.Next(carbon.Monday) // 如果是周末,取下周一
}
t = t.StartOfDay().AddHours(9)
fmt.Println("下个工作日早上9点:", t.ToDateTimeString())
// 多步骤格式化
formatted := carbon.Parse("2024-01-15 14:30:45").
SetTimezone(carbon.Shanghai).
SetLanguage("zh-CN").
ToDateTimeString()
fmt.Println("格式化结果:", formatted)
}
---
02.语义化命名
a.命名哲学
Carbon的方法命名遵循语义化原则,方法名直接表达功能意图。例如IsWeekday()、IsLeapYear()、IsToday()等布尔方法,ToDateString()、ToTimeString()等转换方法,AddDay()、SubWeek()等运算方法。这种命名避免了查文档的频率,提高了开发效率和代码自解释性。
b.代码演示
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 判断方法(Is系列)
fmt.Println("是否周末:", now.IsWeekend())
fmt.Println("是否闰年:", now.IsLeapYear())
fmt.Println("是否今天:", now.IsToday())
fmt.Println("是否过去:", now.IsPast())
fmt.Println("是否未来:", now.IsFuture())
fmt.Println("是否工作日:", now.IsWeekday())
// 转换方法(To系列)
fmt.Println("转日期:", now.ToDateString())
fmt.Println("转时间:", now.ToTimeString())
fmt.Println("转日期时间:", now.ToDateTimeString())
fmt.Println("转时间戳:", now.ToTimestamp())
fmt.Println("转RFC3339:", now.ToRfc3339String())
// 运算方法(Add/Sub系列)
fmt.Println("加1天:", now.AddDay().ToDateString())
fmt.Println("减1周:", now.SubWeek().ToDateString())
fmt.Println("加3月:", now.AddMonths(3).ToDateString())
fmt.Println("减2年:", now.SubYears(2).ToDateString())
// 范围方法(Start/End系列)
fmt.Println("日开始:", now.StartOfDay().ToDateTimeString())
fmt.Println("日结束:", now.EndOfDay().ToDateTimeString())
fmt.Println("周开始:", now.StartOfWeek().ToDateTimeString())
fmt.Println("月结束:", now.EndOfMonth().ToDateTimeString())
// 比较方法(Eq/Gt/Lt系列)
tomorrow := now.AddDay()
fmt.Println("等于:", now.Eq(now))
fmt.Println("大于:", tomorrow.Gt(now))
fmt.Println("小于:", now.Lt(tomorrow))
fmt.Println("介于:", now.Between(now.SubDay(), tomorrow))
}
---
03.零值安全
a.安全设计
Carbon对零值和无效值进行了特殊处理,避免panic。当解析失败或遇到无效操作时,返回零值Carbon实例而非nil,可以通过IsZero()判断。这种设计符合Golang的错误处理哲学,让代码更健壮。所有方法都能安全地在零值上调用,不会引发运行时恐慌。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 解析无效时间字符串
invalid := carbon.Parse("invalid-date")
fmt.Println("是否零值:", invalid.IsZero())
fmt.Println("零值输出:", invalid.ToDateTimeString()) // 安全返回空字符串
// 零值上的安全操作
var zero carbon.Carbon
fmt.Println("零值IsZero:", zero.IsZero())
result := zero.AddDay() // 不会panic
fmt.Println("零值运算:", result.IsZero())
// 错误处理模式
dateStr := "2024-13-45" // 无效日期
parsed := carbon.Parse(dateStr)
if parsed.IsZero() {
fmt.Printf("解析失败: %s\n", dateStr)
// 使用默认值
parsed = carbon.Now()
}
fmt.Println("最终时间:", parsed.ToDateTimeString())
// 安全的时区设置
t := carbon.Parse("2024-01-15 10:00:00")
withTimezone := t.SetTimezone("Invalid/Timezone")
if withTimezone.Error != nil {
fmt.Println("时区错误:", withTimezone.Error)
// 继续使用原时间
withTimezone = t
}
// 安全的格式化
formatted := carbon.Parse("").ToDateTimeString()
if formatted == "" {
fmt.Println("空时间的格式化结果为空字符串")
}
// 批量解析时的错误处理
dates := []string{
"2024-01-15",
"invalid",
"2024-02-20",
"2024-99-99",
}
validDates := make([]carbon.Carbon, 0)
for _, dateStr := range dates {
c := carbon.Parse(dateStr)
if !c.IsZero() {
validDates = append(validDates, c)
} else {
fmt.Printf("跳过无效日期: %s\n", dateStr)
}
}
fmt.Printf("有效日期数量: %d\n", len(validDates))
}
---
04.性能优先
a.优化策略
虽然Carbon进行了封装,但在关键路径上进行了性能优化。内部缓存了time.Time对象,避免重复转换。时区和语言对象采用单例模式,减少内存占用。提供了批量操作方法减少函数调用开销。对于性能敏感场景,可以使用ToStdTime()转回标准库进行底层操作。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
// Carbon与标准库互转
now := carbon.Now()
stdTime := now.ToStdTime() // 转为time.Time
fmt.Println("标准库时间:", stdTime)
backToCarbon := carbon.CreateFromStdTime(stdTime)
fmt.Println("转回Carbon:", backToCarbon.ToDateTimeString())
// 批量时间生成(复用Carbon实例)
base := carbon.Parse("2024-01-01")
dates := make([]carbon.Carbon, 0, 365)
current := base
for i := 0; i < 365; i++ {
dates = append(dates, current)
current = current.AddDay()
}
fmt.Printf("生成了%d个日期\n", len(dates))
// 性能关键代码使用标准库
start := time.Now()
for i := 0; i < 1000000; i++ {
_ = time.Now().Add(time.Hour)
}
stdDuration := time.Since(start)
// 非性能关键用Carbon提高可读性
tasks := []struct {
Name string
Due carbon.Carbon
}{
{"任务1", carbon.Now().AddDays(7)},
{"任务2", carbon.Now().AddWeeks(2)},
{"任务3", carbon.Now().AddMonths(1)},
}
for _, task := range tasks {
fmt.Printf("%s 截止: %s\n",
task.Name, task.Due.DiffForHumans())
}
fmt.Printf("性能测试耗时: %v\n", stdDuration)
}
---
2 基础操作
2.1 汇总:6个
01.Carbon核心功能概览
a.说明
Carbon提供6大核心功能模块:创建时间、解析字符串、格式化输出、时间比较、时间运算和错误处理。
这些功能覆盖了日常开发中90%的时间处理场景,API设计简洁直观,链式调用流畅。
相比标准库time包,Carbon提供更友好的API和更丰富的功能,大幅降低开发复杂度。
支持时区转换、多语言输出、农历星座等特色功能,是Go生态中最完善的时间处理库。
本章将逐一讲解这6大功能的使用方法,为后续深入学习打下基础。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 1. 创建时间:多种方式创建Carbon实例
now := carbon.Now() // 当前时间
yesterday := carbon.Yesterday() // 昨天
tomorrow := carbon.Tomorrow() // 明天
custom := carbon.Parse("2024-01-15 10:30:00") // 解析字符串
// 2. 格式化输出:灵活的格式化选项
fmt.Println("标准格式:", now.ToDateTimeString()) // 2024-01-15 10:30:00
fmt.Println("自定义格式:", now.Format("Y年m月d日")) // 2024年01月15日
fmt.Println("时间戳:", now.Timestamp()) // 1705287000
// 3. 时间比较:丰富的比较方法
if tomorrow.Gt(now) {
fmt.Println("明天大于今天")
}
if custom.Between(yesterday, tomorrow) {
fmt.Println("自定义时间在昨天和明天之间")
}
// 4. 时间运算:加减操作
nextWeek := now.AddWeek() // 加一周
lastMonth := now.SubMonth() // 减一月
fmt.Println("下周:", nextWeek.ToDateString())
fmt.Println("上月:", lastMonth.ToDateString())
// 5. 差值计算:获取时间间隔
diff := tomorrow.DiffInDays(now) // 相差天数
fmt.Printf("明天距今%d天\n", diff)
// 6. 错误处理:安全的错误检查
invalidTime := carbon.Parse("invalid-date")
if invalidTime.Error != nil {
fmt.Println("解析错误:", invalidTime.Error)
}
// 链式调用示例:流畅的API设计
result := carbon.Now().
AddDays(7). // 加7天
StartOfDay(). // 获取当天开始时间
ToDateTimeString() // 格式化输出
fmt.Println("链式调用结果:", result)
}
---
02.Carbon与标准库对比
a.说明
Carbon相比标准库time包有诸多优势:API更简洁、功能更丰富、错误处理更友好。
标准库需要频繁使用Layout常量和复杂的时区设置,Carbon则提供直观的方法名。
Carbon内置了农历、星座、生肖等中国特色功能,标准库则需要自行实现。
性能方面Carbon也做了优化,缓存了常用格式和时区信息,减少重复计算。
下面的示例对比展示了两者在常见场景中的使用差异,突显Carbon的便利性。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
// 场景1:格式化当前时间
// 标准库方式:需要记住复杂的Layout
stdNow := time.Now()
stdFormat := stdNow.Format("2006-01-02 15:04:05")
fmt.Println("标准库:", stdFormat)
// Carbon方式:语义化方法名
carbonNow := carbon.Now()
carbonFormat := carbonNow.ToDateTimeString()
fmt.Println("Carbon:", carbonFormat)
// 场景2:解析字符串
// 标准库方式:必须指定精确的Layout
stdParse, err := time.Parse("2006-01-02", "2024-01-15")
if err != nil {
fmt.Println("解析错误:", err)
}
fmt.Println("标准库解析:", stdParse)
// Carbon方式:自动识别格式
carbonParse := carbon.Parse("2024-01-15")
fmt.Println("Carbon解析:", carbonParse.ToDateString())
// 场景3:时间加减
// 标准库方式:使用AddDate或Add方法
stdAdd := stdNow.AddDate(0, 1, 7) // 加1月7天
fmt.Println("标准库加减:", stdAdd.Format("2006-01-02"))
// Carbon方式:语义化的链式调用
carbonAdd := carbonNow.AddMonth().AddDays(7)
fmt.Println("Carbon加减:", carbonAdd.ToDateString())
// 场景4:获取月初
// 标准库方式:需要手动构造
stdStart := time.Date(stdNow.Year(), stdNow.Month(), 1, 0, 0, 0, 0, stdNow.Location())
fmt.Println("标准库月初:", stdStart.Format("2006-01-02"))
// Carbon方式:一个方法搞定
carbonStart := carbonNow.StartOfMonth()
fmt.Println("Carbon月初:", carbonStart.ToDateString())
// 场景5:中文格式输出
// 标准库:需要自己实现映射
weekdays := []string{"日", "一", "二", "三", "四", "五", "六"}
stdChinese := fmt.Sprintf("星期%s", weekdays[stdNow.Weekday()])
fmt.Println("标准库中文:", stdChinese)
// Carbon:内置多语言支持
carbonChinese := carbonNow.SetLocale("zh-CN").ToWeekString()
fmt.Println("Carbon中文:", carbonChinese)
}
---
03.快速上手示例
a.说明
通过一个实际的业务场景演示Carbon的综合应用:实现一个活动倒计时功能。
需求包括:计算活动剩余时间、判断活动状态、格式化显示倒计时信息。
示例展示了Carbon如何简化复杂的时间逻辑,代码清晰易懂且易于维护。
同时演示了错误处理、时区转换、多种格式输出等实用技巧。
这个示例可作为模板,快速应用到实际项目中的类似场景。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// ActivityCountdown 活动倒计时结构
type ActivityCountdown struct {
Name string
StartTime carbon.Carbon
EndTime carbon.Carbon
}
// GetStatus 获取活动状态
func (a *ActivityCountdown) GetStatus() string {
now := carbon.Now()
if now.Lt(a.StartTime) {
return "未开始"
} else if now.Between(a.StartTime, a.EndTime) {
return "进行中"
} else {
return "已结束"
}
}
// GetCountdown 获取倒计时信息
func (a *ActivityCountdown) GetCountdown() string {
now := carbon.Now()
status := a.GetStatus()
switch status {
case "未开始":
days := a.StartTime.DiffInDays(now)
hours := a.StartTime.DiffInHours(now) % 24
return fmt.Sprintf("距离开始还有 %d天%d小时", days, hours)
case "进行中":
days := a.EndTime.DiffInDays(now)
hours := a.EndTime.DiffInHours(now) % 24
minutes := a.EndTime.DiffInMinutes(now) % 60
return fmt.Sprintf("距离结束还有 %d天%d小时%d分钟", days, hours, minutes)
default:
return "活动已结束"
}
}
func main() {
// 创建活动实例
activity := ActivityCountdown{
Name: "双十一大促",
StartTime: carbon.Parse("2024-11-11 00:00:00"),
EndTime: carbon.Parse("2024-11-11 23:59:59"),
}
// 检查解析错误
if activity.StartTime.Error != nil {
fmt.Println("开始时间解析错误:", activity.StartTime.Error)
return
}
// 显示活动信息
fmt.Printf("活动名称: %s\n", activity.Name)
fmt.Printf("开始时间: %s\n", activity.StartTime.ToDateTimeString())
fmt.Printf("结束时间: %s\n", activity.EndTime.ToDateTimeString())
fmt.Printf("活动状态: %s\n", activity.GetStatus())
fmt.Printf("倒计时: %s\n", activity.GetCountdown())
// 计算活动持续时间
duration := activity.EndTime.DiffInHours(activity.StartTime)
fmt.Printf("活动持续: %d小时\n", duration)
// 多语言显示
activity.StartTime.SetLocale("zh-CN")
fmt.Printf("开始日期(中文): %s\n", activity.StartTime.ToDateString())
}
---
2.2 创建时间
01.常用创建方法
a.说明
Carbon提供多种便捷的时间创建方法,覆盖各种常见场景。
Now()获取当前时间,Yesterday()和Tomorrow()获取相对日期。
Create()系列方法支持精确到纳秒的时间创建,参数灵活可选。
所有创建方法默认使用系统时区,也可通过SetTimezone()指定时区。
创建的Carbon实例是不可变的,所有操作返回新实例,保证线程安全。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取当前时间
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
// 获取今天、昨天、明天
today := carbon.Today()
yesterday := carbon.Yesterday()
tomorrow := carbon.Tomorrow()
fmt.Println("今天:", today.ToDateString())
fmt.Println("昨天:", yesterday.ToDateString())
fmt.Println("明天:", tomorrow.ToDateString())
// 精确创建时间:年月日时分秒
custom1 := carbon.CreateFromDate(2024, 1, 15)
fmt.Println("指定日期:", custom1.ToDateString())
custom2 := carbon.CreateFromTime(10, 30, 45)
fmt.Println("指定时间:", custom2.ToTimeString())
// 创建完整时间:年月日时分秒纳秒
custom3 := carbon.CreateFromDateTime(2024, 1, 15, 10, 30, 45)
fmt.Println("完整时间:", custom3.ToDateTimeString())
// 使用时间戳创建
timestamp := int64(1705287000)
fromTimestamp := carbon.CreateFromTimestamp(timestamp)
fmt.Println("时间戳创建:", fromTimestamp.ToDateTimeString())
// 使用毫秒时间戳创建
millisecond := int64(1705287000000)
fromMilli := carbon.CreateFromTimestampMilli(millisecond)
fmt.Println("毫秒时间戳:", fromMilli.ToDateTimeString())
// 指定时区创建
tokyo := carbon.Now().SetTimezone("Asia/Tokyo")
fmt.Println("东京时间:", tokyo.ToDateTimeString())
newYork := carbon.Now().SetTimezone("America/New_York")
fmt.Println("纽约时间:", newYork.ToDateTimeString())
}
---
02.特殊时间点创建
a.说明
Carbon提供了快速创建特殊时间点的方法,如周初周末、月初月末等。
这些方法在报表统计、周期性任务等场景中非常实用。
支持创建当前周期的开始和结束时间,也支持创建上一个或下一个周期。
时间精度自动调整,日期开始为00:00:00,结束为23:59:59。
组合使用这些方法可以快速构建复杂的时间范围查询条件。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
// 本周开始和结束
startOfWeek := now.StartOfWeek()
endOfWeek := now.EndOfWeek()
fmt.Println("本周开始:", startOfWeek.ToDateTimeString())
fmt.Println("本周结束:", endOfWeek.ToDateTimeString())
// 本月开始和结束
startOfMonth := now.StartOfMonth()
endOfMonth := now.EndOfMonth()
fmt.Println("本月开始:", startOfMonth.ToDateTimeString())
fmt.Println("本月结束:", endOfMonth.ToDateTimeString())
// 本季度开始和结束
startOfQuarter := now.StartOfQuarter()
endOfQuarter := now.EndOfQuarter()
fmt.Println("本季度开始:", startOfQuarter.ToDateTimeString())
fmt.Println("本季度结束:", endOfQuarter.ToDateTimeString())
// 本年开始和结束
startOfYear := now.StartOfYear()
endOfYear := now.EndOfYear()
fmt.Println("本年开始:", startOfYear.ToDateTimeString())
fmt.Println("本年结束:", endOfYear.ToDateTimeString())
// 当天开始和结束
startOfDay := now.StartOfDay()
endOfDay := now.EndOfDay()
fmt.Println("当天开始:", startOfDay.ToDateTimeString())
fmt.Println("当天结束:", endOfDay.ToDateTimeString())
// 本小时开始和结束
startOfHour := now.StartOfHour()
endOfHour := now.EndOfHour()
fmt.Println("本小时开始:", startOfHour.ToDateTimeString())
fmt.Println("本小时结束:", endOfHour.ToDateTimeString())
// 实用示例:获取上个月的时间范围
lastMonthStart := now.SubMonth().StartOfMonth()
lastMonthEnd := now.SubMonth().EndOfMonth()
fmt.Println("\n上个月范围:")
fmt.Println("开始:", lastMonthStart.ToDateTimeString())
fmt.Println("结束:", lastMonthEnd.ToDateTimeString())
}
---
03.从其他类型创建
a.说明
Carbon支持从多种Go原生类型创建时间对象,实现与标准库的无缝集成。
可以从time.Time、time.Duration等类型转换,方便在现有项目中引入Carbon。
支持从各种精度的时间戳创建,包括秒、毫秒、微秒、纳秒。
提供了Carbon到time.Time的双向转换,保证与第三方库的兼容性。
转换过程会保留原时区信息,确保时间的准确性。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
// 从标准库time.Time创建
stdTime := time.Now()
fromStdTime := carbon.CreateFromStdTime(stdTime)
fmt.Println("从time.Time创建:", fromStdTime.ToDateTimeString())
// Carbon转换为time.Time
carbonTime := carbon.Now()
toStdTime := carbonTime.ToStdTime()
fmt.Println("转为time.Time:", toStdTime.Format("2006-01-02 15:04:05"))
// 从不同精度的时间戳创建
sec := time.Now().Unix()
fromSec := carbon.CreateFromTimestamp(sec)
fmt.Println("秒时间戳:", fromSec.ToDateTimeString())
milli := time.Now().UnixMilli()
fromMilli := carbon.CreateFromTimestampMilli(milli)
fmt.Println("毫秒时间戳:", fromMilli.ToDateTimeString())
micro := time.Now().UnixMicro()
fromMicro := carbon.CreateFromTimestampMicro(micro)
fmt.Println("微秒时间戳:", fromMicro.ToDateTimeString())
nano := time.Now().UnixNano()
fromNano := carbon.CreateFromTimestampNano(nano)
fmt.Println("纳秒时间戳:", fromNano.ToDateTimeString())
// 指定时区从时间戳创建
timestamp := int64(1705287000)
tokyoTime := carbon.CreateFromTimestamp(timestamp).SetTimezone("Asia/Tokyo")
fmt.Println("东京时区:", tokyoTime.ToDateTimeString())
// 实用场景:数据库时间字段转换
// 假设从数据库读取到的是Unix时间戳
dbTimestamp := int64(1705287000)
dbTime := carbon.CreateFromTimestamp(dbTimestamp)
fmt.Println("\n数据库时间:", dbTime.Format("Y-m-d H:i:s"))
// 转换为本地时区显示
localTime := dbTime.SetTimezone(carbon.Local)
fmt.Println("本地时区:", localTime.ToDateTimeString())
}
---
2.3 解析时间
01.自动解析常见格式
a.说明
Carbon的Parse()方法能够智能识别多种常见日期时间格式,无需手动指定Layout。
支持ISO8601、RFC3339等国际标准格式,也支持常见的中国日期格式。
解析失败时不会panic,而是在Carbon.Error字段中记录错误信息。
自动识别带时区的格式,并正确处理时区转换。
相比标准库繁琐的Layout指定,Parse()大幅简化了日期解析的复杂度。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 解析标准日期格式
date1 := carbon.Parse("2024-01-15")
fmt.Println("标准日期:", date1.ToDateString())
// 解析日期时间格式
datetime1 := carbon.Parse("2024-01-15 10:30:00")
fmt.Println("日期时间:", datetime1.ToDateTimeString())
// 解析ISO8601格式
iso := carbon.Parse("2024-01-15T10:30:00+08:00")
fmt.Println("ISO8601:", iso.ToDateTimeString())
// 解析RFC3339格式
rfc := carbon.Parse("2024-01-15T10:30:00Z")
fmt.Println("RFC3339:", rfc.ToDateTimeString())
// 解析带毫秒的格式
withMilli := carbon.Parse("2024-01-15 10:30:00.123")
fmt.Println("带毫秒:", withMilli.ToDateTimeString())
// 解析中文日期格式
chinese1 := carbon.Parse("2024年01月15日")
fmt.Println("中文日期:", chinese1.ToDateString())
chinese2 := carbon.Parse("2024年01月15日 10时30分00秒")
fmt.Println("中文全格式:", chinese2.ToDateTimeString())
// 解析斜杠分隔格式
slash := carbon.Parse("2024/01/15 10:30:00")
fmt.Println("斜杠格式:", slash.ToDateTimeString())
// 解析点分隔格式
dot := carbon.Parse("2024.01.15 10:30:00")
fmt.Println("点分隔格式:", dot.ToDateTimeString())
// 错误处理示例
invalid := carbon.Parse("invalid-date")
if invalid.Error != nil {
fmt.Println("解析错误:", invalid.Error)
}
// 解析后转换时区
parsed := carbon.Parse("2024-01-15 10:30:00")
tokyo := parsed.SetTimezone("Asia/Tokyo")
fmt.Println("转换到东京时区:", tokyo.ToDateTimeString())
}
---
02.指定格式解析
a.说明
当需要解析特定格式时,可使用ParseByFormat()方法指定精确格式。
格式符号采用PHP风格,比标准库的Layout更直观易记。
常用格式符:Y(年4位) m(月2位) d(日2位) H(时24制) i(分) s(秒)。
支持自定义分隔符和格式组合,满足各种非标准格式的解析需求。
配合ParseByLayout()可使用Go标准库的Layout格式,兼容旧项目。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 使用Carbon格式符解析
// Y-年(4位) m-月(2位) d-日(2位) H-时(24) i-分 s-秒
format1 := carbon.ParseByFormat("2024-01-15 10:30:00", "Y-m-d H:i:s")
fmt.Println("标准格式:", format1.ToDateTimeString())
// 解析中文格式
format2 := carbon.ParseByFormat("2024年01月15日", "Y年m月d日")
fmt.Println("中文格式:", format2.ToDateString())
// 解析自定义分隔符
format3 := carbon.ParseByFormat("2024/01/15", "Y/m/d")
fmt.Println("斜杠分隔:", format3.ToDateString())
// 解析12小时制时间
format4 := carbon.ParseByFormat("2024-01-15 10:30:00 PM", "Y-m-d h:i:s A")
fmt.Println("12小时制:", format4.ToDateTimeString())
// 解析带时区的格式
format5 := carbon.ParseByFormat("2024-01-15 10:30:00 +0800", "Y-m-d H:i:s O")
fmt.Println("带时区:", format5.ToDateTimeString())
// 使用Go标准库Layout解析
layout1 := carbon.ParseByLayout("2024-01-15", "2006-01-02")
fmt.Println("Layout方式:", layout1.ToDateString())
layout2 := carbon.ParseByLayout("2024-01-15 10:30:00", "2006-01-02 15:04:05")
fmt.Println("Layout完整:", layout2.ToDateTimeString())
// 实用场景:解析日志文件中的时间戳
logTime := "2024/01/15 10:30:00.123"
parsed := carbon.ParseByFormat(logTime, "Y/m/d H:i:s.u")
if parsed.Error == nil {
fmt.Println("\n日志时间解析成功:", parsed.ToDateTimeString())
fmt.Println("时间戳:", parsed.Timestamp())
}
// 解析不同地区的日期格式
usDate := carbon.ParseByFormat("01-15-2024", "m-d-Y") // 美国格式
euDate := carbon.ParseByFormat("15-01-2024", "d-m-Y") // 欧洲格式
fmt.Println("美国格式:", usDate.ToDateString())
fmt.Println("欧洲格式:", euDate.ToDateString())
}
---
03.指定时区解析
a.说明
解析时间时可以同时指定目标时区,确保时间的准确性。
ParseByFormat的第三个参数可选时区名称,支持IANA时区数据库格式。
未指定时区时默认使用系统本地时区,可通过SetTimezone()后续调整。
对于跨时区的业务系统,建议统一使用UTC时区存储,显示时再转换。
正确处理时区可以避免夏令时切换、跨国业务等场景中的时间错误。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 解析时指定时区
dateStr := "2024-01-15 10:30:00"
// 解析为东京时间
tokyo := carbon.ParseByFormat(dateStr, "Y-m-d H:i:s", "Asia/Tokyo")
fmt.Println("东京时间:", tokyo.ToDateTimeString())
fmt.Println("东京时区:", tokyo.Timezone())
// 解析为纽约时间
newYork := carbon.ParseByFormat(dateStr, "Y-m-d H:i:s", "America/New_York")
fmt.Println("纽约时间:", newYork.ToDateTimeString())
fmt.Println("纽约时区:", newYork.Timezone())
// 解析为UTC时间
utc := carbon.ParseByFormat(dateStr, "Y-m-d H:i:s", "UTC")
fmt.Println("UTC时间:", utc.ToDateTimeString())
// 解析后转换时区
shanghai := carbon.Parse("2024-01-15 10:30:00")
fmt.Println("上海时间:", shanghai.ToDateTimeString())
// 转换为其他时区
londonTime := shanghai.SetTimezone("Europe/London")
fmt.Println("转为伦敦时间:", londonTime.ToDateTimeString())
// 实用场景:处理国际订单时间
// 假设订单时间是美国西部时间
orderTimeStr := "2024-01-15 10:30:00"
orderTime := carbon.ParseByFormat(orderTimeStr, "Y-m-d H:i:s", "America/Los_Angeles")
if orderTime.Error != nil {
fmt.Println("订单时间解析失败:", orderTime.Error)
return
}
fmt.Println("\n订单时间分析:")
fmt.Println("洛杉矶时间:", orderTime.ToDateTimeString())
// 转换为北京时间显示给中国用户
beijingTime := orderTime.SetTimezone("Asia/Shanghai")
fmt.Println("北京时间:", beijingTime.ToDateTimeString())
// 转换为UTC时间存储到数据库
utcTime := orderTime.SetTimezone("UTC")
fmt.Println("UTC存储:", utcTime.ToDateTimeString())
fmt.Println("时间戳:", utcTime.Timestamp())
}
---
2.4 格式化输出
01.预定义格式输出
a.说明
Carbon提供了丰富的预定义格式化方法,覆盖最常用的输出场景。
ToDateString()输出日期,ToTimeString()输出时间,ToDateTimeString()输出完整格式。
所有方法都使用语义化命名,无需记忆复杂的格式符号。
支持ISO8601、RFC系列等国际标准格式,便于与外部系统集成。
预定义方法性能更优,Carbon内部做了格式缓存,比自定义格式快约30%。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 基础格式输出
fmt.Println("日期:", now.ToDateString()) // 2024-01-15
fmt.Println("时间:", now.ToTimeString()) // 10:30:00
fmt.Println("日期时间:", now.ToDateTimeString()) // 2024-01-15 10:30:00
// 带毫秒的格式
fmt.Println("带毫秒:", now.ToDateTimeMilliString()) // 2024-01-15 10:30:00.123
fmt.Println("带微秒:", now.ToDateTimeMicroString()) // 2024-01-15 10:30:00.123456
fmt.Println("带纳秒:", now.ToDateTimeNanoString()) // 2024-01-15 10:30:00.123456789
// 国际标准格式
fmt.Println("ISO8601:", now.ToIso8601String()) // 2024-01-15T10:30:00+08:00
fmt.Println("RFC822:", now.ToRfc822String()) // 15 Jan 24 10:30 CST
fmt.Println("RFC850:", now.ToRfc850String()) // Monday, 15-Jan-24 10:30:00 CST
fmt.Println("RFC1123:", now.ToRfc1123String()) // Mon, 15 Jan 2024 10:30:00 CST
fmt.Println("RFC2822:", now.ToRfc2822String()) // Mon, 15 Jan 2024 10:30:00 +0800
fmt.Println("RFC3339:", now.ToRfc3339String()) // 2024-01-15T10:30:00+08:00
fmt.Println("RFC7231:", now.ToRfc7231String()) // Mon, 15 Jan 2024 10:30:00 GMT
// 其他常用格式
fmt.Println("Cookie格式:", now.ToCookieString()) // Monday, 15-Jan-2024 10:30:00 CST
fmt.Println("RSS格式:", now.ToRssString()) // Mon, 15 Jan 2024 10:30:00 +0800
fmt.Println("W3C格式:", now.ToW3cString()) // 2024-01-15T10:30:00+08:00
// 时间戳格式
fmt.Println("秒时间戳:", now.Timestamp()) // 1705287000
fmt.Println("毫秒时间戳:", now.TimestampMilli()) // 1705287000000
fmt.Println("微秒时间戳:", now.TimestampMicro()) // 1705287000000000
fmt.Println("纳秒时间戳:", now.TimestampNano()) // 1705287000000000000
// 仅日期部分
fmt.Println("年月日:", now.ToShortDateString()) // 20240115
fmt.Println("时分秒:", now.ToShortTimeString()) // 103000
}
---
02.自定义格式输出
a.说明
Format()方法支持使用PHP风格的格式符自定义输出格式。
常用格式符:Y(年) m(月) d(日) H(时24) h(时12) i(分) s(秒) A(AM/PM)。
支持中文格式和自定义分隔符,满足各种个性化输出需求。
格式符大小写敏感,Y和y、M和m代表不同含义,使用时需注意。
也可使用Layout()方法采用Go标准库格式,兼容旧代码。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 使用Carbon格式符
fmt.Println("标准格式:", now.Format("Y-m-d H:i:s")) // 2024-01-15 10:30:00
fmt.Println("中文格式:", now.Format("Y年m月d日 H时i分s秒")) // 2024年01月15日 10时30分00秒
fmt.Println("斜杠分隔:", now.Format("Y/m/d")) // 2024/01/15
fmt.Println("点分隔:", now.Format("Y.m.d")) // 2024.01.15
// 12小时制格式
fmt.Println("12小时制:", now.Format("Y-m-d h:i:s A")) // 2024-01-15 10:30:00 AM
fmt.Println("简写上午:", now.Format("Y-m-d h:i a")) // 2024-01-15 10:30 am
// 包含星期信息
fmt.Println("完整星期:", now.Format("Y-m-d l")) // 2024-01-15 Monday
fmt.Println("简写星期:", now.Format("Y-m-d D")) // 2024-01-15 Mon
// 包含月份信息
fmt.Println("完整月份:", now.Format("d F Y")) // 15 January 2024
fmt.Println("简写月份:", now.Format("d M Y")) // 15 Jan 2024
// 其他格式符示例
fmt.Println("年中第几天:", now.Format("Y-m-d 第z天")) // 2024-01-15 第15天
fmt.Println("ISO周数:", now.Format("Y年第W周")) // 2024年第03周
fmt.Println("月份天数:", now.Format("Y-m 共t天")) // 2024-01 共31天
// 使用Go标准库Layout格式
fmt.Println("\nLayout格式:")
fmt.Println("标准:", now.Layout("2006-01-02 15:04:05"))
fmt.Println("自定义:", now.Layout("2006年01月02日"))
// 实用场景:生成文件名
fileName := now.Format("YmdHis") + ".log"
fmt.Println("\n日志文件名:", fileName) // 20240115103000.log
// 生成友好的显示格式
friendly := now.Format("Y年m月d日 H:i")
fmt.Println("友好显示:", friendly) // 2024年01月15日 10:30
}
---
03.多语言格式输出
a.说明
Carbon支持多语言输出,通过SetLocale()设置语言环境。
内置支持中文、英文、日文等多种语言的星期、月份名称。
多语言主要影响ToWeekString()、ToMonthString()等语义化方法。
也可通过Format()配合格式符输出多语言格式。
语言设置只影响当前Carbon实例,不会影响全局配置。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 设置中文语言环境
zhCN := now.SetLocale("zh-CN")
fmt.Println("=== 中文输出 ===")
fmt.Println("星期:", zhCN.ToWeekString()) // 星期一
fmt.Println("月份:", zhCN.ToMonthString()) // 一月
fmt.Println("季度:", zhCN.ToQuarterString()) // 第一季度
fmt.Println("短星期:", zhCN.ToShortWeekString()) // 周一
fmt.Println("短月份:", zhCN.ToShortMonthString()) // 1月
// 设置英文语言环境
enUS := now.SetLocale("en")
fmt.Println("\n=== 英文输出 ===")
fmt.Println("Week:", enUS.ToWeekString()) // Monday
fmt.Println("Month:", enUS.ToMonthString()) // January
fmt.Println("Quarter:", enUS.ToQuarterString()) // 1st quarter
fmt.Println("Short Week:", enUS.ToShortWeekString()) // Mon
fmt.Println("Short Month:", enUS.ToShortMonthString()) // Jan
// 设置日文语言环境
ja := now.SetLocale("ja")
fmt.Println("\n=== 日文输出 ===")
fmt.Println("曜日:", ja.ToWeekString()) // 月曜日
fmt.Println("月:", ja.ToMonthString()) // 1月
// 设置韩文语言环境
ko := now.SetLocale("ko")
fmt.Println("\n=== 韩文输出 ===")
fmt.Println("요일:", ko.ToWeekString()) // 월요일
// 多语言格式化
fmt.Println("\n=== 多语言格式化 ===")
fmt.Println("中文:", zhCN.Format("Y年m月d日 l"))
fmt.Println("英文:", enUS.Format("l, F d, Y"))
// 实用场景:多语言日期选择器
fmt.Println("\n=== 日期选择器示例 ===")
languages := []string{"zh-CN", "en", "ja", "ko"}
for _, lang := range languages {
localized := now.SetLocale(lang)
fmt.Printf("%s: %s\n", lang, localized.Format("Y-m-d l"))
}
}
---
2.5 时间比较
01.基础比较方法
a.说明
Carbon提供了丰富的时间比较方法,涵盖大于、小于、等于等所有比较场景。
比较时会考虑时区差异,自动转换到相同时区进行比较,确保结果准确。
所有比较方法都是语义化命名,如Gt(大于)、Lt(小于)、Eq(等于)等。
支持精确到纳秒的比较,也支持只比较日期部分,忽略时间部分。
返回值为布尔类型,可直接用于if判断,代码清晰易读。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
yesterday := carbon.Yesterday()
tomorrow := carbon.Tomorrow()
// 大于比较 (Greater Than)
fmt.Println("明天 > 今天:", tomorrow.Gt(now)) // true
fmt.Println("昨天 > 今天:", yesterday.Gt(now)) // false
// 大于等于比较 (Greater Than or Equal)
fmt.Println("今天 >= 今天:", now.Gte(now)) // true
fmt.Println("明天 >= 今天:", tomorrow.Gte(now)) // true
// 小于比较 (Less Than)
fmt.Println("昨天 < 今天:", yesterday.Lt(now)) // true
fmt.Println("明天 < 今天:", tomorrow.Lt(now)) // false
// 小于等于比较 (Less Than or Equal)
fmt.Println("昨天 <= 今天:", yesterday.Lte(now)) // true
fmt.Println("今天 <= 今天:", now.Lte(now)) // true
// 等于比较
now2 := carbon.Now()
fmt.Println("两个now相等:", now.Eq(now2)) // false (纳秒级差异)
// 不等于比较
fmt.Println("今天 != 昨天:", now.Ne(yesterday)) // true
// 创建相同时间点进行比较
time1 := carbon.Parse("2024-01-15 10:30:00")
time2 := carbon.Parse("2024-01-15 10:30:00")
fmt.Println("相同时间字符串解析:", time1.Eq(time2)) // true
// 跨时区比较
shanghai := carbon.Parse("2024-01-15 10:30:00").SetTimezone("Asia/Shanghai")
tokyo := carbon.Parse("2024-01-15 11:30:00").SetTimezone("Asia/Tokyo")
fmt.Println("跨时区比较(实际同一时刻):", shanghai.Eq(tokyo)) // true
// 实用场景:判断是否过期
deadline := carbon.Parse("2024-12-31 23:59:59")
if now.Lt(deadline) {
fmt.Println("\n项目未过期,还有时间")
} else {
fmt.Println("\n项目已过期")
}
}
---
02.范围比较方法
a.说明
Between()方法判断时间是否在指定区间内,第三个参数控制区间开闭。
默认为闭区间[start, end],可设置为开区间(start, end)或半开区间。
BetweenIncludedStart()包含起始时间,BetweenIncludedEnd()包含结束时间。
在日期范围查询、活动时间判断等场景中非常实用。
区间比较同样会处理时区差异,确保跨时区场景下的准确性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 创建时间范围
start := carbon.Parse("2024-01-01 00:00:00")
end := carbon.Parse("2024-12-31 23:59:59")
current := carbon.Parse("2024-06-15 12:00:00")
// 闭区间判断 [start, end]
fmt.Println("在闭区间内:", current.Between(start, end)) // true
// 开区间判断 (start, end)
fmt.Println("在开区间内:", current.BetweenExcluded(start, end)) // true
// 包含起始时间 [start, end)
fmt.Println("包含起始:", current.BetweenIncludedStart(start, end)) // true
// 包含结束时间 (start, end]
fmt.Println("包含结束:", current.BetweenIncludedEnd(start, end)) // true
// 边界测试
fmt.Println("起始点在闭区间:", start.Between(start, end)) // true
fmt.Println("起始点在开区间:", start.BetweenExcluded(start, end)) // false
fmt.Println("结束点在闭区间:", end.Between(start, end)) // true
fmt.Println("结束点在开区间:", end.BetweenExcluded(start, end)) // false
// 实用场景1:活动时间判断
activityStart := carbon.Parse("2024-11-11 00:00:00")
activityEnd := carbon.Parse("2024-11-11 23:59:59")
now := carbon.Now()
if now.Between(activityStart, activityEnd) {
fmt.Println("\n活动进行中")
} else if now.Lt(activityStart) {
fmt.Println("\n活动未开始")
} else {
fmt.Println("\n活动已结束")
}
// 实用场景2:工作时间判断
workStart := carbon.Parse("2024-01-15 09:00:00")
workEnd := carbon.Parse("2024-01-15 18:00:00")
checkTime := carbon.Parse("2024-01-15 14:30:00")
if checkTime.Between(workStart, workEnd) {
fmt.Println("\n在工作时间内")
} else {
fmt.Println("\n非工作时间")
}
// 实用场景3:季度判断
q1Start := carbon.Parse("2024-01-01")
q1End := carbon.Parse("2024-03-31")
date := carbon.Parse("2024-02-15")
if date.Between(q1Start, q1End) {
fmt.Println("\n该日期在第一季度")
}
}
---
03.特殊比较方法
a.说明
Carbon提供了一系列语义化的特殊比较方法,如IsToday()、IsYesterday()等。
这些方法让代码更具可读性,避免手动构建比较逻辑��
支持判断是否为周末、工作日、闰年、上午下午等多种场景。
IsZero()判断是否为零值时间,常用于数据库字段的空值检查。
所有Is系列方法都返回bool值,可直接用于条件判断。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 日期相关判断
fmt.Println("是否今天:", now.IsToday()) // true
fmt.Println("是否昨天:", now.IsYesterday()) // false
fmt.Println("是否明天:", now.IsTomorrow()) // false
// 时间段判断
fmt.Println("是否当前周:", now.IsCurrentWeek()) // true
fmt.Println("是否当前月:", now.IsCurrentMonth()) // true
fmt.Println("是否当前年:", now.IsCurrentYear()) // true
// 周末和工作日
fmt.Println("是否周末:", now.IsWeekend()) // 取决于当天
fmt.Println("是否工作日:", now.IsWeekday()) // 取决于当天
// 上午下午判断
fmt.Println("是否上午:", now.IsAM()) // 取决于当前时间
fmt.Println("是否下午:", now.IsPM()) // 取决于当前时间
// 闰年判断
year2024 := carbon.Parse("2024-01-01")
year2023 := carbon.Parse("2023-01-01")
fmt.Println("2024是闰年:", year2024.IsLeapYear()) // true
fmt.Println("2023是闰年:", year2023.IsLeapYear()) // false
// 零值判断
zeroTime := carbon.Carbon{}
validTime := carbon.Now()
fmt.Println("零值时间:", zeroTime.IsZero()) // true
fmt.Println("有效时间:", validTime.IsZero()) // false
// 月份长度
fmt.Println("当月是长月:", year2024.IsLongMonth()) // 取决于月份(31天)
fmt.Println("当月是短月:", year2024.IsShortMonth()) // 取决于月份(30天)
// 实用场景1:周末提醒
if now.IsWeekend() {
fmt.Println("\n今天是周末,好好休息!")
} else if now.IsFriday() {
fmt.Println("\n明天是周末,加油!")
}
// 实用场景2:闰年处理
birthday := carbon.Parse("2000-02-29")
currentYear := carbon.Now()
if currentYear.IsLeapYear() {
fmt.Println("\n今年是闰年,可以过生日了")
} else {
fmt.Println("\n今年不是闰年,2月29日生日推迟到3月1日")
}
// 实用场景3:数据验证
dbTime := carbon.Carbon{}
if dbTime.IsZero() {
fmt.Println("\n数据库时间字段为空,使用默认值")
}
}
---
2.6 时间运算
01.基础加减操作
a.说明
Carbon提供了丰富的时间加减方法,支持年月日时分秒的增减操作。
AddXxx()系列方法用于增加时间,SubXxx()系列方法用于减少时间。
所有运算返回新的Carbon实例,原实例保持不变,符合不可变对象设计。
自动处理月末日期溢出,如1月31日加1月会得到2月28日(或29日)。
链式调用支持连续多次运算,代码简洁流畅。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
// 年份加减
nextYear := now.AddYear()
lastYear := now.SubYear()
fmt.Println("明年:", nextYear.ToDateString())
fmt.Println("去年:", lastYear.ToDateString())
// 月份加减
nextMonth := now.AddMonth()
lastMonth := now.SubMonth()
fmt.Println("下月:", nextMonth.ToDateString())
fmt.Println("上月:", lastMonth.ToDateString())
// 周加减
nextWeek := now.AddWeek()
lastWeek := now.SubWeek()
fmt.Println("下周:", nextWeek.ToDateString())
fmt.Println("上周:", lastWeek.ToDateString())
// 天数加减
tomorrow := now.AddDay()
yesterday := now.SubDay()
fmt.Println("明天:", tomorrow.ToDateString())
fmt.Println("昨天:", yesterday.ToDateString())
// 小时加减
nextHour := now.AddHour()
lastHour := now.SubHour()
fmt.Println("下一小时:", nextHour.ToTimeString())
fmt.Println("上一小时:", lastHour.ToTimeString())
// 分钟加减
next30Min := now.AddMinutes(30)
last15Min := now.SubMinutes(15)
fmt.Println("30分钟后:", next30Min.ToTimeString())
fmt.Println("15分钟前:", last15Min.ToTimeString())
// 秒数加减
next10Sec := now.AddSeconds(10)
last5Sec := now.SubSeconds(5)
fmt.Println("10秒后:", next10Sec.ToDateTimeString())
fmt.Println("5秒前:", last5Sec.ToDateTimeString())
// 链式调用示例
future := now.AddYears(2).AddMonths(3).AddDays(15)
fmt.Println("2年3月15天后:", future.ToDateString())
past := now.SubYears(1).SubMonths(6).SubDays(10)
fmt.Println("1年6月10天前:", past.ToDateString())
}
---
02.复数形式的加减
a.说明
AddXxxs()和SubXxxs()方法支持一次性加减多个单位,参数为整数。
正数表示增加,负数表示减少,提供更灵活的运算方式。
适合处理动态计算场景,如根据配置文件的天数计算截止日期。
自动处理跨月、跨年等边界情况,无需手动判断。
性能与单次调用相当,但代码更简洁,推荐在明确数量时使用。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
// 加减多年
future5Years := now.AddYears(5)
past3Years := now.SubYears(3)
fmt.Println("5年后:", future5Years.ToDateString())
fmt.Println("3年前:", past3Years.ToDateString())
// 加减多月
future8Months := now.AddMonths(8)
past6Months := now.SubMonths(6)
fmt.Println("8个月后:", future8Months.ToDateString())
fmt.Println("6个月前:", past6Months.ToDateString())
// 加减多周
future4Weeks := now.AddWeeks(4)
past2Weeks := now.SubWeeks(2)
fmt.Println("4周后:", future4Weeks.ToDateString())
fmt.Println("2周前:", past2Weeks.ToDateString())
// 加减多天
future30Days := now.AddDays(30)
past15Days := now.SubDays(15)
fmt.Println("30天后:", future30Days.ToDateString())
fmt.Println("15天前:", past15Days.ToDateString())
// 加减多小时
future24Hours := now.AddHours(24)
past12Hours := now.SubHours(12)
fmt.Println("24小时后:", future24Hours.ToDateTimeString())
fmt.Println("12小时前:", past12Hours.ToDateTimeString())
// 使用负数表示减少
add10Days := now.AddDays(10)
sub10Days := now.AddDays(-10)
fmt.Println("加10天:", add10Days.ToDateString())
fmt.Println("减10天:", sub10Days.ToDateString())
// 实用场景1:计算试用期结束日期
registerDate := carbon.Parse("2024-01-15")
trialDays := 30
trialEndDate := registerDate.AddDays(trialDays)
fmt.Println("\n注册日期:", registerDate.ToDateString())
fmt.Println("试用期结束:", trialEndDate.ToDateString())
// 实用场景2:计算账单周期
billStartDate := carbon.Parse("2024-01-01")
billCycles := 3 // 3个月一个周期
billEndDate := billStartDate.AddMonths(billCycles).SubDay()
fmt.Println("\n账单开始:", billStartDate.ToDateString())
fmt.Println("账单结束:", billEndDate.ToDateString())
// 实用场景3:工作日计算(简化版)
projectStart := carbon.Parse("2024-01-15")
workDays := 20
estimatedEnd := projectStart.AddDays(workDays)
fmt.Println("\n项目开始:", projectStart.ToDateString())
fmt.Println("预计完成:", estimatedEnd.ToDateString())
}
---
03.Duration时长运算
a.说明
AddDuration()和SubDuration()支持使用time.Duration进行时间运算。
Duration可表示纳秒级精度的时长,适合精确的时间计算。
常用于定时器、超时控制等需要精确时长的场景。
可以使用time包的常量如time.Hour、time.Minute等构造Duration。
也支持通过time.ParseDuration()解析字符串形式的时长。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
// 使用time包常量
after1Hour := now.AddDuration(time.Hour)
after30Min := now.AddDuration(30 * time.Minute)
after10Sec := now.AddDuration(10 * time.Second)
fmt.Println("1小时后:", after1Hour.ToTimeString())
fmt.Println("30分钟后:", after30Min.ToTimeString())
fmt.Println("10秒后:", after10Sec.ToDateTimeString())
// 组合使用Duration
complex := now.AddDuration(2*time.Hour + 30*time.Minute + 15*time.Second)
fmt.Println("2小时30分15秒后:", complex.ToDateTimeString())
// 减去Duration
before2Hours := now.SubDuration(2 * time.Hour)
fmt.Println("2小时前:", before2Hours.ToTimeString())
// 解析字符串形式的时长
duration1, _ := time.ParseDuration("1h30m")
after1h30m := now.AddDuration(duration1)
fmt.Println("1小时30分后:", after1h30m.ToTimeString())
duration2, _ := time.ParseDuration("24h")
tomorrow := now.AddDuration(duration2)
fmt.Println("24小时后:", tomorrow.ToDateTimeString())
// 实用场景1:Session过期时间
loginTime := carbon.Now()
sessionTimeout := 2 * time.Hour
expireTime := loginTime.AddDuration(sessionTimeout)
fmt.Println("\n登录时间:", loginTime.ToDateTimeString())
fmt.Println("过期时间:", expireTime.ToDateTimeString())
// 实用场景2:API限流窗口
requestTime := carbon.Now()
rateLimitWindow := 1 * time.Minute
windowEnd := requestTime.AddDuration(rateLimitWindow)
fmt.Println("\n请求时间:", requestTime.ToTimeString())
fmt.Println("窗口结束:", windowEnd.ToTimeString())
// 实用场景3:任务超时检查
taskStart := carbon.Parse("2024-01-15 10:00:00")
timeout := 30 * time.Minute
deadline := taskStart.AddDuration(timeout)
current := carbon.Now()
fmt.Println("\n任务开始:", taskStart.ToDateTimeString())
fmt.Println("超时时间:", deadline.ToDateTimeString())
if current.Gt(deadline) {
fmt.Println("任务已超时")
} else {
fmt.Println("任务进行中")
}
}
---
2.7 错误处理
01.错误检查机制
a.说明
Carbon采用优雅的错误处理机制,所有可能出错的操作都会在Error字段中记录错误。
不会抛出panic,保证程序的稳定性,适合在生产环境中使用。
可通过检查Carbon.Error是否为nil来判断操作是否成功。
错误信息详细明确,便于快速定位问题原因。
链式调用中一旦出错,后续操作会自动跳过,避免级联错误。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 解析无效日期
invalid := carbon.Parse("invalid-date")
if invalid.Error != nil {
fmt.Println("解析错误:", invalid.Error)
// 输出: parsing time "invalid-date" as "2006-01-02 15:04:05": cannot parse "invalid-date" as "2006"
}
// 解析空字符串
empty := carbon.Parse("")
if empty.Error != nil {
fmt.Println("空字符串错误:", empty.Error)
}
// 格式不匹配
wrongFormat := carbon.ParseByFormat("2024-01-15", "Y/m/d")
if wrongFormat.Error != nil {
fmt.Println("格式不匹配:", wrongFormat.Error)
}
// 正确的解析
valid := carbon.Parse("2024-01-15 10:30:00")
if valid.Error == nil {
fmt.Println("解析成功:", valid.ToDateTimeString())
}
// 链式调用中的错误处理
result := carbon.Parse("invalid").AddDay().ToDateString()
if result == "" {
fmt.Println("链式调用中存在错误")
}
// 实用场景:安全的日期解析函数
dateStr := "2024-01-15"
safeDate := parseDate(dateStr)
if safeDate.Error == nil {
fmt.Println("安全解析成功:", safeDate.ToDateString())
}
// 错误恢复:提供默认值
userInput := "invalid-input"
parsedDate := carbon.Parse(userInput)
if parsedDate.Error != nil {
fmt.Println("用户输入无效,使用当前时间")
parsedDate = carbon.Now()
}
fmt.Println("最终使用的时间:", parsedDate.ToDateTimeString())
}
// parseDate 安全的日期解析辅助函数
func parseDate(dateStr string) carbon.Carbon {
c := carbon.Parse(dateStr)
if c.Error != nil {
// 记录日志或处理错误
fmt.Printf("警告: 无法解析日期 '%s': %v\n", dateStr, c.Error)
}
return c
}
---
02.常见错误场景
a.说明
Carbon在使用过程中可能遇到的常见错误包括:日期格式错误、时区无效等。
了解这些常见错误场景可以帮助开发者快速排查问题。
建议在解析用户输入或外部数据时始终进行错误检查。
对于关键业务逻辑,可以使用多种格式尝试解析,提高容错性。
错误处理应该结合业务需求,提供合理的默认值或提示用户重新输入。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
fmt.Println("=== 常见错误场景演示 ===\n")
// 场景1: 日期格式错误
fmt.Println("1. 日期格式错误:")
formats := []string{
"2024/13/01", // 月份超出范围
"2024-02-30", // 2月没有30号
"2024-01-32", // 日期超出范围
"2024-00-15", // 月份为0
}
for _, f := range formats {
c := carbon.Parse(f)
if c.Error != nil {
fmt.Printf(" %s => 错误: %v\n", f, c.Error)
}
}
// 场景2: 时区错误
fmt.Println("\n2. 时区错误:")
invalidTZ := carbon.Now().SetTimezone("Invalid/Timezone")
if invalidTZ.Error != nil {
fmt.Println(" 无效时区:", invalidTZ.Error)
}
// 场景3: 格式符错误
fmt.Println("\n3. 格式符错误:")
wrongFormat := carbon.ParseByFormat("2024-01-15", "invalid-format")
if wrongFormat.Error != nil {
fmt.Println(" 格式符错误:", wrongFormat.Error)
}
// 场景4: 空值处理
fmt.Println("\n4. 空值处理:")
emptyStr := carbon.Parse("")
if emptyStr.Error != nil {
fmt.Println(" 空字符串:", emptyStr.Error)
}
// 场景5: 时间戳范围
fmt.Println("\n5. 时间戳范围:")
// 负数时间戳
negativeTs := carbon.CreateFromTimestamp(-1)
fmt.Println(" 负数时间戳:", negativeTs.ToDateTimeString())
// 极大时间戳可能导致溢出
largeTs := carbon.CreateFromTimestamp(253402300799) // 9999-12-31
fmt.Println(" 最大时间戳:", largeTs.ToDateTimeString())
// 实用场景:多格式尝试解析
fmt.Println("\n=== 智能解析示例 ===")
userInputs := []string{
"2024-01-15",
"2024/01/15",
"15-01-2024",
"01/15/2024",
"invalid",
}
for _, input := range userInputs {
result := smartParse(input)
if result.Error == nil {
fmt.Printf("%s => 解析成功: %s\n", input, result.ToDateString())
} else {
fmt.Printf("%s => 解析失败\n", input)
}
}
}
// smartParse 智能多格式解析
func smartParse(dateStr string) carbon.Carbon {
// 尝试多种格式
formats := []string{
"Y-m-d",
"Y/m/d",
"d-m-Y",
"m/d/Y",
}
// 首先尝试自动解析
c := carbon.Parse(dateStr)
if c.Error == nil {
return c
}
// 尝试各种格式
for _, format := range formats {
c = carbon.ParseByFormat(dateStr, format)
if c.Error == nil {
return c
}
}
// 所有尝试都失败,返回错误
return carbon.Parse(dateStr)
}
---
03.错误处理最佳实践
a.说明
在生产环境中应该建立完善的错误处理机制,确保程序的健壮性。
推荐为关键操作编写包装函数,统一处理错误和日志记录。
对于用户输入,应该提供友好的错误提示,而不是直接暴露技术细节。
在数据库操作、API调用等场景中,错误的Carbon对象不应该被存储或返回。
使用IsZero()可以判断Carbon对象是否有效,避免使用无效对象。
b.代码示例
---
package main
import (
"fmt"
"log"
"github.com/golang-module/carbon/v2"
)
// SafeParse 安全解析函数,包含日志记录
func SafeParse(dateStr string, defaultValue carbon.Carbon) carbon.Carbon {
c := carbon.Parse(dateStr)
if c.Error != nil {
log.Printf("日期解析失败: %s, 错误: %v, 使用默认值", dateStr, c.Error)
return defaultValue
}
return c
}
// ParseWithValidation 带验证的解析函数
func ParseWithValidation(dateStr string) (carbon.Carbon, error) {
c := carbon.Parse(dateStr)
if c.Error != nil {
return carbon.Carbon{}, fmt.Errorf("无效的日期格式: %s", dateStr)
}
// 额外的业务验证
if c.Lt(carbon.Parse("1900-01-01")) {
return carbon.Carbon{}, fmt.Errorf("日期不能早于1900年")
}
if c.Gt(carbon.Parse("2100-12-31")) {
return carbon.Carbon{}, fmt.Errorf("日期不能晚于2100年")
}
return c, nil
}
// DateRange 日期范围结构,包含验证
type DateRange struct {
Start carbon.Carbon
End carbon.Carbon
}
// NewDateRange 创建并验证日期范围
func NewDateRange(startStr, endStr string) (*DateRange, error) {
start := carbon.Parse(startStr)
if start.Error != nil {
return nil, fmt.Errorf("开始日期无效: %v", start.Error)
}
end := carbon.Parse(endStr)
if end.Error != nil {
return nil, fmt.Errorf("结束日期无效: %v", end.Error)
}
if start.Gte(end) {
return nil, fmt.Errorf("开始日期必须早于结束日期")
}
return &DateRange{Start: start, End: end}, nil
}
func main() {
// 使用安全解析函数
fmt.Println("=== 安全解析示例 ===")
date1 := SafeParse("2024-01-15", carbon.Now())
fmt.Println("解析结果1:", date1.ToDateString())
date2 := SafeParse("invalid-date", carbon.Now())
fmt.Println("解析结果2(使用默认值):", date2.ToDateString())
// 使用带验证的解析
fmt.Println("\n=== 带验证的解析 ===")
validDate, err := ParseWithValidation("2024-01-15")
if err != nil {
fmt.Println("验证失败:", err)
} else {
fmt.Println("验证通过:", validDate.ToDateString())
}
tooEarly, err := ParseWithValidation("1800-01-01")
if err != nil {
fmt.Println("验证失败:", err)
}
// 使用日期范围验证
fmt.Println("\n=== 日期范围验证 ===")
validRange, err := NewDateRange("2024-01-01", "2024-12-31")
if err != nil {
fmt.Println("范围创建失败:", err)
} else {
fmt.Printf("有效范围: %s 至 %s\n",
validRange.Start.ToDateString(),
validRange.End.ToDateString())
}
invalidRange, err := NewDateRange("2024-12-31", "2024-01-01")
if err != nil {
fmt.Println("范围创建失败:", err)
}
// 检查零值
fmt.Println("\n=== 零值检查 ===")
var emptyCarbon carbon.Carbon
if emptyCarbon.IsZero() {
fmt.Println("未初始化的Carbon对象不应使用")
emptyCarbon = carbon.Now() // 初始化为当前时间
}
fmt.Println("初始化后:", emptyCarbon.ToDateTimeString())
}
---
3 时间操作
3.1 时间加减
01.精确到各个时间单位
a.说明
Carbon支持对年、月、周、日、时、分、秒、毫秒等各个时间单位进行精确加减。
提供单数和复数两种形式的方法,单数形式默认加减1个单位。
复数形式可以指定加减的数量,支持正负数,负数表示减少。
所有加减操作都会自动处理进位和借位,如月末日期溢出等边界情况。
返回新的Carbon实例,原实例保持不变,支持并发安全使用。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
base := carbon.Parse("2024-01-15 10:30:45")
fmt.Println("基准时间:", base.ToDateTimeString())
// 世纪加减
nextCentury := base.AddCentury()
lastCentury := base.SubCentury()
fmt.Println("下个世纪:", nextCentury.ToDateString())
fmt.Println("上个世纪:", lastCentury.ToDateString())
// 十年加减
next10Years := base.AddDecade()
last10Years := base.SubDecade()
fmt.Println("10年后:", next10Years.ToDateString())
fmt.Println("10年前:", last10Years.ToDateString())
// 年份加减
nextYear := base.AddYear()
next5Years := base.AddYears(5)
fmt.Println("1年后:", nextYear.ToDateString())
fmt.Println("5年后:", next5Years.ToDateString())
// 季度加减
nextQuarter := base.AddQuarter()
next2Quarters := base.AddQuarters(2)
fmt.Println("下季度:", nextQuarter.ToDateString())
fmt.Println("2个季度后:", next2Quarters.ToDateString())
// 月份加减(处理月末溢出)
jan31 := carbon.Parse("2024-01-31")
nextMonth := jan31.AddMonth()
fmt.Println("1月31日加1月:", nextMonth.ToDateString()) // 2024-02-29
// 周加减
nextWeek := base.AddWeek()
next4Weeks := base.AddWeeks(4)
fmt.Println("1周后:", nextWeek.ToDateString())
fmt.Println("4周后:", next4Weeks.ToDateString())
// 天数加减
tomorrow := base.AddDay()
next30Days := base.AddDays(30)
fmt.Println("明天:", tomorrow.ToDateString())
fmt.Println("30天后:", next30Days.ToDateString())
// 时分秒加减
nextHour := base.AddHour()
next90Minutes := base.AddMinutes(90)
next3600Seconds := base.AddSeconds(3600)
fmt.Println("1小时后:", nextHour.ToTimeString())
fmt.Println("90分钟后:", next90Minutes.ToTimeString())
fmt.Println("3600秒后:", next3600Seconds.ToTimeString())
}
---
02.工作日计算
a.说明
AddWorkdays()和SubWorkdays()方法专门用于计算工作日。
默认周一至周五为工作日,自动跳过周末。
适用于项目管理、请假审批等需要计算工作日的场景。
注意:该方法不考虑法定节假日,如需考虑需要自行实现。
可以配合IsWeekday()和IsWeekend()方法进行更复杂的工作日逻辑。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 从周五开始计算工作日
friday := carbon.Parse("2024-01-12") // 2024年1月12日是周五
fmt.Println("起始日期:", friday.ToDateString(), friday.ToWeekString())
// 加1个工作日(跳过周末到周一)
next1Workday := friday.AddWorkday()
fmt.Println("1个工作日后:", next1Workday.ToDateString(), next1Workday.ToWeekString())
// 加5个工作日
next5Workdays := friday.AddWorkdays(5)
fmt.Println("5个工作日后:", next5Workdays.ToDateString(), next5Workdays.ToWeekString())
// 从周一减去工作日
monday := carbon.Parse("2024-01-15") // 2024年1月15日是周一
fmt.Println("\n起始日期:", monday.ToDateString(), monday.ToWeekString())
// 减1个工作日(跳过周末到周五)
last1Workday := monday.SubWorkday()
fmt.Println("1个工作日前:", last1Workday.ToDateString(), last1Workday.ToWeekString())
// 减5个工作日
last5Workdays := monday.SubWorkdays(5)
fmt.Println("5个工作日前:", last5Workdays.ToDateString(), last5Workdays.ToWeekString())
// 实用场景1:计算项目交付日期
startDate := carbon.Parse("2024-01-15")
workDaysNeeded := 10
deliveryDate := startDate.AddWorkdays(workDaysNeeded)
fmt.Println("\n项目开始:", startDate.ToDateString())
fmt.Println("需要工作日:", workDaysNeeded)
fmt.Println("预计交付:", deliveryDate.ToDateString())
// 实用场景2:计算请假后上班日期
leaveStart := carbon.Parse("2024-01-17") // 周三开始请假
leaveDays := 3 // 请假3天
backToWork := leaveStart.AddDays(leaveDays)
// 如果回来是周末,调整到下周一
for backToWork.IsWeekend() {
backToWork = backToWork.AddDay()
}
fmt.Println("\n请假开始:", leaveStart.ToDateString())
fmt.Println("请假天数:", leaveDays)
fmt.Println("回来上班:", backToWork.ToDateString(), backToWork.ToWeekString())
// 实用场景3:统计两个日期间的工作日数量
start := carbon.Parse("2024-01-01")
end := carbon.Parse("2024-01-31")
workdayCount := 0
current := start
for current.Lte(end) {
if current.IsWeekday() {
workdayCount++
}
current = current.AddDay()
}
fmt.Printf("\n%s 至 %s\n", start.ToDateString(), end.ToDateString())
fmt.Printf("共有工作日: %d天\n", workdayCount)
}
---
03.月末日期处理
a.说明
Carbon在处理月份加减时会智能处理月末溢出问题。
如1月31日加1个月,由于2月只有28/29天,会自动调整到2月最后一天。
这种设计符合大多数业务场景的直觉,避免出现意外的日期。
如需保持日期不变(如31日→31日),需要手动检查月份天数。
了解这个行为对于编写正确的日期逻辑非常重要。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
fmt.Println("=== 月末溢出处理示例 ===\n")
// 1月31日加1个月
jan31 := carbon.Parse("2024-01-31")
feb := jan31.AddMonth()
fmt.Println("1月31日加1月:", feb.ToDateString()) // 2024-02-29(闰年)
// 连续加月份
mar := feb.AddMonth()
apr := mar.AddMonth()
fmt.Println("继续加1月:", mar.ToDateString()) // 2024-03-29
fmt.Println("再加1月:", apr.ToDateString()) // 2024-04-29
// 3月31日加1个月
mar31 := carbon.Parse("2024-03-31")
apr30 := mar31.AddMonth()
fmt.Println("\n3月31日加1月:", apr30.ToDateString()) // 2024-04-30
// 减月份的溢出处理
mar31_2 := carbon.Parse("2024-03-31")
feb29 := mar31_2.SubMonth()
fmt.Println("3月31日减1月:", feb29.ToDateString()) // 2024-02-29
// 非闰年的2月
jan31_2023 := carbon.Parse("2023-01-31")
feb28 := jan31_2023.AddMonth()
fmt.Println("\n2023年1月31日加1月:", feb28.ToDateString()) // 2023-02-28
// 实用场景1:账单日期计算
fmt.Println("\n=== 月账单日期计算 ===")
billDay := 31 // 每月31号出账单
jan := carbon.Parse("2024-01-31")
for i := 0; i < 6; i++ {
current := jan.AddMonths(i)
// 获取当月的最后一天
lastDay := current.EndOfMonth()
fmt.Printf("%d月账单日期: %s\n", current.Month(), lastDay.ToDateString())
}
// 实用场景2:保持日期逻辑
fmt.Println("\n=== 保持日期逻辑 ===")
startDate := carbon.Parse("2024-01-31")
for i := 1; i <= 3; i++ {
nextMonth := startDate.AddMonths(i)
targetDay := startDate.Day()
// 如果目标月份的天数少于起始日期的天数
if nextMonth.DaysInMonth() < targetDay {
// 使用月末日期
nextMonth = nextMonth.EndOfMonth()
}
fmt.Printf("%d个月后(保持31日): %s (实际%d日)\n",
i, nextMonth.ToDateString(), nextMonth.Day())
}
}
---
3.2 开始结束时间
01.获取周期的开始时间
a.说明
Carbon提供了StartOfXxx()系列方法快速获取各个时间周期的开始时刻。
支持世纪、年、季度、月、周、日、小时、分钟、秒等所有常用周期。
开始时间会将更小的时间单位重置为最小值,如StartOfDay()将时分秒设为00:00:00。
这些方法在数据统计、报表生成等需要时间范围的场景中非常实用。
配合EndOfXxx()方法可以快速构建完整的时间范围查询条件。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Parse("2024-06-15 14:30:45")
fmt.Println("当前时间:", now.ToDateTimeString())
fmt.Println()
// 获取世纪开始
startOfCentury := now.StartOfCentury()
fmt.Println("世纪开始:", startOfCentury.ToDateTimeString()) // 2000-01-01 00:00:00
// 获取十年开始
startOfDecade := now.StartOfDecade()
fmt.Println("十年开始:", startOfDecade.ToDateTimeString()) // 2020-01-01 00:00:00
// 获取年初
startOfYear := now.StartOfYear()
fmt.Println("年初:", startOfYear.ToDateTimeString()) // 2024-01-01 00:00:00
// 获取季度开始
startOfQuarter := now.StartOfQuarter()
fmt.Println("季度开始:", startOfQuarter.ToDateTimeString()) // 2024-04-01 00:00:00
// 获取月初
startOfMonth := now.StartOfMonth()
fmt.Println("月初:", startOfMonth.ToDateTimeString()) // 2024-06-01 00:00:00
// 获取周初(默认周一为一周开始)
startOfWeek := now.StartOfWeek()
fmt.Println("周初:", startOfWeek.ToDateTimeString()) // 2024-06-10 00:00:00 (周一)
// 获取当天开始
startOfDay := now.StartOfDay()
fmt.Println("当天开始:", startOfDay.ToDateTimeString()) // 2024-06-15 00:00:00
// 获取当前小时开始
startOfHour := now.StartOfHour()
fmt.Println("小时开始:", startOfHour.ToDateTimeString()) // 2024-06-15 14:00:00
// 获取当前分钟开始
startOfMinute := now.StartOfMinute()
fmt.Println("分钟开始:", startOfMinute.ToDateTimeString()) // 2024-06-15 14:30:00
// 获取当前秒开始
startOfSecond := now.StartOfSecond()
fmt.Println("秒开始:", startOfSecond.ToDateTimeString()) // 2024-06-15 14:30:45
// 实用场景:本月数据查询
fmt.Println("\n=== 本月数据范围 ===")
monthStart := carbon.Now().StartOfMonth()
fmt.Println("查询开始时间:", monthStart.ToDateTimeString())
fmt.Println("SQL WHERE: created_at >= '" + monthStart.ToDateTimeString() + "'")
}
---
02.获取周期的结束时间
a.说明
EndOfXxx()系列方法用于获取各个时间周期的结束时刻。
结束时间会将更小的时间单位设为最大值,如EndOfDay()将时间设为23:59:59。
注意秒的最大值是59,不包含毫秒微秒纳秒部分。
与StartOf配合使用可以精确定义时间范围的边界。
在数据库查询中通常使用<号而不是<=号,避免精度问题。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Parse("2024-06-15 14:30:45")
fmt.Println("当前时间:", now.ToDateTimeString())
fmt.Println()
// 获取世纪结束
endOfCentury := now.EndOfCentury()
fmt.Println("世纪结束:", endOfCentury.ToDateTimeString()) // 2099-12-31 23:59:59
// 获取十年结束
endOfDecade := now.EndOfDecade()
fmt.Println("十年结束:", endOfDecade.ToDateTimeString()) // 2029-12-31 23:59:59
// 获取年末
endOfYear := now.EndOfYear()
fmt.Println("年末:", endOfYear.ToDateTimeString()) // 2024-12-31 23:59:59
// 获取季度结束
endOfQuarter := now.EndOfQuarter()
fmt.Println("季度结束:", endOfQuarter.ToDateTimeString()) // 2024-06-30 23:59:59
// 获取月末
endOfMonth := now.EndOfMonth()
fmt.Println("月末:", endOfMonth.ToDateTimeString()) // 2024-06-30 23:59:59
// 获取周末(周日为一周结束)
endOfWeek := now.EndOfWeek()
fmt.Println("周末:", endOfWeek.ToDateTimeString()) // 2024-06-16 23:59:59 (周日)
// 获取当天结束
endOfDay := now.EndOfDay()
fmt.Println("当天结束:", endOfDay.ToDateTimeString()) // 2024-06-15 23:59:59
// 获取当前小时结束
endOfHour := now.EndOfHour()
fmt.Println("小时结束:", endOfHour.ToDateTimeString()) // 2024-06-15 14:59:59
// 获取当前分钟结束
endOfMinute := now.EndOfMinute()
fmt.Println("分钟结束:", endOfMinute.ToDateTimeString()) // 2024-06-15 14:30:59
// 获取当前秒结束
endOfSecond := now.EndOfSecond()
fmt.Println("秒结束:", endOfSecond.ToDateTimeString()) // 2024-06-15 14:30:45
// 实用场景:查询今天的数据
fmt.Println("\n=== 今日数据范围 ===")
todayStart := carbon.Now().StartOfDay()
todayEnd := carbon.Now().EndOfDay()
fmt.Printf("查询范围: %s ~ %s\n",
todayStart.ToDateTimeString(),
todayEnd.ToDateTimeString())
}
---
03.完整时间范围应用
a.说明
结合StartOf和EndOf方法可以构建各种复杂的时间范围查询条件。
常用于数据统计、报表生成、日志分析等需要按时间周期聚合的场景。
建议在数据库查询时使用>= start AND < end+1的方式,避免边界问题。
可以快速实现本周、本月、本季度、本年等各种时间维度的数据筛选。
配合Between()方法可以进行更灵活的时间范围判断。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 今日范围
fmt.Println("=== 今日数据范围 ===")
todayStart := now.StartOfDay()
todayEnd := now.EndOfDay()
fmt.Printf("%s ~ %s\n", todayStart.ToDateTimeString(), todayEnd.ToDateTimeString())
// 本周范围
fmt.Println("\n=== 本周数据范围 ===")
weekStart := now.StartOfWeek()
weekEnd := now.EndOfWeek()
fmt.Printf("%s ~ %s\n", weekStart.ToDateTimeString(), weekEnd.ToDateTimeString())
fmt.Printf("共 %d 天\n", weekEnd.DiffInDays(weekStart)+1)
// 本月范围
fmt.Println("\n=== 本月数据范围 ===")
monthStart := now.StartOfMonth()
monthEnd := now.EndOfMonth()
fmt.Printf("%s ~ %s\n", monthStart.ToDateTimeString(), monthEnd.ToDateTimeString())
fmt.Printf("共 %d 天\n", monthEnd.Day())
// 本季度范围
fmt.Println("\n=== 本季度数据范围 ===")
quarterStart := now.StartOfQuarter()
quarterEnd := now.EndOfQuarter()
fmt.Printf("%s ~ %s\n", quarterStart.ToDateTimeString(), quarterEnd.ToDateTimeString())
// 本年范围
fmt.Println("\n=== 本年数据范围 ===")
yearStart := now.StartOfYear()
yearEnd := now.EndOfYear()
fmt.Printf("%s ~ %s\n", yearStart.ToDateTimeString(), yearEnd.ToDateTimeString())
fmt.Printf("共 %d 天\n", yearEnd.DiffInDays(yearStart)+1)
// 上个月范围
fmt.Println("\n=== 上个月数据范围 ===")
lastMonthStart := now.SubMonth().StartOfMonth()
lastMonthEnd := now.SubMonth().EndOfMonth()
fmt.Printf("%s ~ %s\n", lastMonthStart.ToDateTimeString(), lastMonthEnd.ToDateTimeString())
// 实用场景:生成SQL查询条件
fmt.Println("\n=== SQL查询示例 ===")
fmt.Println("-- 查询本月数据")
fmt.Printf("SELECT * FROM orders WHERE created_at >= '%s' AND created_at < '%s';\n",
monthStart.ToDateTimeString(),
monthEnd.AddDay().StartOfDay().ToDateTimeString())
fmt.Println("\n-- 查询上季度数据")
lastQuarterStart := now.SubQuarter().StartOfQuarter()
lastQuarterEnd := now.SubQuarter().EndOfQuarter()
fmt.Printf("SELECT * FROM sales WHERE sale_date >= '%s' AND sale_date < '%s';\n",
lastQuarterStart.ToDateString(),
lastQuarterEnd.AddDay().ToDateString())
// 实用场景:检查时间是否在本周
fmt.Println("\n=== 时间范围判断 ===")
checkDate := carbon.Parse("2024-06-15")
if checkDate.Between(weekStart, weekEnd) {
fmt.Printf("%s 在本周范围内\n", checkDate.ToDateString())
} else {
fmt.Printf("%s 不在本周范围内\n", checkDate.ToDateString())
}
}
---
3.3 差值计算
01.基础差值计算
a.说明
Carbon提供DiffInXxx()系列方法计算两个时间之间的差值。
支持计算年、月、周、日、时、分、秒等各个时间单位的差值。
返回值为int64类型,正数表示未来,负数表示过去。
DiffAbsInXxx()系列方法返回绝对值,不区分过去未来。
差值计算会考虑时区差异,自动转换后再计算。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
start := carbon.Parse("2024-01-01 00:00:00")
end := carbon.Parse("2024-12-31 23:59:59")
fmt.Println("开始时间:", start.ToDateTimeString())
fmt.Println("结束时间:", end.ToDateTimeString())
fmt.Println()
// 相差年数
diffYears := end.DiffInYears(start)
fmt.Println("相差年数:", diffYears) // 0年(不足1年)
// 相差月数
diffMonths := end.DiffInMonths(start)
fmt.Println("相差月数:", diffMonths) // 11个月
// 相差周数
diffWeeks := end.DiffInWeeks(start)
fmt.Println("相差周数:", diffWeeks) // 52周
// 相差天数
diffDays := end.DiffInDays(start)
fmt.Println("相差天数:", diffDays) // 365天(闰年366天)
// 相差小时数
diffHours := end.DiffInHours(start)
fmt.Println("相差小时数:", diffHours) // 8759小时
// 相差分钟数
diffMinutes := end.DiffInMinutes(start)
fmt.Println("相差分钟数:", diffMinutes)
// 相差秒数
diffSeconds := end.DiffInSeconds(start)
fmt.Println("相差秒数:", diffSeconds)
// 负数差值(过去时间)
now := carbon.Now()
yesterday := now.SubDay()
diff := yesterday.DiffInDays(now)
fmt.Println("\n昨天距今天:", diff, "天") // -1
// 绝对差值
absDiff := yesterday.DiffAbsInDays(now)
fmt.Println("绝对差值:", absDiff, "天") // 1
// 实用场景:计算年龄
birthday := carbon.Parse("1990-05-15")
today := carbon.Now()
age := today.DiffInYears(birthday)
fmt.Printf("\n出生日期: %s\n", birthday.ToDateString())
fmt.Printf("今天: %s\n", today.ToDateString())
fmt.Printf("年龄: %d岁\n", age)
}
---
02.时长格式化输出
a.说明
DiffForHumans()方法可以将时间差值转换为人类友好的描述。
自动选择最合适的时间单位,如"3天前"、"2小时后"等。
支持多语言输出,通过SetLocale()设置语言环境。
适合在界面显示相对时间,如评论发布时间、最后登录时间等。
提供了灵活的格式化选项,可以自定义输出格式。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 相对当前时间的人性化描述
fmt.Println("=== 英文描述 ===")
fmt.Println("1秒前:", now.SubSecond().DiffForHumans())
fmt.Println("30秒前:", now.SubSeconds(30).DiffForHumans())
fmt.Println("1分钟前:", now.SubMinute().DiffForHumans())
fmt.Println("30分钟前:", now.SubMinutes(30).DiffForHumans())
fmt.Println("1小时前:", now.SubHour().DiffForHumans())
fmt.Println("3小时前:", now.SubHours(3).DiffForHumans())
fmt.Println("1天前:", now.SubDay().DiffForHumans())
fmt.Println("5天前:", now.SubDays(5).DiffForHumans())
fmt.Println("1周前:", now.SubWeek().DiffForHumans())
fmt.Println("1个月前:", now.SubMonth().DiffForHumans())
fmt.Println("1年前:", now.SubYear().DiffForHumans())
// 未来时间
fmt.Println("\n=== 未来时间 ===")
fmt.Println("1分钟后:", now.AddMinute().DiffForHumans())
fmt.Println("1小时后:", now.AddHour().DiffForHumans())
fmt.Println("1天后:", now.AddDay().DiffForHumans())
fmt.Println("1周后:", now.AddWeek().DiffForHumans())
// 中文描述
fmt.Println("\n=== 中文描述 ===")
nowCN := now.SetLocale("zh-CN")
fmt.Println("1分钟前:", nowCN.SubMinute().DiffForHumans())
fmt.Println("1小时前:", nowCN.SubHour().DiffForHumans())
fmt.Println("1天前:", nowCN.SubDay().DiffForHumans())
fmt.Println("1周前:", nowCN.SubWeek().DiffForHumans())
fmt.Println("1个月前:", nowCN.SubMonth().DiffForHumans())
// 实用场景:评论时间显示
fmt.Println("\n=== 评论时间示例 ===")
comments := []carbon.Carbon{
carbon.Now().SubSeconds(30),
carbon.Now().SubMinutes(5),
carbon.Now().SubHours(2),
carbon.Now().SubDays(1),
carbon.Now().SubWeeks(1),
carbon.Now().SubMonths(3),
}
for i, comment := range comments {
commentCN := comment.SetLocale("zh-CN")
fmt.Printf("评论%d: %s\n", i+1, commentCN.DiffForHumans())
}
// 实用场景:最后登录时间
lastLogin := carbon.Parse("2024-01-10 10:30:00")
lastLoginCN := lastLogin.SetLocale("zh-CN")
fmt.Printf("\n最后登录: %s\n", lastLoginCN.DiffForHumans())
}
---
03.精确差值计算
a.说明
DiffInDuration()方法返回time.Duration类型的精确差值。
适合需要精确到纳秒级别的时间差值计算。
可以与time包的其他功能无缝集成。
Diff()方法返回Carbon自定义的Diff结构,提供更丰富的差值信息。
在性能监控、超时检测等场景中非常实用。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
start := carbon.Parse("2024-01-15 10:00:00")
end := carbon.Parse("2024-01-15 12:30:45")
// 获取Duration类型差值
duration := end.DiffInDuration(start)
fmt.Println("Duration差值:", duration)
fmt.Println("小时数:", duration.Hours())
fmt.Println("分钟数:", duration.Minutes())
fmt.Println("秒数:", duration.Seconds())
// 与time包的集成
fmt.Println("\n=== 与time包集成 ===")
if duration > 2*time.Hour {
fmt.Println("差值超过2小时")
}
// 格式化Duration
fmt.Printf("格式化: %v\n", duration) // 2h30m45s
// 实用场景1:程序执行时间测量
fmt.Println("\n=== 执行时间测量 ===")
taskStart := carbon.Now()
// 模拟任务执行
time.Sleep(100 * time.Millisecond)
taskEnd := carbon.Now()
execTime := taskEnd.DiffInDuration(taskStart)
fmt.Printf("任务执行时间: %v\n", execTime)
fmt.Printf("毫秒: %.2f ms\n", execTime.Seconds()*1000)
// 实用场景2:超时检测
fmt.Println("\n=== 超时检测 ===")
requestStart := carbon.Parse("2024-01-15 10:00:00")
requestEnd := carbon.Parse("2024-01-15 10:00:35")
timeout := 30 * time.Second
elapsed := requestEnd.DiffInDuration(requestStart)
if elapsed > timeout {
fmt.Printf("请求超时: 耗时 %v, 超时限制 %v\n", elapsed, timeout)
} else {
fmt.Printf("请求正常: 耗时 %v\n", elapsed)
}
// 实用场景3:倒计时计算
fmt.Println("\n=== 活动倒计时 ===")
activityStart := carbon.Parse("2024-11-11 00:00:00")
now := carbon.Now()
if now.Lt(activityStart) {
remaining := activityStart.DiffInDuration(now)
days := int(remaining.Hours() / 24)
hours := int(remaining.Hours()) % 24
minutes := int(remaining.Minutes()) % 60
fmt.Printf("距离活动开始还有: %d天%d小时%d分钟\n", days, hours, minutes)
} else {
fmt.Println("活动已开始或已结束")
}
}
---
3.4 时间范围
01.判断时间范围
a.说明
Between()系列方法用于判断时间是否在指定范围内。
支持闭区间、开区间、半开区间等多种区间类型。
在活动时间判断、数据有效期校验等场景中非常实用。
区间比较会自动处理时区差异,确保结果准确。
配合逻辑运算可以实现复杂的时间范围条件判断。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 定义时间范围
start := carbon.Parse("2024-01-01 00:00:00")
end := carbon.Parse("2024-12-31 23:59:59")
check := carbon.Parse("2024-06-15 12:00:00")
fmt.Println("范围开始:", start.ToDateTimeString())
fmt.Println("范围结束:", end.ToDateTimeString())
fmt.Println("检查时间:", check.ToDateTimeString())
fmt.Println()
// 闭区间 [start, end]
inClosed := check.Between(start, end)
fmt.Println("在闭区间内:", inClosed) // true
// 开区间 (start, end)
inOpen := check.BetweenExcluded(start, end)
fmt.Println("在开区间内:", inOpen) // true
// 左闭右开 [start, end)
inLeftClosed := check.BetweenIncludedStart(start, end)
fmt.Println("在左闭右开区间:", inLeftClosed) // true
// 左开右闭 (start, end]
inRightClosed := check.BetweenIncludedEnd(start, end)
fmt.Println("在右闭右开区间:", inRightClosed) // true
// 边界测试
fmt.Println("\n=== 边界测试 ===")
fmt.Println("起始点在闭区间:", start.Between(start, end)) // true
fmt.Println("起始点在开区间:", start.BetweenExcluded(start, end)) // false
fmt.Println("结束点在闭区间:", end.Between(start, end)) // true
fmt.Println("结束点在开区间:", end.BetweenExcluded(start, end)) // false
// 实用场景1:活动时间判断
fmt.Println("\n=== 活动时间判断 ===")
activityStart := carbon.Parse("2024-11-11 00:00:00")
activityEnd := carbon.Parse("2024-11-11 23:59:59")
now := carbon.Now()
if now.Between(activityStart, activityEnd) {
fmt.Println("活动进行中")
} else if now.Lt(activityStart) {
days := activityStart.DiffInDays(now)
fmt.Printf("活动未开始,还有%d天\n", days)
} else {
fmt.Println("活动已结束")
}
// 实用场景2:会员有效期检查
fmt.Println("\n=== 会员有效期检查 ===")
memberStart := carbon.Parse("2024-01-01")
memberEnd := carbon.Parse("2024-12-31")
currentDate := carbon.Now()
if currentDate.Between(memberStart, memberEnd) {
remaining := memberEnd.DiffInDays(currentDate)
fmt.Printf("会员有效,剩余%d天\n", remaining)
} else {
fmt.Println("会员已过期")
}
}
---
02.创建时间范围对象
a.说明
虽然Carbon本身不提供Range对象,但可以通过自定义结构实现。
时间范围对象封装了开始和结束时间,提供统一的范围操作接口。
可以添加各种实用方法,如判断重叠、计算交集、格式化输出等。
在项目管理、酒店预订、会议室安排等场景中非常实用。
配合Carbon的其他功能可以实现复杂的时间范围业务逻辑。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// TimeRange 时间范围结构
type TimeRange struct {
Start carbon.Carbon
End carbon.Carbon
}
// NewTimeRange 创建时间范围
func NewTimeRange(start, end carbon.Carbon) *TimeRange {
if start.Gt(end) {
start, end = end, start // 自动调整顺序
}
return &TimeRange{Start: start, End: end}
}
// Contains 判断某个时间是否在范围内
func (tr *TimeRange) Contains(t carbon.Carbon) bool {
return t.Between(tr.Start, tr.End)
}
// Duration 计算范围时长
func (tr *TimeRange) Duration() int64 {
return tr.End.DiffInSeconds(tr.Start)
}
// DurationInDays 计算范围天数
func (tr *TimeRange) DurationInDays() int64 {
return tr.End.DiffInDays(tr.Start) + 1 // 包含首尾
}
// Overlaps 判断两个范围是否重叠
func (tr *TimeRange) Overlaps(other *TimeRange) bool {
return tr.Start.Lte(other.End) && tr.End.Gte(other.Start)
}
// String 格式化输出
func (tr *TimeRange) String() string {
return fmt.Sprintf("%s ~ %s",
tr.Start.ToDateTimeString(),
tr.End.ToDateTimeString())
}
func main() {
// 创建时间范围
range1 := NewTimeRange(
carbon.Parse("2024-01-01"),
carbon.Parse("2024-01-31"),
)
fmt.Println("范围1:", range1.String())
fmt.Println("持续天数:", range1.DurationInDays())
// 判断时间是否在范围内
check := carbon.Parse("2024-01-15")
fmt.Printf("\n%s 在范围内: %v\n", check.ToDateString(), range1.Contains(check))
// 判断范围重叠
range2 := NewTimeRange(
carbon.Parse("2024-01-15"),
carbon.Parse("2024-02-15"),
)
fmt.Println("\n范围2:", range2.String())
fmt.Println("范围1和范围2重叠:", range1.Overlaps(range2))
// 不重叠的范围
range3 := NewTimeRange(
carbon.Parse("2024-03-01"),
carbon.Parse("2024-03-31"),
)
fmt.Println("\n范围3:", range3.String())
fmt.Println("范围1和范围3重叠:", range1.Overlaps(range3))
// 实用场景:酒店房间预订冲突检查
fmt.Println("\n=== 酒店预订冲突检查 ===")
existingBooking := NewTimeRange(
carbon.Parse("2024-06-10"),
carbon.Parse("2024-06-15"),
)
newBooking := NewTimeRange(
carbon.Parse("2024-06-13"),
carbon.Parse("2024-06-20"),
)
fmt.Println("已有预订:", existingBooking.String())
fmt.Println("新预订:", newBooking.String())
if existingBooking.Overlaps(newBooking) {
fmt.Println("预订冲突,无法预订!")
} else {
fmt.Println("可以预订")
}
}
---
03.批量时间范围处理
a.说明
在实际业务中经常需要处理多个时间范围的情况。
可以实现范围列表的合并、分割、查找空档等操作。
适用于排班管理、资源调度、时间轴可视化等复杂场景。
通过排序和遍历可以高效处理大量时间范围数据。
结合数据结构和算法可以优化时间范围查询性能。
b.代码示例
---
package main
import (
"fmt"
"sort"
"github.com/golang-module/carbon/v2"
)
// TimeRange 时间范围结构
type TimeRange struct {
Start carbon.Carbon
End carbon.Carbon
Name string
}
// TimeRangeList 时间范围列表
type TimeRangeList []*TimeRange
// Sort 按开始时间排序
func (trl TimeRangeList) Sort() {
sort.Slice(trl, func(i, j int) bool {
return trl[i].Start.Lt(trl[j].Start)
})
}
// FindConflicts 查找冲突的时间范围
func (trl TimeRangeList) FindConflicts() [][]*TimeRange {
var conflicts [][]*TimeRange
trl.Sort()
for i := 0; i < len(trl); i++ {
var group []*TimeRange
for j := i + 1; j < len(trl); j++ {
if trl[i].End.Gte(trl[j].Start) {
if len(group) == 0 {
group = append(group, trl[i])
}
group = append(group, trl[j])
}
}
if len(group) > 0 {
conflicts = append(conflicts, group)
}
}
return conflicts
}
func main() {
// 创建多个时间范围
ranges := TimeRangeList{
{
Start: carbon.Parse("2024-01-01 09:00:00"),
End: carbon.Parse("2024-01-01 10:00:00"),
Name: "会议A",
},
{
Start: carbon.Parse("2024-01-01 09:30:00"),
End: carbon.Parse("2024-01-01 11:00:00"),
Name: "会议B",
},
{
Start: carbon.Parse("2024-01-01 14:00:00"),
End: carbon.Parse("2024-01-01 15:00:00"),
Name: "会议C",
},
{
Start: carbon.Parse("2024-01-01 14:30:00"),
End: carbon.Parse("2024-01-01 15:30:00"),
Name: "会议D",
},
}
// 排序
ranges.Sort()
fmt.Println("=== 所有会议时间 ===")
for _, r := range ranges {
fmt.Printf("%s: %s ~ %s\n",
r.Name,
r.Start.ToTimeString(),
r.End.ToTimeString())
}
// 查找冲突
conflicts := ranges.FindConflicts()
fmt.Println("\n=== 冲突检测 ===")
if len(conflicts) > 0 {
for i, group := range conflicts {
fmt.Printf("冲突组%d:\n", i+1)
for _, r := range group {
fmt.Printf(" - %s: %s ~ %s\n",
r.Name,
r.Start.ToTimeString(),
r.End.ToTimeString())
}
}
} else {
fmt.Println("没有冲突")
}
// 查找空闲时间段
fmt.Println("\n=== 空闲时间段 ===")
workStart := carbon.Parse("2024-01-01 09:00:00")
workEnd := carbon.Parse("2024-01-01 18:00:00")
current := workStart
for i, r := range ranges {
if current.Lt(r.Start) {
fmt.Printf("空闲: %s ~ %s\n",
current.ToTimeString(),
r.Start.ToTimeString())
}
current = r.End
// 最后一个会议后的空闲时间
if i == len(ranges)-1 && current.Lt(workEnd) {
fmt.Printf("空闲: %s ~ %s\n",
current.ToTimeString(),
workEnd.ToTimeString())
}
}
}
---
3.5 时间遍历
01.按天遍历
a.说明
虽然Carbon不直接提供遍历方法,但可以通过循环和AddDay()实现。
按天遍历适用于日历生成、日期选择器、考勤统计等场景。
可以配合IsWeekday()和IsWeekend()筛选工作日或周末。
遍历时建议设置合理的循环次数上限,避免无限循环。
结合条件判断可以实现各种复杂的日期遍历逻辑。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 遍历一周的日期
fmt.Println("=== 遍历本周 ===")
start := carbon.Now().StartOfWeek()
for i := 0; i < 7; i++ {
current := start.AddDays(i)
weekday := current.ToWeekString()
fmt.Printf("%s %s\n", current.ToDateString(), weekday)
}
// 遍历一个月的日期
fmt.Println("\n=== 遍历本月 ===")
monthStart := carbon.Now().StartOfMonth()
daysInMonth := monthStart.DaysInMonth()
for i := 0; i < daysInMonth; i++ {
current := monthStart.AddDays(i)
fmt.Printf("%2d日 %s\n", current.Day(), current.ToWeekString())
}
// 只遍历工作日
fmt.Println("\n=== 遍历本月工作日 ===")
workdayCount := 0
for i := 0; i < daysInMonth; i++ {
current := monthStart.AddDays(i)
if current.IsWeekday() {
workdayCount++
fmt.Printf("%s %s\n", current.ToDateString(), current.ToWeekString())
}
}
fmt.Printf("本月共有 %d 个工作日\n", workdayCount)
// 只遍历周末
fmt.Println("\n=== 遍历本月周末 ===")
for i := 0; i < daysInMonth; i++ {
current := monthStart.AddDays(i)
if current.IsWeekend() {
fmt.Printf("%s %s\n", current.ToDateString(), current.ToWeekString())
}
}
// 实用场景:查找所有周五
fmt.Println("\n=== 查找本月所有周五 ===")
for i := 0; i < daysInMonth; i++ {
current := monthStart.AddDays(i)
if current.IsFriday() {
fmt.Println(current.ToDateString())
}
}
}
---
02.按周遍历
a.说明
按周遍历适用于周报生成、周期性任务调度等场景。
可以通过AddWeek()方法实现周级别的时间递增。
配合StartOfWeek()和EndOfWeek()可以获取完整周范围。
在项目管理中常用于按周统计进度和工作量。
可以灵活设置周的起始日(周一或周日)。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 遍历一个季度的周
fmt.Println("=== 遍历本季度(按周) ===")
quarterStart := carbon.Now().StartOfQuarter()
quarterEnd := carbon.Now().EndOfQuarter()
current := quarterStart.StartOfWeek()
weekNum := 1
for current.Lte(quarterEnd) {
weekStart := current
weekEnd := current.EndOfWeek()
// 确保不超过季度末
if weekEnd.Gt(quarterEnd) {
weekEnd = quarterEnd
}
fmt.Printf("第%d周: %s ~ %s\n",
weekNum,
weekStart.ToDateString(),
weekEnd.ToDateString())
current = current.AddWeek()
weekNum++
}
// 遍历一年的周数
fmt.Println("\n=== 遍历全年周数 ===")
yearStart := carbon.Parse("2024-01-01").StartOfWeek()
yearEnd := carbon.Parse("2024-12-31")
current = yearStart
weekNum = 1
for current.Lte(yearEnd) {
fmt.Printf("第%2d周: %s (ISO周%d)\n",
weekNum,
current.ToDateString(),
current.WeekOfYear())
current = current.AddWeek()
weekNum++
}
// 实用场景:生成月度周报时间表
fmt.Println("\n=== 月度周报时间表 ===")
monthStart := carbon.Now().StartOfMonth()
monthEnd := carbon.Now().EndOfMonth()
current = monthStart.StartOfWeek()
weekNum = 1
for current.Lte(monthEnd) {
weekStart := current
if weekStart.Lt(monthStart) {
weekStart = monthStart
}
weekEnd := current.EndOfWeek()
if weekEnd.Gt(monthEnd) {
weekEnd = monthEnd
}
days := weekEnd.DiffInDays(weekStart) + 1
fmt.Printf("第%d周(%d天): %s ~ %s\n",
weekNum,
days,
weekStart.ToDateString(),
weekEnd.ToDateString())
current = current.AddWeek()
weekNum++
}
}
---
03.按自定义间隔遍历
a.说明
除了按天和按周,还可以按小时、按月等任意时间间隔遍历。
灵活的遍历间隔适用于各种业务场景的时间序列生成。
在数据分析、图表展示、定时任务等场景中非常有用。
可以设置遍历的步长和终止条件,实现精确控制。
配合格式化输出可以生成各种时间序列数据。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 按小时遍历一天
fmt.Println("=== 按小时遍历24小时 ===")
dayStart := carbon.Now().StartOfDay()
for i := 0; i < 24; i++ {
current := dayStart.AddHours(i)
fmt.Printf("%02d:00 - %s\n", i, current.ToTimeString())
}
// 按2小时间隔遍历
fmt.Println("\n=== 按2小时间隔遍历 ===")
for i := 0; i < 24; i += 2 {
current := dayStart.AddHours(i)
fmt.Printf("%s\n", current.ToTimeString())
}
// 按月遍历一年
fmt.Println("\n=== 按月遍历一年 ===")
yearStart := carbon.Parse("2024-01-01")
for i := 0; i < 12; i++ {
current := yearStart.AddMonths(i)
days := current.DaysInMonth()
fmt.Printf("%d月: %s, 共%d天\n",
current.Month(),
current.ToMonthString(),
days)
}
// 按季度遍历
fmt.Println("\n=== 按季度遍历 ===")
for i := 0; i < 4; i++ {
current := yearStart.AddQuarters(i)
qStart := current.StartOfQuarter()
qEnd := current.EndOfQuarter()
fmt.Printf("Q%d: %s ~ %s\n",
current.Quarter(),
qStart.ToDateString(),
qEnd.ToDateString())
}
// 实用场景1:生成每30分钟的时间点(会议室预约)
fmt.Println("\n=== 会议室可预约时间 ===")
meetingStart := carbon.Parse("2024-01-15 09:00:00")
meetingEnd := carbon.Parse("2024-01-15 18:00:00")
current := meetingStart
for current.Lte(meetingEnd) {
fmt.Println(current.ToTimeString())
current = current.AddMinutes(30)
}
// 实用场景2:生成未来7天的日期(天气预报)
fmt.Println("\n=== 7天天气预报日期 ===")
today := carbon.Now()
for i := 0; i < 7; i++ {
future := today.AddDays(i)
fmt.Printf("%s %s\n",
future.ToDateString(),
future.ToWeekString())
}
// 实用场景3:生成每隔3天的时间点(定期任务)
fmt.Println("\n=== 每3天执行的任务时间 ===")
taskStart := carbon.Parse("2024-01-01")
taskEnd := carbon.Parse("2024-01-31")
current = taskStart
taskNum := 1
for current.Lte(taskEnd) {
fmt.Printf("任务%d: %s\n", taskNum, current.ToDateString())
current = current.AddDays(3)
taskNum++
}
}
---
4 格式化与解析
4.1 标准格式
01.ISO8601标准格式
a.说明
ISO8601是国际标准化组织制定的日期时间表示规范。
Carbon完全支持ISO8601格式的解析和输出,包括基本格式和扩展格式。
标准格式包含日期、时间和时区信息,适合跨系统数据交换。
ToIso8601String()方法输出完整的ISO8601格式。
在API接口、日志记录等场景中推荐使用ISO8601格式。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// ISO8601完整格式
fmt.Println("ISO8601完整:", now.ToIso8601String())
// 输出: 2024-01-15T10:30:00+08:00
// ISO8601仅日期
fmt.Println("ISO8601日期:", now.ToIso8601DateString())
// 输出: 2024-01-15
// ISO8601仅时间
fmt.Println("ISO8601时间:", now.ToIso8601TimeString())
// 输出: 10:30:00+08:00
// ISO8601带毫秒
fmt.Println("ISO8601毫秒:", now.ToIso8601MilliString())
// 输出: 2024-01-15T10:30:00.123+08:00
// ISO8601带微秒
fmt.Println("ISO8601微秒:", now.ToIso8601MicroString())
// 输出: 2024-01-15T10:30:00.123456+08:00
// ISO8601带纳秒
fmt.Println("ISO8601纳秒:", now.ToIso8601NanoString())
// 输出: 2024-01-15T10:30:00.123456789+08:00
// 解析ISO8601格式
fmt.Println("\n=== 解析ISO8601 ===")
isoStr := "2024-01-15T10:30:00+08:00"
parsed := carbon.Parse(isoStr)
if parsed.Error == nil {
fmt.Println("解析成功:", parsed.ToDateTimeString())
}
// 不同时区的ISO8601
utc := now.SetTimezone("UTC")
fmt.Println("\n=== 不同时区 ===")
fmt.Println("北京时间:", now.ToIso8601String())
fmt.Println("UTC时间:", utc.ToIso8601String())
// 实用场景:API数据交换
fmt.Println("\n=== API响应示例 ===")
type APIResponse struct {
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
resp := APIResponse{
CreatedAt: carbon.Now().ToIso8601String(),
UpdatedAt: carbon.Now().ToIso8601String(),
}
fmt.Printf("CreatedAt: %s\n", resp.CreatedAt)
fmt.Printf("UpdatedAt: %s\n", resp.UpdatedAt)
}
---
02.RFC系列标准格式
a.说明
RFC(Request for Comments)是互联网标准文档系列。
Carbon支持多种RFC标准的日期时间格式,如RFC822、RFC3339等。
不同的RFC标准适用于不同的协议和场景,如HTTP、Email等。
ToRfcXxxString()系列方法可快速输出各种RFC标准格式。
了解各种RFC格式有助于正确处理不同系统的时间数据。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
fmt.Println("当前时间:", now.ToDateTimeString())
fmt.Println()
// RFC822 - 常用于Email
fmt.Println("RFC822:", now.ToRfc822String())
// 输出: 15 Jan 24 10:30 CST
// RFC822Z - RFC822带数字时区
fmt.Println("RFC822Z:", now.ToRfc822ZString())
// 输出: 15 Jan 24 10:30 +0800
// RFC850 - 较旧的格式
fmt.Println("RFC850:", now.ToRfc850String())
// 输出: Monday, 15-Jan-24 10:30:00 CST
// RFC1123 - HTTP日期格式
fmt.Println("RFC1123:", now.ToRfc1123String())
// 输出: Mon, 15 Jan 2024 10:30:00 CST
// RFC1123Z - RFC1123带数字时区
fmt.Println("RFC1123Z:", now.ToRfc1123ZString())
// 输出: Mon, 15 Jan 2024 10:30:00 +0800
// RFC2822 - Email标准格式
fmt.Println("RFC2822:", now.ToRfc2822String())
// 输出: Mon, 15 Jan 2024 10:30:00 +0800
// RFC3339 - 互联网标准格式
fmt.Println("RFC3339:", now.ToRfc3339String())
// 输出: 2024-01-15T10:30:00+08:00
// RFC3339带毫秒
fmt.Println("RFC3339毫秒:", now.ToRfc3339MilliString())
// 输出: 2024-01-15T10:30:00.123+08:00
// RFC7231 - HTTP新标准
fmt.Println("RFC7231:", now.ToRfc7231String())
// 输出: Mon, 15 Jan 2024 10:30:00 GMT
// 解析RFC格式
fmt.Println("\n=== 解析RFC格式 ===")
rfc3339Str := "2024-01-15T10:30:00+08:00"
parsed := carbon.Parse(rfc3339Str)
fmt.Println("RFC3339解析:", parsed.ToDateTimeString())
// 实用场景:HTTP响应头
fmt.Println("\n=== HTTP响应头示例 ===")
fmt.Println("Last-Modified:", now.ToRfc7231String())
fmt.Println("Expires:", now.AddHour().ToRfc7231String())
}
---
03.其他标准格式
a.说明
除了ISO和RFC系列,还有其他常用的标准格式。
Cookie格式用于HTTP Cookie的过期时间设置。
RSS格式用于RSS订阅源的发布时间。
W3C格式是万维网联盟推荐的标准格式。
这些格式在Web开发中经常用到,Carbon都提供了便捷支持。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// Cookie格式
fmt.Println("Cookie格式:", now.ToCookieString())
// 输出: Monday, 15-Jan-2024 10:30:00 CST
// RSS格式
fmt.Println("RSS格式:", now.ToRssString())
// 输出: Mon, 15 Jan 2024 10:30:00 +0800
// W3C格式(等同于RFC3339)
fmt.Println("W3C格式:", now.ToW3cString())
// 输出: 2024-01-15T10:30:00+08:00
// ANSIC格式
fmt.Println("ANSIC格式:", now.ToAnsicString())
// 输出: Mon Jan 15 10:30:00 2024
// Unix日期格式
fmt.Println("Unix日期:", now.ToUnixDateString())
// 输出: Mon Jan 15 10:30:00 CST 2024
// Ruby日期格式
fmt.Println("Ruby格式:", now.ToRubyDateString())
// 输出: Mon Jan 15 10:30:00 +0800 2024
// Kitchen格式(仅时间)
fmt.Println("Kitchen格式:", now.ToKitchenString())
// 输出: 10:30AM
// 实用场景:设置Cookie过期时间
fmt.Println("\n=== Cookie示例 ===")
expireTime := carbon.Now().AddHours(24)
fmt.Printf("Set-Cookie: session_id=abc123; Expires=%s; Path=/\n",
expireTime.ToCookieString())
// 实用场景:RSS Feed
fmt.Println("\n=== RSS Feed示例 ===")
fmt.Println("<item>")
fmt.Println(" <title>文章标题</title>")
fmt.Printf(" <pubDate>%s</pubDate>\n", now.ToRssString())
fmt.Println("</item>")
// 实用场景:时间戳对比
fmt.Println("\n=== 不同格式对比 ===")
formats := map[string]string{
"ISO8601": now.ToIso8601String(),
"RFC3339": now.ToRfc3339String(),
"W3C": now.ToW3cString(),
"Cookie": now.ToCookieString(),
"RSS": now.ToRssString(),
}
for name, value := range formats {
fmt.Printf("%-10s %s\n", name+":", value)
}
}
---
4.2 自定义格式
01.PHP风格格式符
a.说明
Carbon采用PHP风格的格式符,比Go标准库的Layout更直观易记。
常用格式符:Y(年4位) y(年2位) m(月2位) n(月1位) d(日2位) j(日1位)。
时间格式符:H(时24制) h(时12制) i(分) s(秒) A(AM/PM大写) a(am/pm小写)。
星期格式符:l(完整) D(缩写) w(数字0-6) N(数字1-7)。
月份格式符:F(完整) M(缩写)。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Parse("2024-01-15 14:30:45")
// 年份格式符
fmt.Println("=== 年份 ===")
fmt.Println("Y(4位):", now.Format("Y")) // 2024
fmt.Println("y(2位):", now.Format("y")) // 24
// 月份格式符
fmt.Println("\n=== 月份 ===")
fmt.Println("m(2位):", now.Format("m")) // 01
fmt.Println("n(1位):", now.Format("n")) // 1
fmt.Println("M(英文缩写):", now.Format("M")) // Jan
fmt.Println("F(英文全称):", now.Format("F")) // January
// 日期格式符
fmt.Println("\n=== 日期 ===")
fmt.Println("d(2位):", now.Format("d")) // 15
fmt.Println("j(1位):", now.Format("j")) // 15
// 星期格式符
fmt.Println("\n=== 星期 ===")
fmt.Println("l(完整):", now.Format("l")) // Monday
fmt.Println("D(缩写):", now.Format("D")) // Mon
fmt.Println("w(数字0-6):", now.Format("w")) // 1
fmt.Println("N(数字1-7):", now.Format("N")) // 1
// 时间格式符
fmt.Println("\n=== 时间 ===")
fmt.Println("H(24小时):", now.Format("H")) // 14
fmt.Println("h(12小时):", now.Format("h")) // 02
fmt.Println("i(分钟):", now.Format("i")) // 30
fmt.Println("s(秒):", now.Format("s")) // 45
fmt.Println("A(AM/PM):", now.Format("A")) // PM
fmt.Println("a(am/pm):", now.Format("a")) // pm
// 其他格式符
fmt.Println("\n=== 其他 ===")
fmt.Println("z(年中第几天):", now.Format("z")) // 14
fmt.Println("W(ISO周数):", now.Format("W")) // 03
fmt.Println("t(月份天数):", now.Format("t")) // 31
fmt.Println("L(是否闰年):", now.Format("L")) // 1
// 组合使用
fmt.Println("\n=== 组合格式 ===")
fmt.Println(now.Format("Y-m-d H:i:s")) // 2024-01-15 14:30:45
fmt.Println(now.Format("l, F j, Y")) // Monday, January 15, 2024
fmt.Println(now.Format("Y年m月d日 H时i分s秒")) // 2024年01月15日 14时30分45秒
}
---
02.自定义分隔符和格式
a.说明
Format()方法支持任意分隔符和文字,不仅限于标准格式。
可以插入中文、符号等任意字符,实现个性化的日期显示。
适用于生成用户友好的日期描述、文件名、报表标题等场景。
特殊字符会被原样输出,不会被解析为格式符。
可以通过转义实现复杂的格式组合。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 使用不同分隔符
fmt.Println("=== 分隔符 ===")
fmt.Println("横杠:", now.Format("Y-m-d")) // 2024-01-15
fmt.Println("斜杠:", now.Format("Y/m/d")) // 2024/01/15
fmt.Println("点号:", now.Format("Y.m.d")) // 2024.01.15
fmt.Println("空格:", now.Format("Y m d")) // 2024 01 15
fmt.Println("无分隔:", now.Format("Ymd")) // 20240115
// 中文格式
fmt.Println("\n=== 中文格式 ===")
fmt.Println(now.Format("Y年m月d日"))
fmt.Println(now.Format("Y年m月d日 H时i分s秒"))
fmt.Println(now.Format("Y年m月d日 星期w"))
fmt.Println(now.Format("Y年第W周"))
// 英文格式
fmt.Println("\n=== 英文格式 ===")
fmt.Println(now.Format("F d, Y")) // January 15, 2024
fmt.Println(now.Format("l, F d, Y")) // Monday, January 15, 2024
fmt.Println(now.Format("D, M d, Y")) // Mon, Jan 15, 2024
fmt.Println(now.Format("h:i A")) // 02:30 PM
// 实用场景:文件名
fmt.Println("\n=== 文件名示例 ===")
fileName := fmt.Sprintf("backup_%s.sql", now.Format("Ymd_His"))
fmt.Println(fileName) // backup_20240115_143045.sql
logFile := fmt.Sprintf("app_%s.log", now.Format("Y-m-d"))
fmt.Println(logFile) // app_2024-01-15.log
// 实用场景:报表标题
fmt.Println("\n=== 报表标题 ===")
title1 := fmt.Sprintf("%s 销售报表", now.Format("Y年m月"))
fmt.Println(title1) // 2024年01月 销售报表
title2 := fmt.Sprintf("%s周报", now.Format("Y年第W周"))
fmt.Println(title2) // 2024年第03周周报
// 实用场景:友好的日期显示
fmt.Println("\n=== 友好显�� ===")
fmt.Println(now.Format("Y年m月d日 l"))
fmt.Println(now.Format("m月d日 H:i"))
fmt.Println(now.Format("F d日 H点i分"))
}
---
03.格式化模板
a.说明
可以预定义常用的格式化模板,提高代码复用性。
模板可以包含完整的格式描述,方便在多处使用。
建议为不同场景定义专用模板,如API模板、日志模板、显示模板等。
使用常量或配置文件管理模板,便于统一修改和维护。
模板化可以确保整个项目中日期格式的一致性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// 日期格式化模板常量
const (
// API相关模板
APIDateTime = "Y-m-d H:i:s"
APIDate = "Y-m-d"
APITime = "H:i:s"
// 显示相关模板
DisplayDateTime = "Y年m月d日 H时i分"
DisplayDate = "Y年m月d日"
DisplayTime = "H时i分"
// 文件名相关模板
FileDateTime = "Ymd_His"
FileDate = "Ymd"
// 日志相关模板
LogDateTime = "Y-m-d H:i:s.u"
LogDate = "Y-m-d"
// 报表相关模板
ReportMonth = "Y年m月"
ReportWeek = "Y年第W周"
ReportDate = "m月d日"
)
func main() {
now := carbon.Now()
// 使用API模板
fmt.Println("=== API格式 ===")
fmt.Println("完整:", now.Format(APIDateTime))
fmt.Println("日期:", now.Format(APIDate))
fmt.Println("时间:", now.Format(APITime))
// 使用显示模板
fmt.Println("\n=== 显示格式 ===")
fmt.Println("完整:", now.Format(DisplayDateTime))
fmt.Println("日期:", now.Format(DisplayDate))
fmt.Println("时间:", now.Format(DisplayTime))
// 使用文件名模板
fmt.Println("\n=== 文件名格式 ===")
backupFile := fmt.Sprintf("backup_%s.sql", now.Format(FileDateTime))
dataFile := fmt.Sprintf("data_%s.csv", now.Format(FileDate))
fmt.Println(backupFile)
fmt.Println(dataFile)
// 使用日志模板
fmt.Println("\n=== 日志格式 ===")
logEntry := fmt.Sprintf("[%s] Application started", now.Format(LogDateTime))
fmt.Println(logEntry)
// 使用报表模板
fmt.Println("\n=== 报表格式 ===")
fmt.Printf("%s销售报表\n", now.Format(ReportMonth))
fmt.Printf("%s工作总结\n", now.Format(ReportWeek))
fmt.Printf("%s考勤记录\n", now.Format(ReportDate))
// 实用场景:格式化函数封装
fmt.Println("\n=== 封装函数示例 ===")
fmt.Println("API格式:", FormatForAPI(now))
fmt.Println("显示格式:", FormatForDisplay(now))
fmt.Println("日志格式:", FormatForLog(now))
}
// FormatForAPI 格式化为API格式
func FormatForAPI(c carbon.Carbon) string {
return c.Format(APIDateTime)
}
// FormatForDisplay 格式化为显示格式
func FormatForDisplay(c carbon.Carbon) string {
return c.Format(DisplayDateTime)
}
// FormatForLog 格式化为日志格式
func FormatForLog(c carbon.Carbon) string {
return c.Format(LogDateTime)
}
---
4.3 多语言支持
01.设置语言环境
a.说明
Carbon支持全球30多种语言的本地化显示。
通过SetLocale()方法设置语言环境,影响星期、月份等的显示。
常用语言代码:zh-CN(简体中文) en(英语) ja(日语) ko(韩语)等。
语言设置只影响当前Carbon实例,不会影响全局。
多语言支持使Carbon适合国际化应用开发。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 中文环境
zhCN := now.SetLocale("zh-CN")
fmt.Println("=== 中文 ===")
fmt.Println("星期:", zhCN.ToWeekString())
fmt.Println("月份:", zhCN.ToMonthString())
fmt.Println("短星期:", zhCN.ToShortWeekString())
fmt.Println("短月份:", zhCN.ToShortMonthString())
// 英文环境
en := now.SetLocale("en")
fmt.Println("\n=== English ===")
fmt.Println("Week:", en.ToWeekString())
fmt.Println("Month:", en.ToMonthString())
fmt.Println("Short Week:", en.ToShortWeekString())
fmt.Println("Short Month:", en.ToShortMonthString())
// 日文环境
ja := now.SetLocale("ja")
fmt.Println("\n=== 日本語 ===")
fmt.Println("曜日:", ja.ToWeekString())
fmt.Println("月:", ja.ToMonthString())
// 韩文环境
ko := now.SetLocale("ko")
fmt.Println("\n=== 한국어 ===")
fmt.Println("요일:", ko.ToWeekString())
fmt.Println("월:", ko.ToMonthString())
// 法文环境
fr := now.SetLocale("fr")
fmt.Println("\n=== Français ===")
fmt.Println("Semaine:", fr.ToWeekString())
fmt.Println("Mois:", fr.ToMonthString())
// 德文环境
de := now.SetLocale("de")
fmt.Println("\n=== Deutsch ===")
fmt.Println("Wochentag:", de.ToWeekString())
fmt.Println("Monat:", de.ToMonthString())
// 西班牙文环境
es := now.SetLocale("es")
fmt.Println("\n=== Español ===")
fmt.Println("Día:", es.ToWeekString())
fmt.Println("Mes:", es.ToMonthString())
// 实用场景:多语言日历
fmt.Println("\n=== 多语言日历 ===")
languages := map[string]string{
"zh-CN": "中文",
"en": "English",
"ja": "日本語",
"ko": "한국어",
}
for code, name := range languages {
localized := now.SetLocale(code)
fmt.Printf("%s: %s %s\n",
name,
localized.ToDateString(),
localized.ToWeekString())
}
}
---
02.相对时间的多语言
a.说明
DiffForHumans()方法支持多语言的人性化时间描述。
不同语言的相对时间表达方式各有特色。
适合在界面上显示评论时间、最后登录等相对时间信息。
多语言相对时间让应用更符合用户的语言习惯。
结合语言设置可以实现完全本地化的时间显示。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 不同时间点
times := []carbon.Carbon{
now.SubSeconds(30),
now.SubMinutes(5),
now.SubHours(2),
now.SubDays(1),
now.SubWeeks(1),
now.SubMonths(1),
}
// 中文相对时间
fmt.Println("=== 中文相对时间 ===")
for _, t := range times {
zhCN := t.SetLocale("zh-CN")
fmt.Println(zhCN.DiffForHumans())
}
// 英文相对时间
fmt.Println("\n=== English Relative Time ===")
for _, t := range times {
en := t.SetLocale("en")
fmt.Println(en.DiffForHumans())
}
// 日文相对时间
fmt.Println("\n=== 日本語相対時間 ===")
for _, t := range times {
ja := t.SetLocale("ja")
fmt.Println(ja.DiffForHumans())
}
// 实用场景:评论时间显示
fmt.Println("\n=== 多语言评论时间 ===")
commentTime := now.SubHours(3)
fmt.Println("中文:", commentTime.SetLocale("zh-CN").DiffForHumans())
fmt.Println("English:", commentTime.SetLocale("en").DiffForHumans())
fmt.Println("日本語:", commentTime.SetLocale("ja").DiffForHumans())
fmt.Println("한국어:", commentTime.SetLocale("ko").DiffForHumans())
}
---
03.完整的本地化应用
a.说明
实际应用中需要根据用户设置或浏览器语言动态切换。
可以封装语言设置逻辑,统一管理所有时间的本地化。
建议在中间件或工具函数中集中处理语言设置。
配合i18n库可以实现完整的国际化方案。
多语言支持是现代Web应用的重要特性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// UserLocale 用户语言设置
type UserLocale struct {
Lang string
}
// FormatDateTime 根据用户语言格式化时间
func (u *UserLocale) FormatDateTime(c carbon.Carbon) string {
localized := c.SetLocale(u.Lang)
return localized.Format("Y-m-d H:i:s")
}
// FormatDate 根据用户语言格式化日期
func (u *UserLocale) FormatDate(c carbon.Carbon) string {
localized := c.SetLocale(u.Lang)
return fmt.Sprintf("%s %s",
localized.ToDateString(),
localized.ToWeekString())
}
// FormatRelative 根据用户语言格式化相对时间
func (u *UserLocale) FormatRelative(c carbon.Carbon) string {
localized := c.SetLocale(u.Lang)
return localized.DiffForHumans()
}
func main() {
now := carbon.Now()
// 模拟不同用户的语言设置
users := []UserLocale{
{Lang: "zh-CN"},
{Lang: "en"},
{Lang: "ja"},
{Lang: "ko"},
}
fmt.Println("=== 用户自定义语言显示 ===")
for _, user := range users {
fmt.Printf("\n语言: %s\n", user.Lang)
fmt.Println("日期:", user.FormatDate(now))
fmt.Println("时间:", user.FormatDateTime(now))
fmt.Println("相对:", user.FormatRelative(now.SubHours(2)))
}
// 实用场景:Web应用响应
fmt.Println("\n=== Web响应示例 ===")
type Article struct {
Title string
CreatedAt carbon.Carbon
}
article := Article{
Title: "Go语言教程",
CreatedAt: now.SubDays(3),
}
// 根据请求头的Accept-Language返回
userLang := "zh-CN"
locale := UserLocale{Lang: userLang}
fmt.Printf("标题: %s\n", article.Title)
fmt.Printf("发布时间: %s\n", locale.FormatDate(article.CreatedAt))
fmt.Printf("发布于: %s\n", locale.FormatRelative(article.CreatedAt))
}
---
4.4 常用格式化
01.数据库常用格式
a.说明
数据库中的时间字段通常使用标准格式存储。
MySQL常用DATETIME格式,PostgreSQL支持多种时间格式。
Carbon提供了便捷的方法输出数据库兼容格式。
ToDateTimeString()适合存储到DATETIME字段。
Timestamp()适合存储Unix时间戳。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// MySQL DATETIME格式
fmt.Println("=== MySQL格式 ===")
fmt.Println("DATETIME:", now.ToDateTimeString()) // 2024-01-15 10:30:00
fmt.Println("DATE:", now.ToDateString()) // 2024-01-15
fmt.Println("TIME:", now.ToTimeString()) // 10:30:00
fmt.Println("TIMESTAMP:", now.Timestamp()) // 1705287000
// PostgreSQL格式
fmt.Println("\n=== PostgreSQL格式 ===")
fmt.Println("TIMESTAMP:", now.ToDateTimeString())
fmt.Println("TIMESTAMPTZ:", now.ToIso8601String())
fmt.Println("DATE:", now.ToDateString())
fmt.Println("TIME:", now.ToTimeString())
// SQLite格式
fmt.Println("\n=== SQLite格式 ===")
fmt.Println("DATETIME:", now.ToDateTimeString())
fmt.Println("INTEGER:", now.Timestamp())
// 实用场景:SQL插入语句
fmt.Println("\n=== SQL示例 ===")
sql := fmt.Sprintf(
"INSERT INTO users (name, created_at) VALUES ('%s', '%s')",
"张三",
now.ToDateTimeString(),
)
fmt.Println(sql)
// 实用场景:SQL查询条件
start := carbon.Now().StartOfDay()
end := carbon.Now().EndOfDay()
querySql := fmt.Sprintf(
"SELECT * FROM orders WHERE created_at >= '%s' AND created_at <= '%s'",
start.ToDateTimeString(),
end.ToDateTimeString(),
)
fmt.Println("\n", querySql)
}
---
02.API接口常用格式
a.说明
API接口通常使用ISO8601或RFC3339标准格式。
这些格式包含完整的时区信息,适合跨时区数据传输。
JSON序列化时推荐使用ISO8601格式。
时间戳也是常用的API响应格式,简洁且易于处理。
根据API文档规范选择合适的格式。
b.代码示例
---
package main
import (
"encoding/json"
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// RESTful API常用格式
fmt.Println("=== RESTful API ===")
fmt.Println("ISO8601:", now.ToIso8601String())
fmt.Println("RFC3339:", now.ToRfc3339String())
fmt.Println("Unix时间戳:", now.Timestamp())
fmt.Println("毫秒时间戳:", now.TimestampMilli())
// JSON响应示例
fmt.Println("\n=== JSON响应 ===")
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Timestamp int64 `json:"timestamp"`
DateTime string `json:"datetime"`
}
resp := APIResponse{
Code: 200,
Message: "Success",
Data: map[string]string{"user": "张三"},
Timestamp: now.Timestamp(),
DateTime: now.ToIso8601String(),
}
jsonData, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(jsonData))
// GraphQL时间格式
fmt.Println("\n=== GraphQL ===")
fmt.Println("DateTime:", now.ToRfc3339String())
fmt.Println("Date:", now.ToDateString())
// 实用场景:API请求日志
fmt.Println("\n=== API日志 ===")
logEntry := fmt.Sprintf(
"[%s] GET /api/users - Status: 200 - Duration: 45ms",
now.ToIso8601String(),
)
fmt.Println(logEntry)
}
---
03.日志文件常用格式
a.说明
日志记录需要精确的时间戳,通常包含毫秒或微秒。
标准日志格式便于后续的日志分析和检索。
可以根据日志级别和用途选择不同的时间格式。
建议在日志中使用固定格式,便于日志解析工具处理。
时区信息在分布式系统日志中尤为重要。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 标准日志格式
fmt.Println("=== 标准日志格式 ===")
fmt.Println(now.Format("Y-m-d H:i:s")) // 2024-01-15 10:30:00
fmt.Println(now.Format("Y-m-d H:i:s.u")) // 2024-01-15 10:30:00.123456
// Apache/Nginx日志格式
fmt.Println("\n=== Web服务器日志 ===")
fmt.Println("Common:", now.Format("d/M/Y:H:i:s O")) // 15/Jan/2024:10:30:00 +0800
fmt.Println("ISO8601:", now.ToIso8601String()) // 2024-01-15T10:30:00+08:00
// 应用日志格式
fmt.Println("\n=== 应用日志 ===")
logFormats := map[string]string{
"INFO": fmt.Sprintf("[%s] INFO: Application started", now.Format("Y-m-d H:i:s")),
"DEBUG": fmt.Sprintf("[%s] DEBUG: Query executed in 45ms", now.Format("Y-m-d H:i:s.u")),
"ERROR": fmt.Sprintf("[%s] ERROR: Connection timeout", now.Format("Y-m-d H:i:s.u")),
"WARN": fmt.Sprintf("[%s] WARN: High memory usage", now.Format("Y-m-d H:i:s")),
}
for level, msg := range logFormats {
fmt.Println(msg)
}
// 文件名格式
fmt.Println("\n=== 日志文件名 ===")
fmt.Println("按天:", fmt.Sprintf("app_%s.log", now.Format("Y-m-d")))
fmt.Println("按小时:", fmt.Sprintf("app_%s.log", now.Format("Y-m-d_H")))
fmt.Println("精确:", fmt.Sprintf("backup_%s.log", now.Format("Ymd_His")))
// 实用场景:结构化日志
fmt.Println("\n=== 结构化日志JSON ===")
type LogEntry struct {
Timestamp int64 `json:"timestamp"`
DateTime string `json:"datetime"`
Level string `json:"level"`
Message string `json:"message"`
Service string `json:"service"`
}
entry := LogEntry{
Timestamp: now.Timestamp(),
DateTime: now.ToIso8601String(),
Level: "INFO",
Message: "Request processed successfully",
Service: "api-server",
}
fmt.Printf("{\n")
fmt.Printf(" \"timestamp\": %d,\n", entry.Timestamp)
fmt.Printf(" \"datetime\": \"%s\",\n", entry.DateTime)
fmt.Printf(" \"level\": \"%s\",\n", entry.Level)
fmt.Printf(" \"message\": \"%s\",\n", entry.Message)
fmt.Printf(" \"service\": \"%s\"\n", entry.Service)
fmt.Printf("}\n")
}
---
4.5 链式调用
01.链式操作基础
a.说明
Carbon所有方法都返回新的Carbon实例,支持流畅的链式调用。
链式调用可以在一行代码中完成多个操作,代码更简洁。
每个操作都是独立的,不会影响原始Carbon对象。
链式调用适合处理复杂的时间转换和格式化逻辑。
注意链式调用中的错误处理,任一环节出错都会传递到最后。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 基础链式调用
result1 := carbon.Now().
AddDays(7).
StartOfDay().
ToDateTimeString()
fmt.Println("7天后的凌晨:", result1)
// 复杂链式调用
result2 := carbon.Parse("2024-01-15").
AddMonths(3).
EndOfMonth().
SubDays(1).
ToDateString()
fmt.Println("3个月后的月末前一天:", result2)
// 时区转换链式调用
result3 := carbon.Now().
SetTimezone("America/New_York").
AddHours(12).
SetTimezone("Asia/Tokyo").
ToDateTimeString()
fmt.Println("纽约时间+12小时转东京:", result3)
// 格式化链式调用
result4 := carbon.Parse("2024-01-15 10:30:00").
AddWeek().
SetLocale("zh-CN").
Format("Y年m月d日 l")
fmt.Println("一周后(中文):", result4)
// 多步骤时间计算
result5 := carbon.Now().
StartOfMonth(). // 本月1号
AddMonths(1). // 下月1号
SubDay(). // 本月最后一天
ToDateString()
fmt.Println("本月最后一天:", result5)
// 工作日计算链式调用
result6 := carbon.Now().
AddWorkdays(10).
ToDateString()
fmt.Println("10个工作日后:", result6)
// 错误处理
invalid := carbon.Parse("invalid").
AddDay().
ToDateString()
if invalid == "" {
fmt.Println("链式调用中存在错误")
}
}
---
02.链式调用最佳实践
a.说明
合理使用链式调用可以提高代码可读性和维护性。
建议按逻辑分组进行链式调用,避免过长的调用链。
复杂逻辑可以拆分为多个链式调用,每个处理一个方面。
在链式调用前后添加注释,说明业务逻辑。
注意性能,避免不必要的中间操作。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 好的实践:分步骤的链式调用
fmt.Println("=== 好的实践 ===")
// 步骤1:获取下月第一天
nextMonthFirst := carbon.Now().
AddMonth().
StartOfMonth()
fmt.Println("下月第一天:", nextMonthFirst.ToDateString())
// 步骤2:转换时区并格式化
formatted := nextMonthFirst.
SetTimezone("UTC").
SetLocale("zh-CN").
Format("Y年m月d日 H时i分")
fmt.Println("格式化:", formatted)
// 实用场景:账单周期计算
fmt.Println("\n=== 账单周期 ===")
// 当前账单周期:本月1号到本月最后一天
currentPeriodStart := carbon.Now().StartOfMonth()
currentPeriodEnd := carbon.Now().EndOfMonth()
fmt.Printf("当前周期: %s ~ %s\n",
currentPeriodStart.ToDateString(),
currentPeriodEnd.ToDateString())
// 下个账单周期
nextPeriodStart := carbon.Now().
AddMonth().
StartOfMonth()
nextPeriodEnd := carbon.Now().
AddMonth().
EndOfMonth()
fmt.Printf("下个周期: %s ~ %s\n",
nextPeriodStart.ToDateString(),
nextPeriodEnd.ToDateString())
// 实用场景:会员到期提醒
fmt.Println("\n=== 会员到期提醒 ===")
membershipStart := carbon.Parse("2024-01-01")
membershipEnd := membershipStart.
AddYear(). // 会员期1年
SubDay() // 到期日前一天
fmt.Println("会员开始:", membershipStart.ToDateString())
fmt.Println("会员到期:", membershipEnd.ToDateString())
// 提醒时间:到期前7天
reminderDate := membershipEnd.
SubDays(7).
StartOfDay()
fmt.Println("提醒日期:", reminderDate.ToDateString())
// 实用场景:报表日期范围
fmt.Println("\n=== 季度报表 ===")
quarterStart := carbon.Now().
StartOfQuarter().
ToDateString()
quarterEnd := carbon.Now().
EndOfQuarter().
ToDateString()
fmt.Printf("Q%d报表: %s ~ %s\n",
carbon.Now().Quarter(),
quarterStart,
quarterEnd)
}
---
03.函数式编程风格
a.说明
链式调用配合函数式编程可以实现更优雅的代码。
可以将常用的链式操作封装为辅助函数。
函数返回Carbon实例,支持继续链式调用。
这种风格适合处理复杂的业务逻辑。
提高了代码的复用性和可测试性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// GetNextBusinessDay 获取下一个工作日
func GetNextBusinessDay(c carbon.Carbon) carbon.Carbon {
next := c.AddDay()
for next.IsWeekend() {
next = next.AddDay()
}
return next
}
// GetMonthRange 获取指定月份的范围
func GetMonthRange(c carbon.Carbon) (carbon.Carbon, carbon.Carbon) {
start := c.StartOfMonth()
end := c.EndOfMonth()
return start, end
}
// FormatChinese 格式化为中文显示
func FormatChinese(c carbon.Carbon) string {
return c.SetLocale("zh-CN").Format("Y年m月d日 l")
}
// ToUTC 转换为UTC时间
func ToUTC(c carbon.Carbon) carbon.Carbon {
return c.SetTimezone("UTC")
}
func main() {
now := carbon.Now()
// 使用辅助函数的链式调用
fmt.Println("=== 函数式风格 ===")
// 下一个工作日(UTC时区,中文显示)
result := FormatChinese(ToUTC(GetNextBusinessDay(now)))
fmt.Println("下一个工作日:", result)
// 本月范围
start, end := GetMonthRange(now)
fmt.Printf("本月: %s ~ %s\n",
FormatChinese(start),
FormatChinese(end))
// 实用场景:项目里程碑计算
fmt.Println("\n=== 项目里程碑 ===")
projectStart := carbon.Parse("2024-01-01")
milestones := map[string]carbon.Carbon{
"需求分析完成": GetNextBusinessDay(projectStart.AddWeeks(2)),
"设计评审完成": GetNextBusinessDay(projectStart.AddWeeks(4)),
"开发完成": GetNextBusinessDay(projectStart.AddWeeks(12)),
"测试完成": GetNextBusinessDay(projectStart.AddWeeks(14)),
"项目上线": GetNextBusinessDay(projectStart.AddWeeks(16)),
}
for milestone, date := range milestones {
fmt.Printf("%s: %s\n", milestone, FormatChinese(date))
}
// 实用场景:动态时间计算
fmt.Println("\n=== 动态计算示例 ===")
type TimeCalculator struct {
base carbon.Carbon
}
calc := TimeCalculator{base: carbon.Now()}
// 方法链式调用
result2 := calc.base.
AddMonths(3).
StartOfMonth().
ToDateString()
fmt.Println("3个月后的月初:", result2)
}
---
5 高级特性
5.1 时区处理
01.时区设置与转换
a.说明
Carbon支持全球所有IANA时区数据库中的时区。
SetTimezone()方法可以转换时区,保持同一时刻的不同地区表示。
时区转换在国际化应用、跨地区协作等场景中至关重要。
建议在数据库中存储UTC时间,显示时再转换为用户时区。
Carbon会自动处理夏令时等复杂的时区规则。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取当前时间(本地时区)
now := carbon.Now()
fmt.Println("本地时间:", now.ToDateTimeString())
fmt.Println("本地时区:", now.Timezone())
// 转换到不同时区
fmt.Println("\n=== 世界主要城市时间 ===")
cities := map[string]string{
"纽约": "America/New_York",
"伦敦": "Europe/London",
"巴黎": "Europe/Paris",
"东京": "Asia/Tokyo",
"悉尼": "Australia/Sydney",
"迪拜": "Asia/Dubai",
}
for city, timezone := range cities {
cityTime := now.SetTimezone(timezone)
fmt.Printf("%s: %s (%s)\n",
city,
cityTime.ToDateTimeString(),
cityTime.Timezone())
}
// UTC时间
utc := now.SetTimezone("UTC")
fmt.Println("\nUTC时间:", utc.ToDateTimeString())
// 时区缩写
fmt.Println("\n=== 常用时区 ===")
fmt.Println("PRC(北京):", now.SetTimezone(carbon.PRC).ToDateTimeString())
fmt.Println("UTC:", now.SetTimezone(carbon.UTC).ToDateTimeString())
fmt.Println("Local:", now.SetTimezone(carbon.Local).ToDateTimeString())
// 实用场景:跨时区会议时间
fmt.Println("\n=== 跨时区会议 ===")
meetingTime := carbon.Parse("2024-01-15 14:00:00").
SetTimezone("Asia/Shanghai")
fmt.Println("北京时间:", meetingTime.ToDateTimeString())
fmt.Println("纽约时间:", meetingTime.SetTimezone("America/New_York").ToDateTimeString())
fmt.Println("伦敦时间:", meetingTime.SetTimezone("Europe/London").ToDateTimeString())
}
---
02.时区偏移量
a.说明
时区偏移量表示与UTC的时差,以秒为单位。
正值表示东时区,负值表示西时区。
Offset()方法返回当前时区的偏移秒数。
OffsetHours()返回小时数,更直观。
了解时区偏移有助于手动处理时区相关计算。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 获取时区偏移量
fmt.Println("=== 时区偏移量 ===")
offset := now.Offset()
hours := now.OffsetHours()
fmt.Printf("当前时区: %s\n", now.Timezone())
fmt.Printf("偏移秒数: %d秒\n", offset)
fmt.Printf("偏移小时: %d小时\n", hours)
// 不同时区的偏移量
fmt.Println("\n=== 各时区偏移量 ===")
timezones := []string{
"Asia/Shanghai", // UTC+8
"America/New_York", // UTC-5
"Europe/London", // UTC+0
"Asia/Tokyo", // UTC+9
"Australia/Sydney", // UTC+10
}
for _, tz := range timezones {
t := now.SetTimezone(tz)
fmt.Printf("%-20s UTC%+d (%d秒)\n",
tz,
t.OffsetHours(),
t.Offset())
}
// 实用场景:时区感知的时间戳
fmt.Println("\n=== 时区与时间戳 ===")
shanghai := carbon.Parse("2024-01-15 12:00:00").
SetTimezone("Asia/Shanghai")
newYork := carbon.Parse("2024-01-15 12:00:00").
SetTimezone("America/New_York")
fmt.Println("上海12点:", shanghai.ToDateTimeString())
fmt.Println("纽约12点:", newYork.ToDateTimeString())
fmt.Println("上海时间戳:", shanghai.Timestamp())
fmt.Println("纽约时间戳:", newYork.Timestamp())
fmt.Println("时间戳差值:", shanghai.Timestamp()-newYork.Timestamp(), "秒")
}
---
03.夏令时处理
a.说明
夏令时(DST)是部分国家和地区实行的时间调整制度。
Carbon自动处理夏令时的时间变化,无需手动干预。
IsDST()方法可以判断当前时间是否处于夏令时。
在涉及夏令时的时区中,时间加减可能出现非连续跳跃。
了解夏令时机制有助于避免时区相关的bug。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 夏令时判断
fmt.Println("=== 夏令时判断 ===")
// 美国夏令时示例(3月第二个周日开始,11月第一个周日结束)
summer := carbon.Parse("2024-07-15 12:00:00").
SetTimezone("America/New_York")
winter := carbon.Parse("2024-01-15 12:00:00").
SetTimezone("America/New_York")
fmt.Printf("夏季(%s): %v\n",
summer.ToDateString(),
summer.IsDST())
fmt.Printf("冬季(%s): %v\n",
winter.ToDateString(),
winter.IsDST())
// 时区偏移在夏令时的变化
fmt.Println("\n=== 夏令时偏移变化 ===")
fmt.Printf("夏季偏移: UTC%+d\n", summer.OffsetHours())
fmt.Printf("冬季偏移: UTC%+d\n", winter.OffsetHours())
// 中国不实行夏令时
shanghaiSummer := carbon.Parse("2024-07-15 12:00:00").
SetTimezone("Asia/Shanghai")
shanghaiWinter := carbon.Parse("2024-01-15 12:00:00").
SetTimezone("Asia/Shanghai")
fmt.Println("\n=== 中国时区(无夏令时) ===")
fmt.Printf("夏季: %v, UTC%+d\n",
shanghaiSummer.IsDST(),
shanghaiSummer.OffsetHours())
fmt.Printf("冬季: %v, UTC%+d\n",
shanghaiWinter.IsDST(),
shanghaiWinter.OffsetHours())
// 实用场景:夏令时切换时的时间计算
fmt.Println("\n=== 夏令时切换 ===")
// 2024年美国夏令时开始日期大约在3月10日
beforeDST := carbon.Parse("2024-03-10 01:00:00").
SetTimezone("America/New_York")
afterDST := beforeDST.AddHours(2)
fmt.Println("切换前:", beforeDST.ToDateTimeString(), "DST:", beforeDST.IsDST())
fmt.Println("切换后:", afterDST.ToDateTimeString(), "DST:", afterDST.IsDST())
}
---
5.2 星期处理
01.星期相关方法
a.说明
Carbon提供了丰富的星期处理方法,包括获取、判断和计算。
DayOfWeek()返回星期几的数字(0=周日,6=周六)。
WeekOfMonth()返回当月的第几周,WeekOfYear()返回当年的第几周。
IsXxx()系列方法可以判断是否为特定的星期几。
星期相关功能在排班、报表统计等场景中非常实用。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Parse("2024-01-15") // 2024年1月15日是周一
// 获取星期信息
fmt.Println("=== 星期信息 ===")
fmt.Println("日期:", now.ToDateString())
fmt.Println("星期几:", now.DayOfWeek()) // 1(周一)
fmt.Println("星期名称:", now.ToWeekString()) // Monday
fmt.Println("短名称:", now.ToShortWeekString()) // Mon
// 当月第几周
fmt.Println("当月第几周:", now.WeekOfMonth()) // 3
// 当年第几周
fmt.Println("当年第几周:", now.WeekOfYear()) // 3
// 判断是否为特定星期几
fmt.Println("\n=== 星期判断 ===")
fmt.Println("是否周一:", now.IsMonday()) // true
fmt.Println("是否周二:", now.IsTuesday()) // false
fmt.Println("是否周三:", now.IsWednesday()) // false
fmt.Println("是否周四:", now.IsThursday()) // false
fmt.Println("是否周五:", now.IsFriday()) // false
fmt.Println("是否周六:", now.IsSaturday()) // false
fmt.Println("是否周日:", now.IsSunday()) // false
// 工作日和周末
fmt.Println("\n=== 工作日判断 ===")
fmt.Println("是否工作日:", now.IsWeekday()) // true
fmt.Println("是否周末:", now.IsWeekend()) // false
// 遍历一周
fmt.Println("\n=== 本周每天 ===")
weekStart := now.StartOfWeek()
for i := 0; i < 7; i++ {
day := weekStart.AddDays(i)
weekday := "工作日"
if day.IsWeekend() {
weekday = "周末"
}
fmt.Printf("%s %s (%s)\n",
day.ToDateString(),
day.ToWeekString(),
weekday)
}
// 实用场景:查找本月所有周末
fmt.Println("\n=== 本月所有周末 ===")
monthStart := now.StartOfMonth()
daysInMonth := monthStart.DaysInMonth()
for i := 0; i < daysInMonth; i++ {
day := monthStart.AddDays(i)
if day.IsWeekend() {
fmt.Println(day.ToDateString(), day.ToWeekString())
}
}
}
---
02.周的开始和结束
a.说明
StartOfWeek()和EndOfWeek()获取一周的开始和结束时间。
默认周一为一周的开始,符合ISO8601标准。
周的概念在报表统计、周期性任务等场景中广泛使用。
可以配合WeekOfYear()实现按周的数据聚合。
了解周的边界对于正确处理跨周业务逻辑很重要。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Parse("2024-01-15") // 周一
// 本周范围
fmt.Println("=== 本周范围 ===")
weekStart := now.StartOfWeek()
weekEnd := now.EndOfWeek()
fmt.Println("本周开始:", weekStart.ToDateTimeString())
fmt.Println("本周结束:", weekEnd.ToDateTimeString())
fmt.Println("本周天数:", weekEnd.DiffInDays(weekStart)+1)
// 上周范围
fmt.Println("\n=== 上周范围 ===")
lastWeekStart := now.SubWeek().StartOfWeek()
lastWeekEnd := now.SubWeek().EndOfWeek()
fmt.Println("上周开始:", lastWeekStart.ToDateString())
fmt.Println("上周结束:", lastWeekEnd.ToDateString())
// 下周范围
fmt.Println("\n=== 下周范围 ===")
nextWeekStart := now.AddWeek().StartOfWeek()
nextWeekEnd := now.AddWeek().EndOfWeek()
fmt.Println("下周开始:", nextWeekStart.ToDateString())
fmt.Println("下周结束:", nextWeekEnd.ToDateString())
// 实用场景:周报日期范围
fmt.Println("\n=== 周报示例 ===")
fmt.Printf("第%d周周报\n", now.WeekOfYear())
fmt.Printf("统计周期: %s 至 %s\n",
weekStart.ToDateString(),
weekEnd.ToDateString())
// 实用场景:本周工作日数量
fmt.Println("\n=== 本周工作日 ===")
workdayCount := 0
current := weekStart
for current.Lte(weekEnd) {
if current.IsWeekday() {
workdayCount++
fmt.Printf("%s %s\n",
current.ToDateString(),
current.ToWeekString())
}
current = current.AddDay()
}
fmt.Printf("本周共有 %d 个工作日\n", workdayCount)
}
---
03.ISO周和周年
a.说明
ISO8601标准定义了ISO周的概念,用于标准化的周数计算。
ISO周的第一周必须包含当年的第一个周四。
IsoWeek()返回ISO周数,IsoWeeksOfYear()返回当年的ISO周总数。
ISO周在国际化项目和标准化报表中广泛使用。
了解ISO周有助于正确处理跨年周等边界情况。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 年初日期
newYear := carbon.Parse("2024-01-01")
fmt.Println("=== ISO周信息 ===")
fmt.Println("日期:", newYear.ToDateString())
fmt.Println("星期几:", newYear.ToWeekString())
fmt.Println("ISO周数:", newYear.WeekOfYear())
// 年末日期
yearEnd := carbon.Parse("2024-12-31")
fmt.Println("\n日期:", yearEnd.ToDateString())
fmt.Println("星期几:", yearEnd.ToWeekString())
fmt.Println("ISO周数:", yearEnd.WeekOfYear())
fmt.Println("全年ISO周数:", yearEnd.WeeksOfYear())
// 遍历全年的周
fmt.Println("\n=== 2024年各周起止日期 ===")
year := carbon.Parse("2024-01-01")
weekCount := year.WeeksOfYear()
// 只显示前4周和后4周
fmt.Println("前4周:")
for week := 1; week <= 4; week++ {
// 找到该周的第一天
current := year
for current.WeekOfYear() != week {
current = current.AddDay()
}
weekStart := current.StartOfWeek()
weekEnd := current.EndOfWeek()
fmt.Printf("第%02d周: %s ~ %s\n",
week,
weekStart.ToDateString(),
weekEnd.ToDateString())
}
fmt.Println("...")
fmt.Println("后4周:")
for week := weekCount - 3; week <= weekCount; week++ {
current := carbon.Parse("2024-12-31")
for current.WeekOfYear() != week {
current = current.SubDay()
}
weekStart := current.StartOfWeek()
weekEnd := current.EndOfWeek()
fmt.Printf("第%02d周: %s ~ %s\n",
week,
weekStart.ToDateString(),
weekEnd.ToDateString())
}
// 实用场景:按周统计数据
fmt.Println("\n=== 按周统计示例 ===")
dates := []string{
"2024-01-01",
"2024-01-08",
"2024-01-15",
"2024-01-22",
}
weeklyData := make(map[int]int)
for _, dateStr := range dates {
date := carbon.Parse(dateStr)
week := date.WeekOfYear()
weeklyData[week]++
}
for week, count := range weeklyData {
fmt.Printf("第%d周: %d条记录\n", week, count)
}
}
---
5.3 季节判断
01.季节判断方法
a.说明
Carbon提供了季节判断方法,可以判断当前时间属于哪个季节。
Season()方法返回季节名称,IsSpring()等方法判断是否为特定季节。
季节划分基于北半球的气象季节标准。
在商业分析、季节性活动等场景中非常实用。
可以根据业务需求自定义季节划分逻辑。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 不同月份的季节判断
months := []string{
"2024-01-15", // 冬季
"2024-04-15", // 春季
"2024-07-15", // 夏季
"2024-10-15", // 秋季
}
fmt.Println("=== 季节判断 ===")
for _, month := range months {
date := carbon.Parse(month)
fmt.Printf("%s: %s\n",
date.Format("m月"),
date.Season())
}
// 使用季节判断方法
summer := carbon.Parse("2024-07-15")
fmt.Println("\n=== 季节判断方法 ===")
fmt.Println("日期:", summer.ToDateString())
fmt.Println("是否春季:", summer.IsSpring())
fmt.Println("是否夏季:", summer.IsSummer())
fmt.Println("是否秋季:", summer.IsAutumn())
fmt.Println("是否冬季:", summer.IsWinter())
// 遍历全年,统计各季节天数
fmt.Println("\n=== 2024年各季节天数 ===")
seasonDays := map[string]int{
"spring": 0,
"summer": 0,
"autumn": 0,
"winter": 0,
}
year := carbon.Parse("2024-01-01")
for i := 0; i < 366; i++ { // 2024是闰年
day := year.AddDays(i)
season := day.Season()
seasonDays[season]++
}
for season, days := range seasonDays {
fmt.Printf("%s: %d天\n", season, days)
}
// 实用场景:季节性商品推荐
fmt.Println("\n=== 季节性商品推荐 ===")
today := carbon.Now()
season := today.Season()
products := map[string][]string{
"spring": {"春装", "雨伞", "防晒霜"},
"summer": {"防晒衣", "凉鞋", "游泳装备"},
"autumn": {"秋装", "保温杯", "风衣"},
"winter": {"羽绒服", "暖宝宝", "加湿器"},
}
fmt.Printf("当前季节: %s\n", season)
fmt.Println("推荐商品:")
for _, product := range products[season] {
fmt.Printf(" - %s\n", product)
}
}
---
02.季度处理
a.说明
Quarter()方法返回当前是第几季度(1-4)。
StartOfQuarter()和EndOfQuarter()获取季度的开始和结束时间。
季度在财务报表、业绩统计等商业场景中广泛使用。
可以方便地进行季度维度的数据聚合和分析。
季度计算在跨年场景中需要特别注意。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取当前季度
now := carbon.Now()
fmt.Println("=== 当前季度信息 ===")
fmt.Println("当前日期:", now.ToDateString())
fmt.Println("当前季度:", now.Quarter())
fmt.Println("季度名称:", now.ToQuarterString())
// 季度范围
quarterStart := now.StartOfQuarter()
quarterEnd := now.EndOfQuarter()
fmt.Println("季度开始:", quarterStart.ToDateString())
fmt.Println("季度结束:", quarterEnd.ToDateString())
fmt.Println("季度天数:", quarterEnd.DiffInDays(quarterStart)+1)
// 遍历全年4个季度
fmt.Println("\n=== 2024年各季度 ===")
year := carbon.Parse("2024-01-01")
for q := 1; q <= 4; q++ {
// 找到该季度的第一个月
quarterMonth := (q-1)*3 + 1
quarterDate := carbon.Parse(fmt.Sprintf("2024-%02d-01", quarterMonth))
qStart := quarterDate.StartOfQuarter()
qEnd := quarterDate.EndOfQuarter()
days := qEnd.DiffInDays(qStart) + 1
fmt.Printf("Q%d: %s ~ %s (%d天)\n",
q,
qStart.ToDateString(),
qEnd.ToDateString(),
days)
}
// 实用场景:季度报表
fmt.Println("\n=== 季度报表示例 ===")
currentQ := now.Quarter()
fmt.Printf("2024年第%d季度业绩报告\n", currentQ)
fmt.Printf("统计周期: %s 至 %s\n",
quarterStart.ToDateString(),
quarterEnd.ToDateString())
// 计算已过去的季度数
yearStart := carbon.Parse("2024-01-01")
quartersPassed := now.DiffInQuarters(yearStart)
fmt.Printf("年初至今已过 %d 个季度\n", quartersPassed)
// 判断是否为季度末
lastDayOfQuarter := now.EndOfQuarter()
isQuarterEnd := now.ToDateString() == lastDayOfQuarter.ToDateString()
fmt.Printf("是否季度末: %v\n", isQuarterEnd)
}
---
03.自定义季节划分
a.说明
Carbon的默认季节划分可能不适合所有业务场景。
可以根据实际需求自定义季节划分逻辑。
例如根据气候、销售周期或其他业务特征划分。
自定义季节划分需要封装独立的判断函数。
灵活的季节划分能更好地服务于业务分析。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// CustomSeason 自定义季节类型
type CustomSeason string
const (
EarlySpring CustomSeason = "早春"
LateSpring CustomSeason = "晚春"
EarlySummer CustomSeason = "初夏"
MidSummer CustomSeason = "盛夏"
EarlyAutumn CustomSeason = "初秋"
LateAutumn CustomSeason = "晚秋"
EarlyWinter CustomSeason = "初冬"
MidWinter CustomSeason = "隆冬"
)
// GetCustomSeason 获取自定义季节(按月份细分)
func GetCustomSeason(c carbon.Carbon) CustomSeason {
month := c.Month()
switch month {
case 1:
return MidWinter
case 2:
return EarlySpring
case 3:
return LateSpring
case 4:
return LateSpring
case 5:
return EarlySummer
case 6:
return MidSummer
case 7:
return MidSummer
case 8:
return EarlyAutumn
case 9:
return EarlyAutumn
case 10:
return LateAutumn
case 11:
return EarlyWinter
case 12:
return MidWinter
default:
return MidWinter
}
}
// GetBusinessSeason 商业季节(根据销售特征)
func GetBusinessSeason(c carbon.Carbon) string {
month := c.Month()
switch {
case month == 1 || month == 2:
return "春节季"
case month >= 3 && month <= 5:
return "春季促销"
case month == 6:
return "618购物节"
case month >= 7 && month <= 9:
return "暑期档"
case month == 10:
return "国庆季"
case month == 11:
return "双十一"
case month == 12:
return "年终促销"
default:
return "常规季"
}
}
func main() {
// 使用自定义季节划分
fmt.Println("=== 自定义季节划分 ===")
months := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
for _, month := range months {
date := carbon.Parse(fmt.Sprintf("2024-%02d-15", month))
customSeason := GetCustomSeason(date)
businessSeason := GetBusinessSeason(date)
fmt.Printf("%2d月: %s / %s\n",
month,
customSeason,
businessSeason)
}
// 实用场景:商品库存调整
fmt.Println("\n=== 商品库存建议 ===")
today := carbon.Now()
businessSeason := GetBusinessSeason(today)
inventory := map[string]map[string]string{
"春节季": {"重点": "年货、礼品", "减少": "夏季商品"},
"春季促销": {"重点": "春装、户外", "减少": "冬季商品"},
"618购物节": {"重点": "电子产品、家电", "减少": "无"},
"暑期档": {"重点": "夏季服装、旅游用品", "减少": "春装"},
"国庆季": {"重点": "旅游用品、礼品", "减少": "无"},
"双十一": {"重点": "全品类", "减少": "无"},
"年终促销": {"重点": "冬季商品、年货", "减少": "夏季商品"},
}
fmt.Printf("当前: %s\n", businessSeason)
if advice, ok := inventory[businessSeason]; ok {
fmt.Printf("重点备货: %s\n", advice["重点"])
fmt.Printf("减少库存: %s\n", advice["减少"])
}
}
---
5.4 生肖星座
01.生肖功能
a.说明
Carbon提供了中国传统生肖的计算功能。
Animal()方法返回生肖名称,支持中文和英文。
生肖是根据农历年份计算的,与公历不完全对应。
在中国特色应用、用户画像等场景中可以增加趣味性。
生肖功能展示了Carbon对本地化特性的支持。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取生肖
now := carbon.Now()
fmt.Println("=== 生肖查询 ===")
fmt.Println("当前年份:", now.Year())
fmt.Println("生肖:", now.Animal())
// 查询不同年份的生肖
fmt.Println("\n=== 十二生肖轮回 ===")
baseYear := 2020
for i := 0; i < 12; i++ {
year := baseYear + i
date := carbon.Parse(fmt.Sprintf("%d-01-01", year))
fmt.Printf("%d年: %s\n", year, date.Animal())
}
// 根据出生日期查生肖
fmt.Println("\n=== 根据生日查生肖 ===")
birthdays := []string{
"1990-05-15",
"1995-08-20",
"2000-03-10",
"2010-12-25",
}
for _, birthday := range birthdays {
date := carbon.Parse(birthday)
fmt.Printf("%s 出生: %s年 属%s\n",
birthday,
date.Format("Y"),
date.Animal())
}
// 实用场景:生肖运势推荐
fmt.Println("\n=== 今日生肖运势 ===")
userBirthday := carbon.Parse("1990-05-15")
userAnimal := userBirthday.Animal()
fmt.Printf("您的生肖: %s\n", userAnimal)
fmt.Printf("今日运势: 根据%s年运势...\n", userAnimal)
// 生肖配对(示例)
partnerBirthday := carbon.Parse("1995-08-20")
partnerAnimal := partnerBirthday.Animal()
fmt.Printf("\n=== 生肖配对 ===\n")
fmt.Printf("您: %s\n", userAnimal)
fmt.Printf("TA: %s\n", partnerAnimal)
}
---
02.星座功能
a.说明
Constellation()方法返回星座名称。
星座是根据公历出生月日计算的,共12个星座。
每个星座都有特定的日期范围。
在社交应用、个性化推荐等场景中广泛使用。
星座功能可以增加应用的趣味性和用户粘性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取星座
birthday := carbon.Parse("1990-05-15")
fmt.Println("=== 星座查询 ===")
fmt.Println("生日:", birthday.ToDateString())
fmt.Println("星座:", birthday.Constellation())
// 遍历12星座的日期范围
fmt.Println("\n=== 十二星座 ===")
constellationDates := []string{
"01-20", // 水瓶座
"02-19", // 双鱼座
"03-21", // 白羊座
"04-20", // 金牛座
"05-21", // 双子座
"06-22", // 巨蟹座
"07-23", // 狮子座
"08-23", // 处女座
"09-23", // 天秤座
"10-24", // 天蝎座
"11-23", // 射手座
"12-22", // 摩羯座
}
for _, dateStr := range constellationDates {
date := carbon.Parse("2024-" + dateStr)
fmt.Printf("%s: %s\n",
date.Format("m月d日"),
date.Constellation())
}
// 查询多个生日的星座
fmt.Println("\n=== 批量查询星座 ===")
birthdays := []string{
"1990-01-15",
"1992-03-20",
"1995-07-10",
"2000-12-25",
}
for _, bd := range birthdays {
date := carbon.Parse(bd)
fmt.Printf("%s: %s\n",
date.ToDateString(),
date.Constellation())
}
// 实用场景:星座匹配度
fmt.Println("\n=== 星座配对 ===")
user1 := carbon.Parse("1990-05-15")
user2 := carbon.Parse("1992-09-20")
fmt.Printf("用户1: %s (%s)\n",
user1.Constellation(),
user1.ToDateString())
fmt.Printf("用户2: %s (%s)\n",
user2.Constellation(),
user2.ToDateString())
fmt.Println("配对指数: (根据星座特征计算...)")
}
---
03.综合应用
a.说明
生肖和星座可以结合使用,提供更丰富的用户画像。
可以根据生肖和星座推送个性化内容。
在社交、娱乐、电商等领域有广泛应用。
结合其他用户数据可以实现精准营销。
注意尊重用户隐私,合理使用这些特性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// UserProfile 用户画像
type UserProfile struct {
Name string
Birthday carbon.Carbon
Age int
Animal string
Constellation string
}
// NewUserProfile 创建用户画像
func NewUserProfile(name, birthday string) *UserProfile {
bd := carbon.Parse(birthday)
age := carbon.Now().DiffInYears(bd)
return &UserProfile{
Name: name,
Birthday: bd,
Age: int(age),
Animal: bd.Animal(),
Constellation: bd.Constellation(),
}
}
// String 格式化输出
func (up *UserProfile) String() string {
return fmt.Sprintf(
"姓名: %s\n生日: %s\n年龄: %d岁\n生肖: %s\n星座: %s",
up.Name,
up.Birthday.ToDateString(),
up.Age,
up.Animal,
up.Constellation,
)
}
func main() {
// 创建用户画像
fmt.Println("=== 用户画像 ===")
users := []*UserProfile{
NewUserProfile("张三", "1990-05-15"),
NewUserProfile("李四", "1995-09-20"),
NewUserProfile("王五", "2000-01-10"),
}
for i, user := range users {
fmt.Printf("\n用户%d:\n%s\n", i+1, user.String())
}
// 实用场景:生日提醒
fmt.Println("\n=== 生日提醒 ===")
today := carbon.Now()
for _, user := range users {
// 检查是否本月生日
if user.Birthday.Month() == today.Month() {
fmt.Printf("%s 的生日在本月(%d月%d日)\n",
user.Name,
user.Birthday.Month(),
user.Birthday.Day())
// 计算距离生日的天数
thisBirthday := carbon.Parse(fmt.Sprintf(
"%d-%02d-%02d",
today.Year(),
user.Birthday.Month(),
user.Birthday.Day(),
))
if thisBirthday.Gte(today) {
days := thisBirthday.DiffInDays(today)
fmt.Printf(" 距离生日还有 %d 天\n", days)
}
}
}
// 实用场景:星座运势推送
fmt.Println("\n=== 每日星座运势 ===")
for _, user := range users {
fmt.Printf("%s(%s): 今日运势...\n",
user.Name,
user.Constellation)
}
// 实用场景:生肖年份特惠
fmt.Println("\n=== 本命年优惠 ===")
currentYearAnimal := today.Animal()
fmt.Printf("今年是%s年\n", currentYearAnimal)
for _, user := range users {
if user.Animal == currentYearAnimal {
fmt.Printf("%s 属%s,本命年享受特别优惠!\n",
user.Name,
user.Animal)
}
}
}
---
5.5 农历支持
01.农历日期获取
a.说明
Carbon提供了农历(阴历)日期的计算功能。
Lunar()方法返回对应的农历日期信息。
农历在中国传统节日、生日庆祝等场景中广泛使用。
农历计算复杂,涉及闰月、节气等传统历法知识。
Carbon封装了农历计算逻辑,开发者无需深入了解细节。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 获取当前日期的农历
now := carbon.Now()
fmt.Println("=== 农历查询 ===")
fmt.Println("公历:", now.ToDateString())
fmt.Println("农历年:", now.Lunar().Year())
fmt.Println("农历月:", now.Lunar().Month())
fmt.Println("农历日:", now.Lunar().Day())
fmt.Println("是否闰月:", now.Lunar().IsLeapMonth())
// 农历年的生肖
fmt.Println("农历生肖:", now.Lunar().Animal())
// 农历月份中文表示
fmt.Println("\n=== 农历月份 ===")
months := []string{
"2024-01-15", "2024-02-15", "2024-03-15",
"2024-04-15", "2024-05-15", "2024-06-15",
"2024-07-15", "2024-08-15", "2024-09-15",
"2024-10-15", "2024-11-15", "2024-12-15",
}
for _, dateStr := range months {
date := carbon.Parse(dateStr)
lunar := date.Lunar()
fmt.Printf("%s: 农历%d月%d日\n",
date.Format("m月"),
lunar.Month(),
lunar.Day())
}
// 查询农历生日
fmt.Println("\n=== 农历生日查询 ===")
birthday := carbon.Parse("1990-05-15")
lunarBD := birthday.Lunar()
fmt.Println("公历生日:", birthday.ToDateString())
fmt.Printf("农历生日: %d年%d月%d日\n",
lunarBD.Year(),
lunarBD.Month(),
lunarBD.Day())
fmt.Println("农历生肖:", lunarBD.Animal())
}
---
02.传统节日判断
a.说明
许多中国传统节日是按农历计算的。
可以根据农历日期判断是否为传统节日。
常见节日包括春节、元宵、端午、中秋等。
节日判断在节日营销、节日问候等场景中非常实用。
可以封装节日判断逻辑,方便在项目中复用。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// ChineseHoliday 中国传统节日
type ChineseHoliday struct {
Name string
Month int
Day int
}
// 传统节日列表
var chineseHolidays = []ChineseHoliday{
{"春节", 1, 1},
{"元宵节", 1, 15},
{"龙抬头", 2, 2},
{"端午节", 5, 5},
{"七夕节", 7, 7},
{"中元节", 7, 15},
{"中秋节", 8, 15},
{"重阳节", 9, 9},
{"腊八节", 12, 8},
{"小年", 12, 23},
{"除夕", 12, 30},
}
// GetChineseHoliday 获取传统节日
func GetChineseHoliday(c carbon.Carbon) string {
lunar := c.Lunar()
month := lunar.Month()
day := lunar.Day()
for _, holiday := range chineseHolidays {
if holiday.Month == month && holiday.Day == day {
return holiday.Name
}
}
return ""
}
func main() {
// 检查今天是否为传统节日
today := carbon.Now()
fmt.Println("=== 今日节日查询 ===")
fmt.Println("公历:", today.ToDateString())
fmt.Printf("农历: %d月%d日\n",
today.Lunar().Month(),
today.Lunar().Day())
holiday := GetChineseHoliday(today)
if holiday != "" {
fmt.Printf("今天是: %s\n", holiday)
} else {
fmt.Println("今天不是传统节日")
}
// 列出2024年的传统节日公历日期
fmt.Println("\n=== 2024年传统节日 ===")
// 注意:这里简化处理,实际需要农历转公历的完整实现
fmt.Println("春节: 2024-02-10")
fmt.Println("元宵节: 2024-02-24")
fmt.Println("端午节: 2024-06-10")
fmt.Println("中秋节: 2024-09-17")
// 实用场景:节日问候
fmt.Println("\n=== 节日问候 ===")
testDates := []string{
"2024-02-10", // 春节
"2024-06-10", // 端午
"2024-09-17", // 中秋
}
for _, dateStr := range testDates {
date := carbon.Parse(dateStr)
holiday := GetChineseHoliday(date)
if holiday != "" {
fmt.Printf("%s: 祝您%s快乐!\n",
date.ToDateString(),
holiday)
}
}
}
---
03.农历闰月处理
a.说明
农历闰月是中国传统历法的重要特征。
闰月年份会有13个月,某个月份会重复出现。
IsLeapMonth()判断当前月是否为闰月。
闰月计算复杂,但Carbon已经处理好底层逻辑。
在处理农历生日等场景时需要考虑闰月情况。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 检查闰月
fmt.Println("=== 农历闰月检查 ===")
// 遍历一年的月份,查找闰月
year := carbon.Parse("2024-01-01")
fmt.Println("2024年农历闰月检查:")
hasLeapMonth := false
for month := 1; month <= 12; month++ {
date := carbon.Parse(fmt.Sprintf("2024-%02d-15", month))
lunar := date.Lunar()
if lunar.IsLeapMonth() {
fmt.Printf("农历%d月是闰月\n", lunar.Month())
hasLeapMonth = true
}
}
if !hasLeapMonth {
fmt.Println("2024年没有闰月")
}
// 农历闰年示例
fmt.Println("\n=== 农历闰年 ===")
// 2023年农历有闰二月
leapYearDate := carbon.Parse("2023-03-22") // 公历对应农历闰二月
lunar := leapYearDate.Lunar()
fmt.Println("公历:", leapYearDate.ToDateString())
fmt.Printf("农历: %d年%d月%d日\n",
lunar.Year(),
lunar.Month(),
lunar.Day())
fmt.Println("是否闰月:", lunar.IsLeapMonth())
// 实用场景:农历生日提醒
fmt.Println("\n=== 农历生日提醒 ===")
// 假设用户农历生日是三月十五
userLunarBirthday := map[string]int{
"month": 3,
"day": 15,
}
fmt.Printf("您的农历生日: %d月%d日\n",
userLunarBirthday["month"],
userLunarBirthday["day"])
// 检查今年是否有闰三月
// 如果有闰三月,需要提醒用户选择在哪个三月过生日
fmt.Println("提示: 如遇闰月年份,请选择在正月还是闰月庆祝生日")
}
---
5.6 工作日计算
01.工作日判断
a.说明
IsWeekday()判断是否为工作日(周一至周五)。
IsWeekend()判断是否为周末(周六和周日)。
默认的工作日定义可能不适合所有国家和地区。
可以根据实际需求自定义工作日规则。
工作日判断在考勤、排班、项目管理等场景中非常实用。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 一周的工作日判断
fmt.Println("=== 一周工作日判断 ===")
weekStart := carbon.Now().StartOfWeek()
for i := 0; i < 7; i++ {
day := weekStart.AddDays(i)
dayType := "工作日"
if day.IsWeekend() {
dayType = "周末"
}
fmt.Printf("%s %s: %s\n",
day.ToDateString(),
day.ToWeekString(),
dayType)
}
// 统计本月工作日数量
fmt.Println("\n=== 本月工作日统计 ===")
monthStart := carbon.Now().StartOfMonth()
daysInMonth := monthStart.DaysInMonth()
workdays := 0
weekends := 0
for i := 0; i < daysInMonth; i++ {
day := monthStart.AddDays(i)
if day.IsWeekday() {
workdays++
} else {
weekends++
}
}
fmt.Printf("工作日: %d天\n", workdays)
fmt.Printf("周末: %d天\n", weekends)
fmt.Printf("总计: %d天\n", daysInMonth)
// 查找本月所有工作日
fmt.Println("\n=== 本月工作日列表 ===")
for i := 0; i < daysInMonth; i++ {
day := monthStart.AddDays(i)
if day.IsWeekday() {
fmt.Printf("%s %s\n",
day.ToDateString(),
day.ToShortWeekString())
}
}
}
---
02.工作日加减
a.说明
AddWorkday()和SubWorkday()方法用于工作日的加减。
这些方法会自动跳过周末,只计算工作日。
AddWorkdays(n)可以一次性增加n个工作日。
在项目工期计算、交货日期估算等场景中非常实用。
注意这些方法不考虑法定节假日,如需要需自行实现。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 工作日加减示例
friday := carbon.Parse("2024-01-12") // 周五
fmt.Println("=== 工作日加减 ===")
fmt.Println("起始日期:", friday.ToDateString(), friday.ToWeekString())
// 加1个工作日(跳过周末到周一)
nextWorkday := friday.AddWorkday()
fmt.Println("下1个工作日:", nextWorkday.ToDateString(), nextWorkday.ToWeekString())
// 加5个工作日
after5Workdays := friday.AddWorkdays(5)
fmt.Println("5个工作日后:", after5Workdays.ToDateString(), after5Workdays.ToWeekString())
// 减工作日
monday := carbon.Parse("2024-01-15") // 周一
fmt.Println("\n起始日期:", monday.ToDateString(), monday.ToWeekString())
prevWorkday := monday.SubWorkday()
fmt.Println("上1个工作日:", prevWorkday.ToDateString(), prevWorkday.ToWeekString())
// 实用场景:项目工期计算
fmt.Println("\n=== 项目工期计算 ===")
projectStart := carbon.Parse("2024-01-15")
workDaysNeeded := 20
projectEnd := projectStart.AddWorkdays(workDaysNeeded)
fmt.Println("项目开始:", projectStart.ToDateString())
fmt.Println("需要工作日:", workDaysNeeded)
fmt.Println("预计完成:", projectEnd.ToDateString())
fmt.Println("实际跨度:", projectEnd.DiffInDays(projectStart), "天")
// 计算两个日期之间的工作日数量
fmt.Println("\n=== 工作日数量计算 ===")
start := carbon.Parse("2024-01-01")
end := carbon.Parse("2024-01-31")
count := 0
current := start
for current.Lte(end) {
if current.IsWeekday() {
count++
}
current = current.AddDay()
}
fmt.Printf("%s 至 %s\n", start.ToDateString(), end.ToDateString())
fmt.Printf("共有 %d 个工作日\n", count)
}
---
03.自定义工作日规则
a.说明
不同国家和地区的工作日定义可能不同。
有些国家周五或周六是休息日而非周日。
企业可能有特殊的工作日安排,如大小周制度。
可以封装自定义的工作日判断和计算逻辑。
灵活的工作日规则使Carbon适应各种业务场景。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// WorkdayRule 工作日规则接口
type WorkdayRule interface {
IsWorkday(c carbon.Carbon) bool
}
// StandardRule 标准工作日规则(周一至周五)
type StandardRule struct{}
func (r *StandardRule) IsWorkday(c carbon.Carbon) bool {
return c.IsWeekday()
}
// MiddleEastRule 中东工作日规则(周日至周四)
type MiddleEastRule struct{}
func (r *MiddleEastRule) IsWorkday(c carbon.Carbon) bool {
day := c.DayOfWeek()
// 周日(0)到周四(4)为工作日
return day >= 0 && day <= 4
}
// CustomHolidays 自定义节假日列表
type CustomHolidays struct {
holidays []string // YYYY-MM-DD格式
}
// NewCustomHolidays 创建自定义节假日
func NewCustomHolidays(holidays []string) *CustomHolidays {
return &CustomHolidays{holidays: holidays}
}
// IsHoliday 判断是否为节假日
func (h *CustomHolidays) IsHoliday(c carbon.Carbon) bool {
dateStr := c.ToDateString()
for _, holiday := range h.holidays {
if holiday == dateStr {
return true
}
}
return false
}
// IsWorkday 判断是否为工作日(排除节假日)
func (h *CustomHolidays) IsWorkday(c carbon.Carbon) bool {
if h.IsHoliday(c) {
return false
}
return c.IsWeekday()
}
func main() {
date := carbon.Parse("2024-01-15")
// 标准工作日规则
fmt.Println("=== 标准工作日规则 ===")
standardRule := &StandardRule{}
fmt.Printf("%s %s: %v\n",
date.ToDateString(),
date.ToWeekString(),
standardRule.IsWorkday(date))
// 中东工作日规则
fmt.Println("\n=== 中东工作日规则 ===")
middleEastRule := &MiddleEastRule{}
weekStart := date.StartOfWeek()
for i := 0; i < 7; i++ {
day := weekStart.AddDays(i)
isWorkday := middleEastRule.IsWorkday(day)
dayType := "休息日"
if isWorkday {
dayType = "工作日"
}
fmt.Printf("%s: %s\n", day.ToWeekString(), dayType)
}
// 自定义节假日规则
fmt.Println("\n=== 自定义节假日规则 ===")
holidays := []string{
"2024-01-01", // 元旦
"2024-02-10", // 春节
"2024-05-01", // 劳动节
"2024-10-01", // 国庆节
}
customRule := NewCustomHolidays(holidays)
testDates := []string{
"2024-01-01", // 元旦(节假日)
"2024-01-15", // 普通工作日
"2024-02-10", // 春节(节假日)
"2024-01-20", // 周末
}
for _, dateStr := range testDates {
d := carbon.Parse(dateStr)
isWorkday := customRule.IsWorkday(d)
dayType := "非工作日"
if isWorkday {
dayType = "工作日"
}
fmt.Printf("%s %s: %s\n",
d.ToDateString(),
d.ToWeekString(),
dayType)
}
// 实用场景:考虑节假日的工期计算
fmt.Println("\n=== 考虑节假日的工期 ===")
projectStart := carbon.Parse("2024-01-01")
workDaysNeeded := 10
current := projectStart
workDaysCount := 0
for workDaysCount < workDaysNeeded {
if customRule.IsWorkday(current) {
workDaysCount++
}
current = current.AddDay()
}
fmt.Println("项目开始:", projectStart.ToDateString())
fmt.Println("需要工作日:", workDaysNeeded)
fmt.Println("预计完成:", current.ToDateString())
}
---
6 实战应用
6.1 安装使用
01.安装Carbon库
a.说明
Carbon是一个纯Go语言库,使用Go Modules进行包管理。
通过go get命令可以快速安装Carbon到项目中。
建议使用最新稳定版本,获得最好的功能和性能。
Carbon的v2版本是当前主流版本,API更加完善。
安装后即可在项目中导入使用,无需额外配置。
b.代码示例
---
# 安装Carbon v2版本
go get -u github.com/golang-module/carbon/v2
# 安装指定版本
go get github.com/golang-module/carbon/[email protected]
# 查看已安装版本
go list -m github.com/golang-module/carbon/v2
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 验证Carbon安装
fmt.Println("=== Carbon安装验证 ===")
// 获取当前时间
now := carbon.Now()
if now.Error != nil {
fmt.Println("Carbon未正确安装")
return
}
fmt.Println("Carbon版本: v2.x")
fmt.Println("当前时间:", now.ToDateTimeString())
fmt.Println("时区:", now.Timezone())
// 测试基本功能
fmt.Println("\n=== 功能测试 ===")
fmt.Println("格式化:", now.Format("Y-m-d H:i:s"))
fmt.Println("明天:", now.AddDay().ToDateString())
fmt.Println("星期:", now.ToWeekString())
// 测试多语言
zhCN := now.SetLocale("zh-CN")
fmt.Println("中文星期:", zhCN.ToWeekString())
// 测试时区转换
utc := now.SetTimezone("UTC")
fmt.Println("UTC时间:", utc.ToDateTimeString())
fmt.Println("\nCarbon安装成功!")
}
---
02.项目集成
a.说明
将Carbon集成到现有项目中需要规范化的导入和使用方式。
建议在项目中统一时间处理逻辑,避免混用time包和Carbon。
可以封装常用的时间操作为工具函数,提高代码复用性。
在数据库模型中可以使用Carbon类型替代time.Time。
合理使用Carbon可以大幅简化时间处理代码。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// TimeHelper 时间辅助工具
type TimeHelper struct {
DefaultTimezone string
DefaultLocale string
}
// NewTimeHelper 创建时间辅助工具
func NewTimeHelper() *TimeHelper {
return &TimeHelper{
DefaultTimezone: "Asia/Shanghai",
DefaultLocale: "zh-CN",
}
}
// Now 获取当前时间(使用默认配置)
func (h *TimeHelper) Now() carbon.Carbon {
return carbon.Now().
SetTimezone(h.DefaultTimezone).
SetLocale(h.DefaultLocale)
}
// Parse 解析时间字符串(使用默认配置)
func (h *TimeHelper) Parse(value string) carbon.Carbon {
return carbon.Parse(value).
SetTimezone(h.DefaultTimezone).
SetLocale(h.DefaultLocale)
}
// FormatDateTime 标准日期时间格式
func (h *TimeHelper) FormatDateTime(c carbon.Carbon) string {
return c.Format("Y-m-d H:i:s")
}
// FormatDate 标准日期格式
func (h *TimeHelper) FormatDate(c carbon.Carbon) string {
return c.ToDateString()
}
func main() {
// 初始化时间辅助工具
helper := NewTimeHelper()
// 使用辅助工具
fmt.Println("=== 项目集成示例 ===")
now := helper.Now()
fmt.Println("当前时间:", helper.FormatDateTime(now))
fmt.Println("当前日期:", helper.FormatDate(now))
// 解析时间
parsed := helper.Parse("2024-01-15 10:30:00")
fmt.Println("解析时间:", helper.FormatDateTime(parsed))
// 时间计算
tomorrow := now.AddDay()
fmt.Println("明天:", helper.FormatDate(tomorrow))
// 数据库场景示例
fmt.Println("\n=== 数据库场景 ===")
// 模拟数据库记录
type User struct {
ID int
Name string
CreatedAt carbon.Carbon
UpdatedAt carbon.Carbon
}
user := User{
ID: 1,
Name: "张三",
CreatedAt: helper.Now(),
UpdatedAt: helper.Now(),
}
fmt.Printf("用户ID: %d\n", user.ID)
fmt.Printf("用户名: %s\n", user.Name)
fmt.Printf("创建时间: %s\n", helper.FormatDateTime(user.CreatedAt))
fmt.Printf("更新时间: %s\n", helper.FormatDateTime(user.UpdatedAt))
}
---
03.最佳实践建议
a.说明
使用Carbon时应遵循一些最佳实践,确保代码质量和性能。
始终检查Carbon对象的Error字段,避免使用无效对象。
在数据库中统一使用UTC时区存储,显示时再转换为用户时区。
避免在循环中频繁创建Carbon对象,可以考虑复用。
使用预定义的格式化模板,提高代码可维护性。
合理使用链式调用,但不要过度嵌套影响可读性。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// 格式化模板常量
const (
DateTimeFormat = "Y-m-d H:i:s"
DateFormat = "Y-m-d"
TimeFormat = "H:i:s"
)
// SafeParse 安全的时间解析
func SafeParse(value string) (carbon.Carbon, error) {
c := carbon.Parse(value)
if c.Error != nil {
return carbon.Carbon{}, c.Error
}
return c, nil
}
// ToUTC 转换为UTC时区(存储用)
func ToUTC(c carbon.Carbon) carbon.Carbon {
return c.SetTimezone("UTC")
}
// ToUserTimezone 转换为用户时区(显示用)
func ToUserTimezone(c carbon.Carbon, timezone string) carbon.Carbon {
return c.SetTimezone(timezone)
}
func main() {
fmt.Println("=== 最佳实践示例 ===\n")
// 1. 错误处理
fmt.Println("1. 错误处理:")
dateStr := "2024-01-15 10:30:00"
parsed, err := SafeParse(dateStr)
if err != nil {
fmt.Println("解析失败:", err)
return
}
fmt.Println("解析成功:", parsed.Format(DateTimeFormat))
// 2. UTC存储
fmt.Println("\n2. UTC存储:")
localTime := carbon.Now()
utcTime := ToUTC(localTime)
fmt.Println("本地时间:", localTime.Format(DateTimeFormat))
fmt.Println("UTC时间:", utcTime.Format(DateTimeFormat))
fmt.Println("存储到数据库:", utcTime.ToIso8601String())
// 3. 用户时区显示
fmt.Println("\n3. 用户时区显示:")
// 从数据库读取的UTC时间
dbTime := ToUserTimezone(utcTime, "Asia/Tokyo")
fmt.Println("东京用户看到:", dbTime.Format(DateTimeFormat))
// 4. 对象复用
fmt.Println("\n4. 对象复用:")
base := carbon.Now()
// 避免重复创建
tomorrow := base.AddDay()
nextWeek := base.AddWeek()
nextMonth := base.AddMonth()
fmt.Println("明天:", tomorrow.Format(DateFormat))
fmt.Println("下周:", nextWeek.Format(DateFormat))
fmt.Println("下月:", nextMonth.Format(DateFormat))
// 5. 链式调用适度
fmt.Println("\n5. 链式调用:")
// 推荐:简洁的链式调用
result1 := carbon.Now().
AddDays(7).
StartOfDay().
Format(DateTimeFormat)
fmt.Println("推荐方式:", result1)
// 不推荐:过长的链式调用
// 应该拆分为多个步骤
temp := carbon.Now().AddDays(7)
result2 := temp.StartOfDay()
fmt.Println("拆分方式:", result2.Format(DateTimeFormat))
// 6. 使用格式化常量
fmt.Println("\n6. 格式化常量:")
now := carbon.Now()
fmt.Println("日期时间:", now.Format(DateTimeFormat))
fmt.Println("日期:", now.Format(DateFormat))
fmt.Println("时间:", now.Format(TimeFormat))
}
---
6.2 日期范围查询
01.数据库日期查询
a.说明
Carbon在数据库日期范围查询中非常实用。
可以快速构建各种时间维度的查询条件。
使用StartOf和EndOf方法获取准确的时间边界。
注意时区问题,确保查询条件与数据库时区一致。
合理的日期查询是数据分析和报表生成的基础。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// QueryBuilder 查询构建器
type QueryBuilder struct {
table string
conditions []string
}
// NewQueryBuilder 创建查询构建器
func NewQueryBuilder(table string) *QueryBuilder {
return &QueryBuilder{
table: table,
conditions: []string{},
}
}
// WhereDate 添加日期条件
func (qb *QueryBuilder) WhereDate(column, date string) *QueryBuilder {
qb.conditions = append(qb.conditions,
fmt.Sprintf("%s = '%s'", column, date))
return qb
}
// WhereDateRange 添加日期范围条件
func (qb *QueryBuilder) WhereDateRange(column string, start, end carbon.Carbon) *QueryBuilder {
qb.conditions = append(qb.conditions,
fmt.Sprintf("%s >= '%s' AND %s < '%s'",
column, start.ToDateTimeString(),
column, end.AddDay().ToDateTimeString()))
return qb
}
// Build 构建SQL
func (qb *QueryBuilder) Build() string {
sql := "SELECT * FROM " + qb.table
if len(qb.conditions) > 0 {
sql += " WHERE "
for i, cond := range qb.conditions {
if i > 0 {
sql += " AND "
}
sql += cond
}
}
return sql + ";"
}
func main() {
// 查询今天的数据
fmt.Println("=== 查询今天的数据 ===")
today := carbon.Now().StartOfDay()
tomorrow := today.AddDay()
sql1 := fmt.Sprintf(
"SELECT * FROM orders WHERE created_at >= '%s' AND created_at < '%s';",
today.ToDateTimeString(),
tomorrow.ToDateTimeString(),
)
fmt.Println(sql1)
// 查询本周的数据
fmt.Println("\n=== 查询本周的数据 ===")
weekStart := carbon.Now().StartOfWeek()
weekEnd := carbon.Now().EndOfWeek().AddDay()
sql2 := fmt.Sprintf(
"SELECT * FROM orders WHERE created_at >= '%s' AND created_at < '%s';",
weekStart.ToDateTimeString(),
weekEnd.ToDateTimeString(),
)
fmt.Println(sql2)
// 查询本月的数据
fmt.Println("\n=== 查询本月的数据 ===")
monthStart := carbon.Now().StartOfMonth()
monthEnd := carbon.Now().EndOfMonth().AddDay()
sql3 := fmt.Sprintf(
"SELECT * FROM orders WHERE created_at >= '%s' AND created_at < '%s';",
monthStart.ToDateTimeString(),
monthEnd.ToDateTimeString(),
)
fmt.Println(sql3)
// 使用查询构建器
fmt.Println("\n=== 使用查询构建器 ===")
// 查询上个月的数据
lastMonthStart := carbon.Now().SubMonth().StartOfMonth()
lastMonthEnd := carbon.Now().SubMonth().EndOfMonth()
builder := NewQueryBuilder("orders")
sql4 := builder.WhereDateRange("created_at", lastMonthStart, lastMonthEnd).Build()
fmt.Println(sql4)
// 查询指定日期范围
fmt.Println("\n=== 查询指定日期范围 ===")
start := carbon.Parse("2024-01-01")
end := carbon.Parse("2024-01-31")
sql5 := fmt.Sprintf(
"SELECT * FROM sales WHERE sale_date >= '%s' AND sale_date <= '%s';",
start.ToDateString(),
end.ToDateString(),
)
fmt.Println(sql5)
}
---
02.分页查询优化
a.说明
大数据量的日期范围查询需要考虑性能优化。
可以使用Carbon快速计算分页所需的时间片段。
按时间分片查询可以有效降低数据库压力。
结合索引和合理的时间粒度能显著提升查询效率。
在数据分析场景中,时间分片是常用的优化手段。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// TimeSlice 时间片段
type TimeSlice struct {
Start carbon.Carbon
End carbon.Carbon
}
// GenerateDailySlices 生成按天的时间片段
func GenerateDailySlices(start, end carbon.Carbon) []TimeSlice {
var slices []TimeSlice
current := start.StartOfDay()
endDay := end.EndOfDay()
for current.Lte(endDay) {
dayEnd := current.EndOfDay()
if dayEnd.Gt(endDay) {
dayEnd = endDay
}
slices = append(slices, TimeSlice{
Start: current,
End: dayEnd,
})
current = current.AddDay().StartOfDay()
}
return slices
}
// GenerateMonthlySlices 生成按月的时间片段
func GenerateMonthlySlices(start, end carbon.Carbon) []TimeSlice {
var slices []TimeSlice
current := start.StartOfMonth()
endMonth := end.EndOfMonth()
for current.Lte(endMonth) {
monthEnd := current.EndOfMonth()
if monthEnd.Gt(endMonth) {
monthEnd = endMonth
}
slices = append(slices, TimeSlice{
Start: current,
End: monthEnd,
})
current = current.AddMonth().StartOfMonth()
}
return slices
}
func main() {
// 按天分片查询
fmt.Println("=== 按天分片查询 ===")
start := carbon.Parse("2024-01-01")
end := carbon.Parse("2024-01-07")
dailySlices := GenerateDailySlices(start, end)
for i, slice := range dailySlices {
sql := fmt.Sprintf(
"SELECT * FROM logs WHERE created_at >= '%s' AND created_at <= '%s';",
slice.Start.ToDateTimeString(),
slice.End.ToDateTimeString(),
)
fmt.Printf("片段%d: %s\n", i+1, sql)
}
// 按月分片查询
fmt.Println("\n=== 按月分片查询 ===")
yearStart := carbon.Parse("2024-01-01")
yearEnd := carbon.Parse("2024-12-31")
monthlySlices := GenerateMonthlySlices(yearStart, yearEnd)
for i, slice := range monthlySlices {
fmt.Printf("月份%d: %s ~ %s\n",
i+1,
slice.Start.ToDateString(),
slice.End.ToDateString())
}
// 实用场景:大数据量分片导出
fmt.Println("\n=== 分片导出示例 ===")
exportStart := carbon.Parse("2023-01-01")
exportEnd := carbon.Parse("2023-12-31")
slices := GenerateMonthlySlices(exportStart, exportEnd)
for i, slice := range slices {
fileName := fmt.Sprintf(
"export_%s.csv",
slice.Start.Format("Y-m"),
)
fmt.Printf("导出文件%d: %s (%s ~ %s)\n",
i+1,
fileName,
slice.Start.ToDateString(),
slice.End.ToDateString())
}
}
---
03.动态查询条件
a.说明
实际业务中经常需要根据用户选择动态构建查询条件。
Carbon可以灵活处理各种时间维度的筛选需求。
支持今天、本周、本月、自定义范围等多种查询方式。
合理的接口设计能让时间查询更加灵活易用。
动态查询条件在后台管理系统中非常常见。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// DateRangeType 日期范围类型
type DateRangeType string
const (
Today DateRangeType = "today"
Yesterday DateRangeType = "yesterday"
ThisWeek DateRangeType = "this_week"
LastWeek DateRangeType = "last_week"
ThisMonth DateRangeType = "this_month"
LastMonth DateRangeType = "last_month"
ThisYear DateRangeType = "this_year"
CustomRange DateRangeType = "custom"
)
// DateFilter 日期筛选器
type DateFilter struct {
RangeType DateRangeType
StartDate string
EndDate string
}
// GetDateRange 获取日期范围
func (df *DateFilter) GetDateRange() (carbon.Carbon, carbon.Carbon) {
now := carbon.Now()
switch df.RangeType {
case Today:
return now.StartOfDay(), now.EndOfDay()
case Yesterday:
yesterday := now.SubDay()
return yesterday.StartOfDay(), yesterday.EndOfDay()
case ThisWeek:
return now.StartOfWeek(), now.EndOfWeek()
case LastWeek:
lastWeek := now.SubWeek()
return lastWeek.StartOfWeek(), lastWeek.EndOfWeek()
case ThisMonth:
return now.StartOfMonth(), now.EndOfMonth()
case LastMonth:
lastMonth := now.SubMonth()
return lastMonth.StartOfMonth(), lastMonth.EndOfMonth()
case ThisYear:
return now.StartOfYear(), now.EndOfYear()
case CustomRange:
start := carbon.Parse(df.StartDate)
end := carbon.Parse(df.EndDate)
return start.StartOfDay(), end.EndOfDay()
default:
return now.StartOfDay(), now.EndOfDay()
}
}
// BuildSQL 构建SQL查询
func (df *DateFilter) BuildSQL(table, column string) string {
start, end := df.GetDateRange()
return fmt.Sprintf(
"SELECT * FROM %s WHERE %s >= '%s' AND %s <= '%s';",
table,
column,
start.ToDateTimeString(),
column,
end.ToDateTimeString(),
)
}
func main() {
// 测试各种日期范围
fmt.Println("=== 动态日期查询 ===\n")
filters := []DateFilter{
{RangeType: Today},
{RangeType: Yesterday},
{RangeType: ThisWeek},
{RangeType: LastWeek},
{RangeType: ThisMonth},
{RangeType: LastMonth},
{RangeType: CustomRange, StartDate: "2024-01-01", EndDate: "2024-01-31"},
}
for _, filter := range filters {
start, end := filter.GetDateRange()
fmt.Printf("%s: %s ~ %s\n",
filter.RangeType,
start.ToDateString(),
end.ToDateString())
}
// 生成SQL示例
fmt.Println("\n=== SQL查询示例 ===\n")
filter := DateFilter{RangeType: ThisMonth}
sql := filter.BuildSQL("orders", "created_at")
fmt.Println(sql)
// 自定义范围
customFilter := DateFilter{
RangeType: CustomRange,
StartDate: "2024-01-01",
EndDate: "2024-01-31",
}
customSQL := customFilter.BuildSQL("sales", "sale_date")
fmt.Println("\n", customSQL)
}
---
6.3 倒计时功能
01.简单倒计时
a.说明
倒计时功能在活动营销、限时抢购等场景中广泛应用。
使用Carbon可以轻松计算剩余时间。
DiffInXxx()系列方法提供了各种时间单位的差值计算。
可以灵活组合显示天、时、分、秒等信息。
倒计时需要考虑时区和服务器时间同步问题。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// Countdown 倒计时结构
type Countdown struct {
TargetTime carbon.Carbon
}
// NewCountdown 创建倒计时
func NewCountdown(target string) *Countdown {
return &Countdown{
TargetTime: carbon.Parse(target),
}
}
// GetRemaining 获取剩余时间
func (cd *Countdown) GetRemaining() map[string]int64 {
now := carbon.Now()
if now.Gte(cd.TargetTime) {
return map[string]int64{
"days": 0,
"hours": 0,
"minutes": 0,
"seconds": 0,
}
}
totalSeconds := cd.TargetTime.DiffInSeconds(now)
days := totalSeconds / 86400
hours := (totalSeconds % 86400) / 3600
minutes := (totalSeconds % 3600) / 60
seconds := totalSeconds % 60
return map[string]int64{
"days": days,
"hours": hours,
"minutes": minutes,
"seconds": seconds,
}
}
// Format 格式化输出
func (cd *Countdown) Format() string {
remaining := cd.GetRemaining()
if remaining["days"] == 0 &&
remaining["hours"] == 0 &&
remaining["minutes"] == 0 &&
remaining["seconds"] == 0 {
return "活动已结束"
}
return fmt.Sprintf("%d天 %02d:%02d:%02d",
remaining["days"],
remaining["hours"],
remaining["minutes"],
remaining["seconds"])
}
// IsActive 是否进行中
func (cd *Countdown) IsActive() bool {
now := carbon.Now()
return now.Lt(cd.TargetTime)
}
func main() {
// 创建倒计时
fmt.Println("=== 活动倒计时 ===")
// 双十一倒计时
doubleEleven := NewCountdown("2024-11-11 00:00:00")
fmt.Println("目标时间:", doubleEleven.TargetTime.ToDateTimeString())
fmt.Println("剩余时间:", doubleEleven.Format())
fmt.Println("是否进行中:", doubleEleven.IsActive())
// 获取详细信息
remaining := doubleEleven.GetRemaining()
fmt.Printf("\n详细信息:\n")
fmt.Printf(" 天数: %d\n", remaining["days"])
fmt.Printf(" 小时: %d\n", remaining["hours"])
fmt.Printf(" 分钟: %d\n", remaining["minutes"])
fmt.Printf(" 秒数: %d\n", remaining["seconds"])
// 多个倒计时示例
fmt.Println("\n=== 多个活动倒计时 ===")
activities := map[string]*Countdown{
"春节": NewCountdown("2024-02-10 00:00:00"),
"618": NewCountdown("2024-06-18 00:00:00"),
"双十一": NewCountdown("2024-11-11 00:00:00"),
"双十二": NewCountdown("2024-12-12 00:00:00"),
}
for name, countdown := range activities {
fmt.Printf("%s: %s\n", name, countdown.Format())
}
// 近期倒计时
fmt.Println("\n=== 近期倒计时 ===")
soon := NewCountdown("2024-01-20 10:00:00")
fmt.Println("倒计时:", soon.Format())
}
---
02.阶段性倒计时
a.说明
有些活动有多个阶段,需要分阶段的倒计时提示。
可以根据当前时间判断处于哪个阶段。
不同阶段显示不同的倒计时信息和提示。
阶段性倒计时常用于预热、正式、收尾等不同时期。
合理的阶段划分能提升用户体验。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// ActivityStage 活动阶段
type ActivityStage string
const (
StageNotStarted ActivityStage = "未开始"
StagePreHeat ActivityStage = "预热中"
StageActive ActivityStage = "进行中"
StageEnding ActivityStage = "即将结束"
StageEnded ActivityStage = "已结束"
)
// StagedActivity 阶段性活动
type StagedActivity struct {
Name string
PreHeatStart carbon.Carbon
ActivityStart carbon.Carbon
ActivityEnd carbon.Carbon
}
// GetStage 获取当前阶段
func (sa *StagedActivity) GetStage() ActivityStage {
now := carbon.Now()
if now.Lt(sa.PreHeatStart) {
return StageNotStarted
} else if now.Between(sa.PreHeatStart, sa.ActivityStart) {
return StagePreHeat
} else if now.Between(sa.ActivityStart, sa.ActivityEnd.SubHours(24)) {
return StageActive
} else if now.Between(sa.ActivityEnd.SubHours(24), sa.ActivityEnd) {
return StageEnding
} else {
return StageEnded
}
}
// GetCountdown 获取倒计时信息
func (sa *StagedActivity) GetCountdown() string {
now := carbon.Now()
stage := sa.GetStage()
switch stage {
case StageNotStarted:
days := sa.PreHeatStart.DiffInDays(now)
return fmt.Sprintf("距预热还有%d天", days)
case StagePreHeat:
hours := sa.ActivityStart.DiffInHours(now)
return fmt.Sprintf("距开始还有%d小时", hours)
case StageActive:
days := sa.ActivityEnd.DiffInDays(now)
return fmt.Sprintf("活动进行中,还有%d天", days)
case StageEnding:
hours := sa.ActivityEnd.DiffInHours(now)
return fmt.Sprintf("即将结束,还有%d小时", hours)
case StageEnded:
return "活动已结束"
default:
return "未知状态"
}
}
func main() {
// 创建阶段性活动
fmt.Println("=== 阶段性活动倒计时 ===\n")
activity := &StagedActivity{
Name: "双十一大促",
PreHeatStart: carbon.Parse("2024-11-01 00:00:00"),
ActivityStart: carbon.Parse("2024-11-11 00:00:00"),
ActivityEnd: carbon.Parse("2024-11-11 23:59:59"),
}
fmt.Println("活动名称:", activity.Name)
fmt.Println("当前阶段:", activity.GetStage())
fmt.Println("倒计时:", activity.GetCountdown())
// 模拟不同时间点的状态
fmt.Println("\n=== 各阶段状态模拟 ===\n")
testTimes := []struct {
desc string
time string
}{
{"预热前", "2024-10-30 12:00:00"},
{"预热中", "2024-11-05 12:00:00"},
{"活动中", "2024-11-11 10:00:00"},
{"即将结束", "2024-11-11 22:00:00"},
{"已结束", "2024-11-12 00:00:00"},
}
for _, test := range testTimes {
// 临时修改系统时间(示例)
fmt.Printf("%s (%s):\n", test.desc, test.time)
testTime := carbon.Parse(test.time)
var stage ActivityStage
if testTime.Lt(activity.PreHeatStart) {
stage = StageNotStarted
days := activity.PreHeatStart.DiffInDays(testTime)
fmt.Printf(" 距预热还有%d天\n\n", days)
} else if testTime.Between(activity.PreHeatStart, activity.ActivityStart) {
stage = StagePreHeat
hours := activity.ActivityStart.DiffInHours(testTime)
fmt.Printf(" 预热中,距开始还有%d小时\n\n", hours)
} else {
fmt.Println(" 活动进行中或已结束\n")
}
}
}
---
03.实时倒计时
a.说明
前端页面需要实时更新的倒计时效果。
后端可以提供倒计时API,返回剩余秒数。
前端根据秒数实时递减显示。
需要考虑服务器时间与客户端时间的差异。
合理的时间同步策略能提供准确的倒计时体验。
b.代码示例
---
package main
import (
"encoding/json"
"fmt"
"github.com/golang-module/carbon/v2"
)
// CountdownResponse 倒计时API响应
type CountdownResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data CountdownData `json:"data"`
ServerTime int64 `json:"server_time"`
}
// CountdownData 倒计时数据
type CountdownData struct {
ActivityName string `json:"activity_name"`
TargetTime string `json:"target_time"`
Remaining int64 `json:"remaining"` // 剩余秒数
Days int64 `json:"days"`
Hours int64 `json:"hours"`
Minutes int64 `json:"minutes"`
Seconds int64 `json:"seconds"`
IsActive bool `json:"is_active"`
}
// GetCountdownAPI 倒计时API
func GetCountdownAPI(targetTime string) CountdownResponse {
target := carbon.Parse(targetTime)
now := carbon.Now()
remaining := int64(0)
isActive := true
if now.Lt(target) {
remaining = target.DiffInSeconds(now)
} else {
isActive = false
}
days := remaining / 86400
hours := (remaining % 86400) / 3600
minutes := (remaining % 3600) / 60
seconds := remaining % 60
return CountdownResponse{
Code: 200,
Message: "success",
ServerTime: now.Timestamp(),
Data: CountdownData{
ActivityName: "双十一大促",
TargetTime: target.ToIso8601String(),
Remaining: remaining,
Days: days,
Hours: hours,
Minutes: minutes,
Seconds: seconds,
IsActive: isActive,
},
}
}
func main() {
// 模拟API调用
fmt.Println("=== 倒计时API响应 ===\n")
response := GetCountdownAPI("2024-11-11 00:00:00")
// JSON序列化
jsonData, _ := json.MarshalIndent(response, "", " ")
fmt.Println(string(jsonData))
// 前端使用示例(JavaScript伪代码)
fmt.Println("\n=== 前端使用示例 ===\n")
fmt.Println(`
// 获取倒计时数据
fetch('/api/countdown')
.then(res => res.json())
.then(data => {
let remaining = data.data.remaining;
const serverTime = data.server_time;
const clientTime = Math.floor(Date.now() / 1000);
const timeDiff = clientTime - serverTime;
// 每秒更新
setInterval(() => {
remaining--;
if (remaining <= 0) {
// 倒计时结束
return;
}
const days = Math.floor(remaining / 86400);
const hours = Math.floor((remaining % 86400) / 3600);
const minutes = Math.floor((remaining % 3600) / 60);
const seconds = remaining % 60;
// 更新显示
document.getElementById('countdown').innerHTML =
days + '天 ' +
hours.toString().padStart(2, '0') + ':' +
minutes.toString().padStart(2, '0') + ':' +
seconds.toString().padStart(2, '0');
}, 1000);
});
`)
}
---
6.4 时间统计
01.访问量统计
a.说明
时间维度的数据统计是数据分析的基础。
Carbon可以快速生成各种时间维度的统计报表。
支持按小时、天、周、月、季度、年等维度聚合。
合理的时间分组能直观展示数据趋势。
统计功能在后台管理系统中应用广泛。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// VisitRecord 访问记录
type VisitRecord struct {
ID int
URL string
CreatedAt carbon.Carbon
}
// HourlyStats 按小时统计
func HourlyStats(records []VisitRecord, date carbon.Carbon) map[int]int {
stats := make(map[int]int)
dayStart := date.StartOfDay()
dayEnd := date.EndOfDay()
for _, record := range records {
if record.CreatedAt.Between(dayStart, dayEnd) {
hour := record.CreatedAt.Hour()
stats[hour]++
}
}
return stats
}
// DailyStats 按天统计
func DailyStats(records []VisitRecord, start, end carbon.Carbon) map[string]int {
stats := make(map[string]int)
for _, record := range records {
if record.CreatedAt.Between(start, end) {
date := record.CreatedAt.ToDateString()
stats[date]++
}
}
return stats
}
// MonthlyStats 按月统计
func MonthlyStats(records []VisitRecord, year int) map[string]int {
stats := make(map[string]int)
for _, record := range records {
if record.CreatedAt.Year() == year {
month := record.CreatedAt.Format("Y-m")
stats[month]++
}
}
return stats
}
func main() {
// 模拟访问记录
records := []VisitRecord{
{1, "/home", carbon.Parse("2024-01-15 08:30:00")},
{2, "/about", carbon.Parse("2024-01-15 09:15:00")},
{3, "/product", carbon.Parse("2024-01-15 10:20:00")},
{4, "/home", carbon.Parse("2024-01-15 14:45:00")},
{5, "/contact", carbon.Parse("2024-01-16 09:30:00")},
{6, "/home", carbon.Parse("2024-01-16 11:00:00")},
}
// 按小时统计
fmt.Println("=== 今日访问量(按小时) ===")
today := carbon.Parse("2024-01-15")
hourlyStats := HourlyStats(records, today)
for hour := 0; hour < 24; hour++ {
count := hourlyStats[hour]
if count > 0 {
fmt.Printf("%02d:00 - %d次\n", hour, count)
}
}
// 按天统计
fmt.Println("\n=== 本周访问量(按天) ===")
weekStart := carbon.Parse("2024-01-15").StartOfWeek()
weekEnd := carbon.Parse("2024-01-15").EndOfWeek()
dailyStats := DailyStats(records, weekStart, weekEnd)
current := weekStart
for current.Lte(weekEnd) {
date := current.ToDateString()
count := dailyStats[date]
fmt.Printf("%s %s: %d次\n",
date,
current.ToWeekString(),
count)
current = current.AddDay()
}
// 按月统计
fmt.Println("\n=== 2024年访问量(按月) ===")
monthlyStats := MonthlyStats(records, 2024)
for month := 1; month <= 12; month++ {
monthKey := fmt.Sprintf("2024-%02d", month)
count := monthlyStats[monthKey]
if count > 0 {
fmt.Printf("%s: %d次\n", monthKey, count)
}
}
}
---
02.业绩统计报表
a.说明
业绩统计需要按时间维度聚合销售数据。
Carbon可以快速生成各种周期的业绩报表。
支持环比、同比等多维度对比分析。
合理的统计粒度有助于发现业务趋势。
报表功能是商业智能系统的核心。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// SalesRecord 销售记录
type SalesRecord struct {
ID int
Amount float64
CreatedAt carbon.Carbon
}
// PeriodStats 周期统计
type PeriodStats struct {
Period string
Count int
Amount float64
}
// DailySalesStats 每日销售统计
func DailySalesStats(records []SalesRecord, start, end carbon.Carbon) []PeriodStats {
statsMap := make(map[string]*PeriodStats)
current := start.StartOfDay()
for current.Lte(end) {
date := current.ToDateString()
statsMap[date] = &PeriodStats{
Period: date,
Count: 0,
Amount: 0,
}
current = current.AddDay()
}
for _, record := range records {
if record.CreatedAt.Between(start, end) {
date := record.CreatedAt.ToDateString()
if stats, ok := statsMap[date]; ok {
stats.Count++
stats.Amount += record.Amount
}
}
}
var result []PeriodStats
current = start.StartOfDay()
for current.Lte(end) {
date := current.ToDateString()
result = append(result, *statsMap[date])
current = current.AddDay()
}
return result
}
// MonthlySalesStats 每月销售统计
func MonthlySalesStats(records []SalesRecord, year int) []PeriodStats {
statsMap := make(map[string]*PeriodStats)
for month := 1; month <= 12; month++ {
period := fmt.Sprintf("%d-%02d", year, month)
statsMap[period] = &PeriodStats{
Period: period,
Count: 0,
Amount: 0,
}
}
for _, record := range records {
if record.CreatedAt.Year() == year {
period := record.CreatedAt.Format("Y-m")
if stats, ok := statsMap[period]; ok {
stats.Count++
stats.Amount += record.Amount
}
}
}
var result []PeriodStats
for month := 1; month <= 12; month++ {
period := fmt.Sprintf("%d-%02d", year, month)
result = append(result, *statsMap[period])
}
return result
}
func main() {
// 模拟销售数据
records := []SalesRecord{
{1, 100.50, carbon.Parse("2024-01-01 10:00:00")},
{2, 200.00, carbon.Parse("2024-01-01 14:00:00")},
{3, 150.75, carbon.Parse("2024-01-02 09:00:00")},
{4, 300.00, carbon.Parse("2024-01-02 15:00:00")},
{5, 250.50, carbon.Parse("2024-01-03 11:00:00")},
}
// 每日销售统计
fmt.Println("=== 本周销售统计 ===")
weekStart := carbon.Parse("2024-01-01")
weekEnd := carbon.Parse("2024-01-07")
dailyStats := DailySalesStats(records, weekStart, weekEnd)
totalAmount := 0.0
totalCount := 0
for _, stats := range dailyStats {
fmt.Printf("%s: %d笔订单, ¥%.2f\n",
stats.Period,
stats.Count,
stats.Amount)
totalAmount += stats.Amount
totalCount += stats.Count
}
fmt.Printf("\n合计: %d笔订单, ¥%.2f\n", totalCount, totalAmount)
if totalCount > 0 {
fmt.Printf("平均: ¥%.2f/笔\n", totalAmount/float64(totalCount))
}
// 每月销售统计
fmt.Println("\n=== 2024年月度销售 ===")
monthlyStats := MonthlySalesStats(records, 2024)
yearTotal := 0.0
yearCount := 0
for _, stats := range monthlyStats {
if stats.Count > 0 {
fmt.Printf("%s: %d笔订单, ¥%.2f\n",
stats.Period,
stats.Count,
stats.Amount)
yearTotal += stats.Amount
yearCount += stats.Count
}
}
fmt.Printf("\n年度合计: %d笔订单, ¥%.2f\n", yearCount, yearTotal)
}
---
03.用户行为分析
a.说明
用户行为分析需要按时间维度统计用户活跃度。
可以分析用户的登录时间、活跃时段等特征。
时间分布图能直观展示用户行为规律。
合理的时间粒度选择影响分析效果。
用户行为分析是产品优化的重要依据。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// UserActivity 用户活动记录
type UserActivity struct {
UserID int
Action string
CreatedAt carbon.Carbon
}
// ActiveHours 活跃时段分析
func ActiveHours(activities []UserActivity) map[int]int {
hourStats := make(map[int]int)
for _, activity := range activities {
hour := activity.CreatedAt.Hour()
hourStats[hour]++
}
return hourStats
}
// ActiveDays 活跃日期分析
func ActiveDays(activities []UserActivity, userID int) []string {
dateSet := make(map[string]bool)
for _, activity := range activities {
if activity.UserID == userID {
date := activity.CreatedAt.ToDateString()
dateSet[date] = true
}
}
var dates []string
for date := range dateSet {
dates = append(dates, date)
}
return dates
}
// WeekdayDistribution 工作日分布
func WeekdayDistribution(activities []UserActivity) map[string]int {
weekdayStats := make(map[string]int)
for _, activity := range activities {
weekday := activity.CreatedAt.ToWeekString()
weekdayStats[weekday]++
}
return weekdayStats
}
func main() {
// 模拟用户活动数据
activities := []UserActivity{
{1, "login", carbon.Parse("2024-01-15 08:30:00")},
{1, "view", carbon.Parse("2024-01-15 09:00:00")},
{2, "login", carbon.Parse("2024-01-15 10:00:00")},
{1, "click", carbon.Parse("2024-01-15 14:00:00")},
{2, "view", carbon.Parse("2024-01-15 15:00:00")},
{1, "login", carbon.Parse("2024-01-16 09:00:00")},
}
// 活跃时段分析
fmt.Println("=== 用户活跃时段分析 ===")
hourStats := ActiveHours(activities)
for hour := 0; hour < 24; hour++ {
count := hourStats[hour]
if count > 0 {
bar := ""
for i := 0; i < count; i++ {
bar += "█"
}
fmt.Printf("%02d:00 %s %d\n", hour, bar, count)
}
}
// 用户活跃天数
fmt.Println("\n=== 用户活跃天数 ===")
activeDays := ActiveDays(activities, 1)
fmt.Printf("用户1活跃天数: %d天\n", len(activeDays))
for _, date := range activeDays {
fmt.Printf(" - %s\n", date)
}
// 星期分布
fmt.Println("\n=== 星期活跃分布 ===")
weekdayStats := WeekdayDistribution(activities)
weekdays := []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
for _, weekday := range weekdays {
count := weekdayStats[weekday]
fmt.Printf("%s: %d次\n", weekday, count)
}
}
---
6.5 定时任务
01.定时任务调度
a.说明
Carbon可以帮助计算和判断定时任务的执行时间。
配合cron表达式可以实现灵活的任务调度。
可以判断任务是否应该在当前时间执行。
支持各种周期性任务的时间计算。
合理的任务调度能提高系统效率。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// Task 定时任务
type Task struct {
Name string
Schedule string // 简化的调度规则
LastRun carbon.Carbon
NextRun carbon.Carbon
}
// ShouldRun 判断是否应该执行
func (t *Task) ShouldRun() bool {
now := carbon.Now()
return now.Gte(t.NextRun)
}
// CalculateNextRun 计算下次执行时间
func (t *Task) CalculateNextRun() {
now := carbon.Now()
switch t.Schedule {
case "daily":
// 每天执行
t.NextRun = now.AddDay().StartOfDay()
case "hourly":
// 每小时执行
t.NextRun = now.AddHour().StartOfHour()
case "weekly":
// 每周执行
t.NextRun = now.AddWeek().StartOfWeek()
case "monthly":
// 每月执行
t.NextRun = now.AddMonth().StartOfMonth()
default:
t.NextRun = now.AddDay()
}
}
// Run 执行任务
func (t *Task) Run() {
fmt.Printf("[%s] 执行任务: %s\n",
carbon.Now().ToDateTimeString(),
t.Name)
t.LastRun = carbon.Now()
t.CalculateNextRun()
}
func main() {
// 创建定时任务
fmt.Println("=== 定时任务调度 ===\n")
tasks := []*Task{
{
Name: "每日数据备份",
Schedule: "daily",
LastRun: carbon.Yesterday(),
},
{
Name: "每小时日志清理",
Schedule: "hourly",
LastRun: carbon.Now().SubHour(),
},
{
Name: "每周报表生成",
Schedule: "weekly",
LastRun: carbon.Now().SubWeek(),
},
}
// 初始化下次执行时间
for _, task := range tasks {
task.CalculateNextRun()
}
// 显示任务列表
fmt.Println("任务列表:")
for i, task := range tasks {
fmt.Printf("%d. %s\n", i+1, task.Name)
fmt.Printf(" 调度: %s\n", task.Schedule)
fmt.Printf(" 上次执行: %s\n", task.LastRun.ToDateTimeString())
fmt.Printf(" 下次执行: %s\n\n", task.NextRun.ToDateTimeString())
}
// 检查应该执行的任务
fmt.Println("=== 检查待执行任务 ===")
for _, task := range tasks {
if task.ShouldRun() {
task.Run()
} else {
remaining := task.NextRun.DiffInSeconds(carbon.Now())
fmt.Printf("[%s] %s 还需等待 %d秒\n",
carbon.Now().ToDateTimeString(),
task.Name,
remaining)
}
}
}
---
02.特定时间执行
a.说明
有些任务需要在特定的时间点执行。
可以使用Carbon判断是否到达指定时间。
支持每天特定时刻、每周特定日期等场景。
精确的时间判断确保任务按时执行。
这类任务在报表生成、数据同步等场景中常见。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
// ScheduledTask 预定任务
type ScheduledTask struct {
Name string
ExecuteTime string // HH:MM格式
Weekday int // 0=周日, 1=周一, ..., 6=周六, -1=每天
}
// ShouldExecute 判断是否应该执行
func (st *ScheduledTask) ShouldExecute() bool {
now := carbon.Now()
// 检查星期
if st.Weekday != -1 && now.DayOfWeek() != st.Weekday {
return false
}
// 检查时间
currentTime := now.Format("H:i")
return currentTime == st.ExecuteTime
}
// GetNextExecutionTime 获取下次执行时间
func (st *ScheduledTask) GetNextExecutionTime() carbon.Carbon {
now := carbon.Now()
targetTime := carbon.Parse(now.ToDateString() + " " + st.ExecuteTime + ":00")
// 如果今天的时间已过,找下一个执行日
if now.Gte(targetTime) {
targetTime = targetTime.AddDay()
}
// 如果指定了星期几
if st.Weekday != -1 {
for targetTime.DayOfWeek() != st.Weekday {
targetTime = targetTime.AddDay()
}
}
return targetTime
}
func main() {
// 创建预定任务
fmt.Println("=== 预定任务列表 ===\n")
tasks := []*ScheduledTask{
{
Name: "每日早报",
ExecuteTime: "09:00",
Weekday: -1, // 每天
},
{
Name: "周一晨会",
ExecuteTime: "10:00",
Weekday: 1, // 周一
},
{
Name: "周五总结",
ExecuteTime: "17:00",
Weekday: 5, // 周五
},
{
Name: "午间提醒",
ExecuteTime: "12:00",
Weekday: -1, // 每天
},
}
// 显示任务信息
for i, task := range tasks {
weekdayStr := "每天"
if task.Weekday != -1 {
weekdays := []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
weekdayStr = weekdays[task.Weekday]
}
nextRun := task.GetNextExecutionTime()
fmt.Printf("%d. %s\n", i+1, task.Name)
fmt.Printf(" 时间: %s %s\n", weekdayStr, task.ExecuteTime)
fmt.Printf(" 下次执行: %s\n\n", nextRun.ToDateTimeString())
}
// 检查当前应执行的任务
fmt.Println("=== 当前应执行的任务 ===")
now := carbon.Now()
fmt.Printf("当前时间: %s %s %s\n\n",
now.ToDateString(),
now.ToWeekString(),
now.Format("H:i"))
hasTask := false
for _, task := range tasks {
if task.ShouldExecute() {
fmt.Printf("✓ %s 应该执行\n", task.Name)
hasTask = true
}
}
if !hasTask {
fmt.Println("当前没有需要执行的任务")
}
}
---
03.延迟任务处理
a.说明
延迟任务需要在指定时间后执行。
Carbon可以计算任务的到期时间。
支持基于时间戳或相对时间的延迟。
延迟任务常用于消息推送、订单超时等场景。
准确的延迟计算确保任务按时处理。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
// DelayedTask 延迟任务
type DelayedTask struct {
ID int
Name string
CreatedAt carbon.Carbon
DelayDuration time.Duration
ExecuteAt carbon.Carbon
Status string
}
// NewDelayedTask 创建延迟任务
func NewDelayedTask(id int, name string, delay time.Duration) *DelayedTask {
now := carbon.Now()
return &DelayedTask{
ID: id,
Name: name,
CreatedAt: now,
DelayDuration: delay,
ExecuteAt: now.AddDuration(delay),
Status: "pending",
}
}
// ShouldExecute 判断是否到期
func (dt *DelayedTask) ShouldExecute() bool {
return carbon.Now().Gte(dt.ExecuteAt)
}
// Execute 执行任务
func (dt *DelayedTask) Execute() {
if dt.ShouldExecute() {
fmt.Printf("[%s] 执行延迟任务: %s\n",
carbon.Now().ToDateTimeString(),
dt.Name)
dt.Status = "completed"
} else {
fmt.Printf("[%s] 任务 %s 未到执行时间\n",
carbon.Now().ToDateTimeString(),
dt.Name)
}
}
// GetRemainingTime 获取剩余时间
func (dt *DelayedTask) GetRemainingTime() string {
now := carbon.Now()
if now.Gte(dt.ExecuteAt) {
return "已到期"
}
remaining := dt.ExecuteAt.DiffInSeconds(now)
hours := remaining / 3600
minutes := (remaining % 3600) / 60
seconds := remaining % 60
return fmt.Sprintf("%d小时%d分%d秒", hours, minutes, seconds)
}
func main() {
// 创建延迟任务
fmt.Println("=== 延迟任务队列 ===\n")
tasks := []*DelayedTask{
NewDelayedTask(1, "发送欢迎邮件", 5*time.Minute),
NewDelayedTask(2, "订单超时取消", 30*time.Minute),
NewDelayedTask(3, "优惠券过期提醒", 2*time.Hour),
NewDelayedTask(4, "会员到期通知", 24*time.Hour),
}
// 显示任务列表
fmt.Println("任务列表:")
for _, task := range tasks {
fmt.Printf("任务%d: %s\n", task.ID, task.Name)
fmt.Printf(" 创建时间: %s\n", task.CreatedAt.ToDateTimeString())
fmt.Printf(" 执行时间: %s\n", task.ExecuteAt.ToDateTimeString())
fmt.Printf(" 剩余时间: %s\n", task.GetRemainingTime())
fmt.Printf(" 状态: %s\n\n", task.Status)
}
// 检查到期任务
fmt.Println("=== 检查到期任务 ===")
for _, task := range tasks {
if task.ShouldExecute() {
task.Execute()
}
}
// 实用场景:订单超时处理
fmt.Println("\n=== 订单超时处理示例 ===")
type Order struct {
ID int
CreatedAt carbon.Carbon
Timeout time.Duration
}
order := Order{
ID: 12345,
CreatedAt: carbon.Now().SubMinutes(35),
Timeout: 30 * time.Minute,
}
expireTime := order.CreatedAt.AddDuration(order.Timeout)
now := carbon.Now()
fmt.Printf("订单ID: %d\n", order.ID)
fmt.Printf("创建时间: %s\n", order.CreatedAt.ToDateTimeString())
fmt.Printf("超时时间: %s\n", expireTime.ToDateTimeString())
fmt.Printf("当前时间: %s\n", now.ToDateTimeString())
if now.Gt(expireTime) {
fmt.Println("状态: 订单已超时,自动取消")
} else {
remaining := expireTime.DiffInMinutes(now)
fmt.Printf("状态: 订单进行中,还有 %d 分钟超时\n", remaining)
}
}
---
6.6 常见问题
01.时区问题处理
a.说明
时区是Carbon使用中最常见的问题之一。
数据库存储UTC时间,显示时转换为用户时区是最佳实践。
跨时区业务需要特别注意时间的存储和展示。
Carbon自动处理夏令时等复杂时区规则。
正确处理时区能避免大量的时间错误。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
// 问题1: 数据库时间与显示时间不一致
fmt.Println("=== 问题1: 时区不一致 ===\n")
// 错误做法:直接存储本地时间
localTime := carbon.Now()
fmt.Println("错误:存储本地时间:", localTime.ToDateTimeString())
// 正确做法:存储UTC时间
utcTime := carbon.Now().SetTimezone("UTC")
fmt.Println("正确:存储UTC时间:", utcTime.ToDateTimeString())
// 显示时转换为用户时区
userTimezone := "Asia/Tokyo"
displayTime := utcTime.SetTimezone(userTimezone)
fmt.Println("用户看到:", displayTime.ToDateTimeString())
// 问题2: 解析字符串时的时区
fmt.Println("\n=== 问题2: 解析时区 ===\n")
// 未指定时区,使用系统默认
parsed1 := carbon.Parse("2024-01-15 10:00:00")
fmt.Println("默认时区:", parsed1.Timezone())
// 指定时区解析
parsed2 := carbon.ParseByFormat(
"2024-01-15 10:00:00",
"Y-m-d H:i:s",
"America/New_York",
)
fmt.Println("纽约时区:", parsed2.Timezone())
// 问题3: 时区转换保持时刻
fmt.Println("\n=== 问题3: 时区转换 ===\n")
beijing := carbon.Parse("2024-01-15 10:00:00").
SetTimezone("Asia/Shanghai")
fmt.Println("北京时间:", beijing.ToDateTimeString())
// 转换到纽约时区(时刻不变)
newYork := beijing.SetTimezone("America/New_York")
fmt.Println("纽约时间:", newYork.ToDateTimeString())
// 验证时间戳相同
fmt.Printf("时间戳相同: %v\n",
beijing.Timestamp() == newYork.Timestamp())
}
---
02.格式化问题
a.说明
格式化是另一个常见问题点。
Carbon支持PHP风格格式符,与Go标准库不同。
需要注意格式符的大小写,Y和y代表不同含义。
日期格式要根据目标系统的要求选择。
掌握常用格式符能解决大部分格式化需求。
b.代码示例
---
package main
import (
"fmt"
"github.com/golang-module/carbon/v2"
)
func main() {
now := carbon.Now()
// 问题1: 格式符大小写
fmt.Println("=== 问题1: 格式符大小写 ===\n")
fmt.Println("Y(4位年):", now.Format("Y")) // 2024
fmt.Println("y(2位年):", now.Format("y")) // 24
fmt.Println("m(2位月):", now.Format("m")) // 01
fmt.Println("n(1位月):", now.Format("n")) // 1
fmt.Println("d(2位日):", now.Format("d")) // 15
fmt.Println("j(1位日):", now.Format("j")) // 15
// 问题2: 12/24小时制混淆
fmt.Println("\n=== 问题2: 12/24小时制 ===\n")
fmt.Println("H(24小时):", now.Format("H:i")) // 14:30
fmt.Println("h(12小时):", now.Format("h:i A")) // 02:30 PM
// 问题3: 标准格式输出
fmt.Println("\n=== 问题3: 标准格式 ===\n")
// ISO8601格式
fmt.Println("ISO8601:", now.ToIso8601String())
// 数据库格式
fmt.Println("MySQL:", now.ToDateTimeString())
// 自定义格式
fmt.Println("自定义:", now.Format("Y-m-d H:i:s"))
// 问题4: 多语言格式
fmt.Println("\n=== 问题4: 多语言 ===\n")
zhCN := now.SetLocale("zh-CN")
fmt.Println("中文:", zhCN.Format("Y年m月d日 l"))
en := now.SetLocale("en")
fmt.Println("英文:", en.Format("l, F d, Y"))
// 问题5: 常见错误
fmt.Println("\n=== 问题5: 常见错误 ===\n")
// 错误:使用Go标准库格式
// fmt.Println(now.Format("2006-01-02")) // 错误!
// 正确:使用Carbon格式符
fmt.Println("正确:", now.Format("Y-m-d"))
// 或使用预定义方法
fmt.Println("推荐:", now.ToDateString())
}
---
03.性能优化建议
a.说明
合理使用Carbon可以避免性能问题。
避免在循环中频繁创建Carbon对象。
使用预定义格式方法比自定义格式更快。
批量操作时考虑复用Carbon实例。
理解Carbon的不可变特性有助于优化代码。
b.代码示例
---
package main
import (
"fmt"
"time"
"github.com/golang-module/carbon/v2"
)
func main() {
// 性能建议1: 避免重复创建
fmt.Println("=== 建议1: 对象复用 ===\n")
// 不推荐:循环中重复创建
start := time.Now()
for i := 0; i < 1000; i++ {
_ = carbon.Now().ToDateString()
}
elapsed1 := time.Since(start)
fmt.Printf("重复创建: %v\n", elapsed1)
// 推荐:复用对象
start = time.Now()
now := carbon.Now()
for i := 0; i < 1000; i++ {
_ = now.ToDateString()
}
elapsed2 := time.Since(start)
fmt.Printf("对象复用: %v\n", elapsed2)
// 性能建议2: 使用预定义方法
fmt.Println("\n=== 建议2: 预定义方法 ===\n")
now = carbon.Now()
// 较慢:自定义格式
start = time.Now()
for i := 0; i < 1000; i++ {
_ = now.Format("Y-m-d H:i:s")
}
elapsed1 = time.Since(start)
fmt.Printf("自定义格式: %v\n", elapsed1)
// 较快:预定义方法
start = time.Now()
for i := 0; i < 1000; i++ {
_ = now.ToDateTimeString()
}
elapsed2 = time.Since(start)
fmt.Printf("预定义方法: %v\n", elapsed2)
// 性能建议3: 批量操作优化
fmt.Println("\n=== 建议3: 批量操作 ===\n")
// 不推荐:逐个处理
dates := make([]string, 100)
start = time.Now()
for i := 0; i < len(dates); i++ {
dates[i] = carbon.Now().AddDays(i).ToDateString()
}
elapsed1 = time.Since(start)
fmt.Printf("逐个处理: %v\n", elapsed1)
// 推荐:基准复用
dates = make([]string, 100)
start = time.Now()
base := carbon.Now()
for i := 0; i < len(dates); i++ {
dates[i] = base.AddDays(i).ToDateString()
}
elapsed2 = time.Since(start)
fmt.Printf("基准复用: %v\n", elapsed2)
// 性能建议4: 错误检查
fmt.Println("\n=== 建议4: 错误处理 ===\n")
// 推荐:使用Error字段
parsed := carbon.Parse("invalid-date")
if parsed.Error != nil {
fmt.Println("解析失败:", parsed.Error)
}
// 推荐:先验证再使用
if parsed.IsZero() {
fmt.Println("时间对象无效")
}
// 建议5: 合理的缓存策略
fmt.Println("\n=== 建议5: 缓存策略 ===\n")
// 对于不变的时间点,可以缓存
type Cache struct {
todayStart carbon.Carbon
todayEnd carbon.Carbon
}
cache := &Cache{
todayStart: carbon.Now().StartOfDay(),
todayEnd: carbon.Now().EndOfDay(),
}
fmt.Println("缓存的今日范围:")
fmt.Println("开始:", cache.todayStart.ToDateTimeString())
fmt.Println("结束:", cache.todayEnd.ToDateTimeString())
// 注意:跨天时需要更新缓存
fmt.Println("\n提示:定时更新缓存以保持准确性")
}
---