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
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
twoHoursAgo := now.Add(-2 * time.Hour)
fmt.Println(tomorrow.Format("2006-01-02 15:04"))
// 输出类似:2025-06-12 14:22Sub 计算两个时间点的差值(返回 time.Duration)Sub 是唯一能直接得到两个时间点间隔的方法,结果是 time.Duration 类型,单位为纳秒(但可转成秒、小时等)。
t1.Sub(t2) 等价于 t1 - t2,结果为正表示 t1 在 t2 之后Sub 基于 Unix 时间戳(UTC),自动对齐start := time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC) end := time.Date(2025, 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)——后者不处理月份天数差异和闰年。
AddDate(years, months, days) 按日历规则调整,比如 1 月 31 日 + 1 个月 → 2 月 28 日(或 29 日)Add 和 AddDate 不能互相替代;混用会导致时间漂移(尤其在月末)SubDate,减法用 AddDate(-y, -m, -d)
t := time.Date(2025, 1, 31, 0, 0, 0, 0, time.UTC) nextMonth := t.AddDate(0, 1, 0) // 2025-02-29(闰年) // 错误示范:t.Add(30 * 24 * time.Hour) → 2025-03-02(跳过整个 2 月)
看似简单的方法,实际出错常因忽略底层细节:
time.Time{} 是零值(1 年 1 月 1 日 UTC),对它调用 Add 或 Sub 不报错但
t.IsZero() 预检*time.Time,记得先解引用再调用方法:tPtr.Add(...) 而非 (*tPtr).Add(...)(Go 允许前者,但易误读)Sub 结果是 Duration,不是 Time;反过来想“某时间点前 5 分钟”得用 t.Add(-5 * time.Minute),不是 t.Sub(5 * time.Minute)(语法错误)时间计算的复杂性不在 API,而在你是否清楚自己要的是“物理偏移”还是“日历偏移”,以及是否意识到零值、时区、闰秒这些隐含前提。