在日常 Go 开发中,我们经常需要计算时间差:统计一个函数的执行耗时、检测请求是否超时、判断缓存是否过期……这些场景都离不开时间计算。

你是否写过这样的代码?

start := time.Now()
doSomething()
elapsed := time.Now().Sub(start)

或者这样?

deadline := time.Now().Add(5 * time.Second)
// 处理超时逻辑
if time.Now().Sub(deadline) > 0 {
    // 已超时
}

如果你写过,那么今天的文章一定要看完——Go 语言为我们带来了更优雅的解决方案。

传统写法:time.Sub 与 time.Add

在 Go 1.8 之前,计算时间差主要靠 time.Time 类型的 Sub 方法:

start := time.Now()
heavyTask()
duration := time.Now().Sub(start)

Sub 接收一个时间参数,返回两者之间的 time.Duration

想让时间向前推移,则使用 Add 方法:

deadline := time.Now().Add(5 * time.Minute)

这种写法的问题是:语义不够明确。阅读 time.Now().Sub(start) 时,需要稍微想一下"谁减谁";Add 传入正数是向未来,负数才是向过去。当代码复杂时,这些细节会增加理解成本。

新写法:time.Since 与 time.Until

Go 1.8 正式引入了 time.Sincetime.Until 两个函数,彻底解决了上述问题。

// time.Since: 计算从过去某个时间点到现在的时间间隔
start := time.Now()
doWork()
elapsed := time.Since(start)
// time.Until: 计算从现在到未来某个时间点的间隔
deadline := time.Now().Add(5 * time.Second)
remaining := time.Until(deadline)

这两个函数的签名非常简洁:

func Since(t Time) Duration
func Until(t Time) Duration
  • time.Since(t) 等价于 time.Now().Sub(t),但语义更清晰——"从时间 t 到现在"
  • time.Until(t) 等价于 t.Sub(time.Now()),但更容易理解——"到时间 t 为止"

进化之美:为什么新写法更优秀

1. 语义更加直观

// 旧写法:需要思考"谁减谁"
elapsed := time.Now().Sub(start)

// 新写法:语义自解释
elapsed := time.Since(start)  // "自从 start 到现在"

阅读 time.Since(start),你不需要回忆 Sub 方法的参数顺序,因为它本身就是 "自从" 的意思。

2. 代码更简洁

在计算超时场景中,新写法优势尤为明显:

// 旧写法
if time.Now().Sub(deadline) > 0 {
    // 处理超时
}

// 新写法
if time.Until(deadline) < 0 {
    // 处理超时
}

time.Until(deadline) < 0 直观地表达 "距离截止时间还有负数时间",即已过期。

3. 减少错误发生

曾经有开发者写出这样的 bug:

// 错误:Sub 的参数顺序写反了
duration := start.Sub(time.Now())  // 得到负数!

使用 time.Since 时需要注意:它计算的是"从指定时间到现在"的间隔。如果传入的是过去时间,返回正值;传入未来时间,则返回负值。这与 time.Until 恰好相反——time.Until(t) 等价于 -time.Since(t)

4. 统一代码风格

在代码审查中,我们经常需要统一时间计算的方式。time.Sincetime.Until 提供了更一致且易于理解的 API,尤其在团队协作项目中,能显著减少沟通成本。

写在最后

time.Sub / time.Addtime.Since / time.Until,不仅仅是 API 的更新,更是 Go 语言对开发者体验的持续优化。

新方法的优势总结:

  • 语义明确Since 表示"自从",Until 表示"直到",见名知意
  • 降低心智负担:无需记忆参数顺序
  • 减少 bug:固定的语义减少了误用可能性
  • 代码更优雅:让时间计算逻辑一目了然

在实际项目中,我建议:能用 time.Since 和 time.Until 的场景,就不要使用 time.Sub。这不仅让代码更易读,也是 Go 社区推荐的最佳实践。