time 包能解决哪些时间问题
在 Go 里,只要涉及当前时间、时间差、定时等待、日志时间展示,基本都会用到 time 包。它既负责表示时间点,也提供时间戳转换、时间运算、格式化输出和字符串解析等能力,是处理时间相关逻辑的基础工具。
time.Time 是最核心的时间类型。通常先通过 time.Now() 获取当前时间对象,再从中读取年、月、日、时、分、秒等字段。
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间 2022-07-19 10:03:42.70981 +0800 CST m=+0.000080935
now := time.Now()
fmt.Println(now)
fmt.Println(now.Year())
fmt.Println(now.Month())
fmt.Println(now.Day())
fmt.Println(now.Hour())
fmt.Println(now.Minute())
fmt.Println(now.Second())
}
如果你的需求只是“拿到当前时间并拆出具体字段”,这一组方法就已经足够。后续的时间戳、格式化和加减运算,本质上也都是围绕 time.Time 展开的。
时间戳怎么获取,怎么转回时间对象
时间戳通常指从 1970 年 1 月 1 日(UTC)开始累计到当前时刻的时间值。实际开发里常见的有秒、毫秒和纳秒三种粒度。Go 里可以直接从时间对象上读取:

package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间 2022-07-19 10:03:42.70981 +0800 CST m=+0.000080935
now := time.Now()
fmt.Println(now)
// 时间戳 秒
fmt.Println(now.Unix())
// 时间戳 毫秒
fmt.Println(now.UnixMilli())
// 时间戳 纳秒
fmt.Println(now.UnixNano())
}
这几个方法分别对应不同精度。接口对接、数据库存储、消息队列或前后端传值时,先确认对方使用的是秒、毫秒还是纳秒,避免精度错位。
如果拿到的是 Unix 时间戳,想恢复成 time.Time,可以使用 time.Unix():
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间 2022-07-19 10:03:42.70981 +0800 CST m=+0.000080935
now := time.Now()
fmt.Println(now)
// 时间戳转时间类型
unix := time.Unix(now.Unix(), 0)
fmt.Println(unix)
}
这里的第一个参数是秒级时间戳,第二个参数是纳秒部分。示例里传入 0,表示只按秒恢复。如果你手里是更高精度的数据,需要把秒和纳秒拆开再传入。
时间间隔与常见时间运算
表示“经过了多久”时,Go 使用的是 time.Duration。它的底层单位是纳秒,适合描述两个时间点之间的间隔,也适合做延迟、超时和时间加减。

time 包预定义了常见时间单位常量:
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
这也是为什么我们经常会写出 time.Second * 5、time.Minute * 15 这样的表达式。它可读性强,也适合直接传给等待、超时和加减时间的相关 API。
time.Duration 还提供了不同粒度的读取方法:
func (d Duration) Nanoseconds() int64 {} // 纳秒
func (d Duration) Microseconds() int64 {} // 微秒
func (d Duration) Milliseconds() int64 {} // 毫秒
func (d Duration) Seconds() float64 {} // 秒
func (d Duration) Minutes() float64 {} // 分钟
func (d Duration) Hours() float64 {} // 小时
如果已经有一个时间点,最常见的操作就是做加减和求差值。Go 提供了几组常用方法:
func (t Time) Add(d Duration) Time {}
func (t Time) Sub(u Time) Duration {}
func (t Time) AddDate(years int, months int, days int) Time {}
Add 用于按时间间隔做加减,Sub 用于计算两个时间点之间的差值,AddDate 则适合按年、月、日进行偏移。对于“距离现在过去了多久”或“离某个时间点还有多久”这类场景,还可以直接使用快捷函数:
func Since(t Time) Duration {}
func Until(t Time) Duration {}
其中,Since 等价于 time.Now().Sub(t),Until 等价于 t.Sub(time.Now())。写计时、倒计时或过期判断时,这两个函数通常会更顺手。
时间加减与差值计算示例
t := time.Now() fmt.Println(t) // 2022-07-17 22:41:06.001567 +0800 CST m=+0.000057466 //时间增加 1小时 fmt.Println(t.Add(time.Hour * 1)) // 2022-07-17 23:41:06.001567 +0800 CST m=+3600.000057466 //时间增加 15 分钟 fmt.Println(t.Add(time.Minute * 15))// 2022-07-17 22:56:06.001567 +0800 CST m=+900.000057466 //时间增加 10 秒钟 fmt.Println(t.Add(time.Second * 10))// 2022-07-17 22:41:16.001567 +0800 CST m=+10.000057466 //时间减少 1 小时 fmt.Println(t.Add(-time.Hour * 1)) // 2022-07-17 21:41:06.001567 +0800 CST m=-3599.999942534 //时间减少 15 分钟 fmt.Println(t.Add(-time.Minute * 15))// 2022-07-17 22:26:06.001567 +0800 CST m=-899.999942534 //时间减少 10 秒钟 fmt.Println(t.Add(-time.Second * 10))// 2022-07-17 22:40:56.001567 +0800 CST m=-9.999942534 time.Sleep(time.Second * 5) t2 := time.Now() // 计算 t 到 t2 的持续时间 fmt.Println(t2.Sub(t)) // 5.004318874s // 1 年之后的时间 t3 := t2.AddDate(1, 0, 0) // 计算从 t 到当前的持续时间 fmt.Println(time.Since(t)) // 5.004442316s // 计算现在到明年的持续时间 fmt.Println(time.Until(t3)) // 8759h59m59.999864s
这段代码基本覆盖了开发里最常见的三类需求:给时间点做偏移、比较两个时间点的差值、基于当前时刻做耗时和剩余时间计算。实际写业务逻辑时,优先区分你处理的是“时间点”还是“时间间隔”,代码会更清晰。

时间格式化与字符串解析的关键规则
时间格式化是 time 包里最容易踩坑、也是使用最频繁的一部分。Go 没有采用常见的 Y-m-d H:M:S 这类占位符,而是用一个固定参考时间作为模板:
2006-01-02 15:04:05
准确地说,Go 使用的是 2006 年 1 月 2 日 15 点 04 分 05 秒这个参考时间来表示格式。如果模板写错,即使看起来像“正常日期格式”,结果也可能不符合预期。
格式化输出示例如下:
now := time.Now()
// 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
// 24小时制
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
这里有一个容易忽略的点:如果你要输出 12 小时制,模板里要明确写上 PM。如果使用 24 小时制,则通常用 15 来表示小时部分。
反过来,如果你需要把字符串解析成 time.Time,可以使用 Parse 或 ParseInLocation。当输入字符串本身没有完整时区信息,但业务又依赖指定地区时间时,推荐直接使用后者:
now := time.Now()
fmt.Println(now)
// 加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
// 按照指定时区和指定格式解析字符串时间
timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2019/08/04 14:15:20", loc)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(timeObj)
fmt.Println(timeObj.Sub(now))
time.LoadLocation("Asia/Shanghai") 先加载时区,再配合 time.ParseInLocation 按指定模板解析字符串。这样做的好处是,解析结果会直接落在目标时区,适合处理本地业务时间、日志对齐、报表统计等需求。
日常开发里该怎么选这些 API
如果只是获取当前时间并读取字段,用 time.Now() 加上 Year()、Month()、Day() 这类方法就够了;如果需要跨系统传值,优先确认时间戳单位,再选择 Unix()、UnixMilli() 或 UnixNano()。
需要做时间差、超时或延迟时,核心是 time.Duration;需要对某个时间点做前后偏移,用 Add 或 AddDate;需要“距离现在多久”,直接用 Since 和 Until 更简洁。
而在格式化与解析场景里,最值得记住的一条规则就是 Go 的模板时间:2006-01-02 15:04:05。把这个参考模板记熟,再配合时区处理,绝大多数时间相关需求都能比较稳地落地。





