今天学习time包
- time.Time
- time.Duration
- time.Now()和time.Since()
- Add
- AddDate
- time.Date
- 日期格式化
- time.Parse
- Unix时间戳
- 时间比较
time.Time
time.Time表示某个具体瞬间,精度到纳秒,通常建议把time.Time当值传递,而不是用*time.Time;它的零值是0001-01-01 00:00:00 UTC,判断是否初始化应该使用.IsZero()。
从他的零值就可以看到,它有别于时间戳,它的默认值是公元1年1月1日。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| type User struct { ID int64 LastLogin time.Time }
func NeedFirstLoginGuide(u User) bool { return u.LastLogin.IsZero() }
func TestTimeTime(t *testing.T) { u := User{ ID: 1, LastLogin: time.Now(), }
t.Log(NeedFirstLoginGuide(u)) }
|
time.Duration
表示两个时间点之间的间隔,Duration底层是int64,单位是纳秒。
1 2 3 4 5 6 7 8
| type Config struct { TimeoutSeconds int }
func Timeout(cfg Config) time.Duration { return time.Duration(cfg.TimeoutSeconds) * time.Second }
|
time.Second不是普通整数,而是time.Duration的类型常量。
time.Now()、time.Since()
time.Now()返回当前本地时间;从time.Now()得到的值包含wall clock和monotonic clock。Go在计算Sub、Since、Until等耗时时,会优先使用单调时钟,避免系统时间被NTP或人工调整。
1 2 3 4 5 6 7
| func TestTimeNowAndSince(t *testing.T) { start := time.Now() defer func() { fmt.Println("cost :", time.Since(start)) }() time.Sleep(120 * time.Millisecond) }
|
测量耗时用time.Since(start),不要用time.Now().Unix() - start.Unix()。
Add:加固定时长
Add(d)表示在某个时间点上加一个固定的Duration。
1 2 3
| func NewCodeExpireAt() time.Time { return time.Now().Add(5 * time.Minute) }
|
Add(24 * time.Hour)是加固定24小时,不等于日历上的明天。
AddDate: 加年、月、日
AddDate(years,months,days)用于日历意义上的加年、月、日。
1 2 3 4 5 6 7
| func TestAddDate(t *testing.T) { layout := "2006-01-02 15:04:05" now := time.Now() fmt.Println(now.Format(layout)) fmt.Println(NextBillingDate(now).Format(layout)) }
|
AddDate(0,1,0)和Add(30 * 24 * time.Hour)是不一样的,切记切记。会受到夏令时、月底等因素造成结果不同。
time.Date 构造时间
time.Date可以构造指定年月日时分秒纳秒和时区的事件。他会对超出范围的月、日、时、分、秒、做归一化。例如10月32日会变成11月1日。传入nillocation会panic。
1 2 3 4 5 6 7 8
| func LastDayOfMonth(year int, month time.Month, loc *time.Location) time.Time { return time.Date(year, month+1, 0, 0, 0, 0, 0, loc) }
func TestTimeDate(t *testing.T) { layout := "2006-01-02 15:04:05" t.Log(LastDayOfMonth(2026, time.June, time.Local).Format(layout)) }
|
格式化
Go的时间格式化并不是yyyy-MM-dd,而是使用固定参考时间2006-01-02 15:04:05 (记忆:01-02 下午 03:04:05 06年)
1 2 3 4 5
| func TestTimeDate(t *testing.T) { layout := "2006-01-02 15:04:05" now := time.Now() t.Log(now.Format(layout)) }
|
如果不想用2006-01-02 15:04:05,你可以使用任意一个时间,因为它是参考格式,并不是内容:2026-06-19 10:00:00。
time.Parse 解析
time.Parse(layout,value)使用layout解析字符串;如果字符串没有时区信息,Parse默认返回UTC时间。
1 2 3 4 5 6 7 8 9 10 11
| func ParseUTC(s string) (time.Time, error) { return time.Parse("2006-01-02 15:04:05", s) }
func TestTimeParse(t *testing.T) { currentTime, err := ParseUTC("2026-06-18 15:00:00") if err != nil { t.Fatal(err) } t.Log(currentTime.AddDate(0, 0, 1).Format("2006-01-02 15:04:05")) }
|
time.ParseInLocation本地时区解析
用户输入2026-06-18 09:00:00通常表示用户所在时区的9点,这是应使用ParseInLocation。
1 2 3 4 5 6 7 8 9 10 11
| func ParseShanghaiTime(s string) (time.Time, error) { loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { return time.Time{}, err } return time.ParseInLocation("2006-01-02 15:04:05", s, loc) }
func TestTimeParseInLocation(t *testing.T) { t.Log(ParseShanghaiTime("2026-06-18 15:00:00")) }
|
Unix时间戳
time.Unix(sec,nsec)用秒和纳秒构造时间,另外还提供UnixMilli、UnixMicro等方法处理毫秒和微秒时间戳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func FromJSTimestamp(ms int64) time.Time { return time.UnixMilli(ms) }
func ToJSTimestamp(t time.Time) int64 { return t.UnixMilli() }
func TestUnix(t *testing.T) { ms := ToJSTimestamp(time.Now()) t.Log(ms)
unixTime := FromJSTimestamp(ms) t.Log(unixTime.Format("2006-01-02 15:04:05")) }
|
通常前端使用的都是毫秒,而Go的Unix()是秒。不要把毫秒吴传给time.Unix(ms,0)。
时间比较
Equal判断两个Time是否表示同一瞬间,即使它们处于不同时区也可以相等。 通常建议使用t.Equal(u)来判断,而不是t == u,因为==还会比较Location和单调时钟信息。其他的比较还有After、Before、Compare。
1 2 3 4 5 6 7 8 9 10 11
| func IsExpired(expireAt time.Time) bool { return time.Now().After(expireAt) }
func TestTimeEqual(t *testing.T) { utc := time.Date(2026, time.June, 18, 15, 0, 0, 0, time.UTC) shanghai, _ := time.LoadLocation("Asia/Shanghai") cn := time.Date(2026, time.June, 18, 15, 0, 0, 0, shanghai) t.Logf("UTC: %v, Shanghai: %v, Equals %v ", utc, cn, utc.Equal(cn)) }
|
Round和Truncate:时间取整
Time.Round(d)会四舍五入到最近的d倍数。Time.Truncate(d)会向下截断到d倍数。
1 2 3 4 5 6 7
| func TestTimeRound(t *testing.T) { now := time.Now() t.Log(now.Round(time.Minute).Format("2006-01-02 15:04:05")) t.Log(now.Truncate(time.Minute).Format("2006-01-02 15:04:05")) }
|
Truncate(24 * time.Hour)是按照绝对时间阶段,不等于本地当天零点。
如果想要实现本地当天零点:
1 2 3 4
| func TestTimeRound1(t *testing.T) { y, m, d := time.Now().Date() t.Log(time.Date(y, m, d, 0, 0, 0, 0, time.Local).Format("2006-01-02 15:04:05")) }
|
time.Sleep:暂停当前goroutine
这个和Java的一模一样了,都是让当前线(协)程睡眠一会儿。
简单的重试
1 2 3 4 5 6 7 8 9 10 11
| func Retry(fn func() error) error { var err error for i := 0; i < 3; i++ { if err = fn(); err != nil { return nil }
time.Sleep(time.Duration(i+1) * time.Second) } return err }
|
Time.Sleep() 只阻塞当前goroutine,不会阻止整个进程。
time.After: 一次性超时channel
time.After(d)返回一个channel,到期后会受到当前时间。未引用且停止的timer可以被GC回收;
select超时:
1 2 3 4 5 6 7 8
| func ReadWithTimeout(ch <-chan string) (string, error) { select { case v := <-ch: return v, nil case <-time.After(2 * time.Second): return "", fmt.Errorf("timeout") } }
|
循环中频繁创建time.After可能带来额外分配,对于这种高频循环,更推荐复用Timer
NewTimer\Stop\Reset:可控的一次性的定时器
Timer表示一次性事件,到期后会向c发送当前事件,从Go 1.23开始 timer channel默认是同步、无缓冲的。Stop或Reset返回后不会在收到就得stale time。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func WatchIdle(events <-chan struct{}) { timer := time.NewTimer(10 * time.Second) defer timer.Stop() for { select { case <-events: timer.Reset(10 * time.Second) case <-timer.C: fmt.Println("idel timeout") return } } }
|
AfterFunc:到期后启动goroutine执行函数
time.AfterFunc(d,f)会等待d后在自己的goroutine里执行f;Stop不会等待已久启动的f结束。
1 2 3 4 5 6
| func AutoClose(closeFn func()) *time.Timer { return time.AfterFunc(30*time.Second, func() { closeFn() }) }
|
AfterFunc的回调可能并发执行,如果Reset后旧调用还没结束,要自己用锁、Channel或者context协调。
Ticker:周期性任务
Ticker用于周期性出发,从Go 1.23开始,未引用的ticker可以被GC回收;Stop不在为了帮助GC必须,但业务上想停止周期任务任需要调用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| func HeartBeat(done <-chan struct{}) { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-ticker.C: fmt.Println("Ping ...") case <-done: return } } }
|
获取年月日、星期、ISO周
Time提供Date()、Clock()、Weekday()、ISOWeek()、YearDay()等方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func TestTimeDate1(t *testing.T) { date := time.Date(2026, 6, 10, 10, 30, 0, 0, time.Local) t.Logf("date: %v, year: %d, month: %d, day: %d", date, date.Year(), date.Month(), date.Day()) t.Log(date.Date()) t.Log(date.Clock()) t.Log(date.Weekday()) t.Log(date.YearDay()) t.Log(date.ISOWeek()) }
|