Go 的 time.Time 不可变,Add 和 Sub 是最安全的时间计算方法:Add 用于物理时间偏移(如 24*time.Hour),Sub 返回纳秒级 Duration 差值;AddDate 才适用于年/月/日的日历语义增减,避免混用导致漂移;需警惕零值、时区和指针解引用问题。

Go 的 time.Time 本身不可变,所有时间计算都返回新值,Add 和 Sub 是最常用、最安全的两个方法——别试图修改原时间变量。
用 Add 加减固定时长(time.Duration)
Add 接收一个 time.Duration,返回一个新的 time.Time。它不关心时区或夏令时跳变,纯粹是“物理时间偏移”。
- 正数
Duration向后推,负数向前拉 - 常见写法:
time.Hour、24 * time.Hour、time.Second * 30 - 不能传入年/月这种不固定长度的单位(比如“加1个月”可能有28/30/31天)
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
twoHoursAgo := now.Add(-2 * time.Hour)
fmt.Println(tomorrow.Format("2006-01-02 15:04"))
// 输出类似:2024-06-12 14:22用 Sub 计算两个时间点的差值(返回 time.Duration)
Sub 是唯一能直接得到两个时间点间隔的方法,结果是 time.Duration 类型,单位为纳秒(但可转成秒、小时等)。
-
t1.Sub(t2)等价于t1 - t2,结果为正表示t1在t2之后 - 结果可能为负,注意业务逻辑是否允许(比如倒计时场景需取绝对值)
- 跨时区计算仍准确——
Sub基于 Unix 时间戳(UTC),自动对齐
start := time.Date(2024, 1, 1, 10, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 12, 30, 0, 0, time.UTC) dur := end.Sub(start) // 2h30m fmt.Println(dur.Hours()) // 2.5 fmt.Println(int64(dur / time.Second)) // 9000
别混淆 AddDate:它专用于年/月/日的语义化增减
如果要“加1个月”或“减2年”,必须用 AddDate,而不是 Add(30 * 24 * time.Hour)——后者不处理月份天数差异和闰年。
立即学习“go语言免费学习笔记(深入)”;
-
AddDate(years, months, days)按日历规则调整,比如 1 月 31 日 + 1 个月 → 2 月 28 日(或 29 日) -
Add和AddDate不能互相替代;混用会导致时间漂移(尤其在月末) - 没有
SubDate,减法用AddDate(-y, -m, -d)
t := time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC) nextMonth := t.AddDate(0, 1, 0) // 2024-02-29(闰年) // 错误示范:t.Add(30 * 24 * time.Hour) → 2024-03-02(跳过整个 2 月)
容易踩的坑:时区与零值、指针解引用
看似简单的方法,实际出错常因忽略底层细节:
-
time.Time{}是零值(1 年 1 月 1 日 UTC),对它调用Add或Sub不报错但结果无意义 - 从数据库或 JSON 解析可能得到零值时间,建议用
t.IsZero()预检 - 若变量是
*time.Time,记得先解引用再调用方法:tPtr.Add(...)而非(*tPtr).Add(...)(Go 允许前者,但易误读) -
Sub结果是Duration,不是Time;反过来想“某时间点前 5 分钟”得用t.Add(-5 * time.Minute),不是t.Sub(5 * time.Minute)(语法错误)
时间计算的复杂性不在 API,而在你是否清楚自己要的是“物理偏移”还是“日历偏移”,以及是否意识到零值、时区、闰秒这些隐含前提。










