Go中计算时间差最可靠方式是time.Sub,返回time.Duration需手动转换单位;注意时区统一、避免浮点误差、慎用time.Since,优先用UnixMilli()获取单调递增整数差。

Go 里计算两个时间差,直接用 time.Since 或 time.Until 最省事,但要注意它们返回的是 time.Duration,不是秒数或毫秒数——得手动转,否则容易误判。
用 time.Sub 得到精确的 Duration
这是最基础也最可控的方式:两个 time.Time 相减,返回正/负的 time.Duration。
-
time.Now().Sub(t1)返回从t1到现在的时长(若t1在过去,结果为正) -
t2.Sub(t1)若t2早于t1,结果为负;Go 不会自动取绝对值 - 注意时区:如果两个
time.Time时区不同(比如一个带UTC,一个用本地时区),Sub算的是绝对时间差,没问题;但显示时容易混淆,建议统一用UTC()或Local()再比较 -
Duration默认以纳秒为单位,打印出来是类似3h24m15.123s的字符串,但底层是 int64,别直接拿它跟数字比较
把 Duration 转成秒、毫秒、分钟这些常用单位
Duration 提供了 .Seconds()、.Milliseconds()、.Minutes() 等方法,但它们返回 float64 —— 这点常被忽略,导致整数比较出错。
- 要整数毫秒:用
d.Milliseconds()(Go 1.19+),或兼容老版本写d / time.Millisecond(注意这是 int64 除法,截断不四舍五入) - 要秒数取整:推荐
int64(d.Seconds()),而不是d / time.Second—— 后者在纳秒精度下可能因舍入误差差 1 - 避免直接用
d > 5 * time.Second做阈值判断,没问题;但若你存的是毫秒整数再反推,就容易错位
别用 time.Since 做跨 goroutine 或长周期计时
time.Since(t) 等价于 time.Now().Sub(t),看着方便,但有隐含风险:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 它每次调用都触发一次系统时钟读取,在高频循环里(比如每毫秒调用)会有可观开销
- 如果
t是很久以前的时间(比如服务启动时刻),而系统时钟被 NTP 调整过,Since可能突变(跳变或回退),导致差值异常 - 更稳的做法:用
time.Now().UnixMilli() - t.UnixMilli()(Go 1.17+),这是单调递增的整数差,不受系统时钟调整影响
时间差格式化输出容易漏掉时区和精度
直接 fmt.Println(t2.Sub(t1)) 会输出带纳秒精度的字符串,但实际业务中往往只需要“X 小时 Y 分钟”或“Z 天前”这种人类可读格式。
- 标准库不提供自然语言格式化(如 “2 days ago”),得自己写或引入
github.com/freddierice/ago这类小包 - 用
fmt.Printf("%02dh%02dm%02ds", d.Hours(), d.Minutes(), d.Seconds())是错的——Hours()返回 float64,且不是整数小时余数 - 正确拆解:先转成总秒数
s := int64(d.Seconds()),再用s/3600、(s%3600)/60、s%60计算 - 如果涉及日期跨度(比如超过 24 小时),记得考虑
time.Date的日历逻辑,别只靠Duration
真正难的不是算差值,而是搞清你要的是“物理经过时间”还是“日历间隔”,以及是否受系统时钟漂移影响。这两点没想清楚,后面所有格式化和判断都可能偏一拍。

















