Go 从 0 到精通 · 第 20 课:时间处理
学习定位:这是整套 Go 教程的第 20 课。
前置要求:已经完成第 19 课,掌握了文件操作基础。需要熟悉基本结构体和方法的用法。
本课目标:掌握 Go 中时间与日期的常见操作,包括获取时间、格式化、解析、时间计算、定时器和超时控制,能处理日志时间、延迟和超时等真实场景。
1. 本课你要解决的核心问题
时间无处不在——日志要带时间戳,缓存要判断过期,任务要定时执行,接口要设置超时……
你需要搞明白以下问题:
- 怎么获取当前时间
- Go 独特的时间格式化方式到底怎么回事
- 怎么把字符串解析成时间、把时间转成字符串
- 怎么做时间加减、比较
- 怎么实现定时器和周期执行
- 怎么设置超时控制
学完这一课,你就能让程序"感知时间"了。
2. time 包总览
Go 的所有时间功能都在 time 包里。核心类型就两个:
| 类型 |
含义 |
time.Time |
一个时间点(比如 “2024年1月15日 10点30分0秒”) |
time.Duration |
一段时间(比如 “3秒”、“5分钟”) |
先记住这两个概念,下面逐步展开。
3. 获取当前时间
3.1 time.Now()
1 2 3 4 5 6 7 8 9 10 11
| package main
import ( "fmt" "time" )
func main() { now := time.Now() fmt.Println(now) }
|
输出(取决于你的时区):
1
| 2024-01-15 10:30:45.123456789 +0800 CST
|
这个输出包含:
2024-01-15:日期
10:30:45.123456789:时间(精确到纳秒)
+0800:时区偏移(东8区)
CST:时区名称
3.2 获取时间的各个组成部分
time.Time 提供了一系列方法来获取年、月、日、时、分、秒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import ( "fmt" "time" )
func main() { now := time.Now()
fmt.Println("年:", now.Year()) fmt.Println("月:", now.Month()) fmt.Println("月份数字:", int(now.Month())) fmt.Println("日:", now.Day()) fmt.Println("时:", now.Hour()) fmt.Println("分:", now.Minute()) fmt.Println("秒:", now.Second()) fmt.Println("纳秒:", now.Nanosecond()) fmt.Println("星期几:", now.Weekday()) }
|
注意:now.Month() 返回的是 time.Month 类型,打印出来是英文名。要数字就 int(now.Month())。
3.3 获取更多时间信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| now := time.Now()
fmt.Println("一年中的第几天:", now.YearDay())
year, month, day := now.Date() midnight := time.Date(year, month, day, 0, 0, 0, 0, now.Location()) since := now.Sub(midnight) fmt.Println("今天已过:", since)
isLeap := time.Date(now.Year(), 3, 0, 0, 0, 0, 0, time.UTC).Day() == 29 fmt.Println("闰年:", isLeap)
|
4. 时间格式化——Go 最独特的设计之一
4.1 为什么说它"独特"
其他语言用占位符格式化时间:
1 2 3
| Java: yyyy-MM-dd HH:mm:ss Python: %Y-%m-%d %H:%M:%S C: %Y-%m-%d %H:%M:%S
|
Go 用一个特定的参考时间来格式化:
1
| 2006-01-02 15:04:05 Monday MST -0700
|
记住这个时间:2006年1月2日 15点4分5秒 星期一 MST -0700
为什么选这个时间?因为这是 Go 语言正式对外公布的时间(2006年1月2日下午3点4分5秒,美国山地标准时间-0700)。每个数字都是唯一的:
| 位置 |
值 |
含义 |
| 2006 |
年 |
四位年份 |
| 01 |
月 |
两位月份(01-12) |
| 02 |
日 |
两位日期(01-31) |
| 15 |
时 |
24小时制(00-23) |
| 04 |
分 |
分钟(00-59) |
| 05 |
秒 |
秒(00-59) |
| Monday |
星期 |
星期几 |
| MST |
时区 |
时区缩写 |
| -0700 |
时区偏移 |
±HHMM |
如何记住这个参考时间:1-2-3-4-5-6。即 2006-01-02 15:04:05。月是01,日是02,时是15(下午3点=12+3),分是04,秒是05。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package main
import ( "fmt" "time" )
func main() { now := time.Now()
fmt.Println(now.Format("2006-01-02"))
fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("01-02 15:04"))
fmt.Println(now.Format("15:04:05"))
fmt.Println(now.Format("2006-01-02 Monday"))
fmt.Println(now.Format("2006-01-02 03:04:05 PM"))
fmt.Println(now.Format(time.RFC3339)) }
|
4.3 Go 预定义的格式常量
Go 提供了一批预定义的格式,避免你每次都手写参考时间:
1 2 3 4 5 6
| time.ANSIC time.RFC3339 time.RFC822 time.Kitchen time.Stamp
|
使用方式:
1
| now.Format(time.RFC3339)
|
4.4 格式化实战:日志时间戳
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main
import ( "fmt" "time" )
func log(level, msg string) { timestamp := time.Now().Format("2006-01-02 15:04:05.000") fmt.Printf("[%s] [%-5s] %s\n", timestamp, level, msg) }
func main() { log("INFO", "程序启动") log("DEBUG", "加载配置文件") log("INFO", "监听端口 8080") log("WARN", "配置文件未找到,使用默认值") log("ERROR", "数据库连接失败") }
|
输出:
1 2 3 4 5
| [2024-01-15 10:30:45.123] [INFO ] 程序启动 [2024-01-15 10:30:45.123] [DEBUG] 加载配置文件 [2024-01-15 10:30:45.124] [INFO ] 监听端口 8080 [2024-01-15 10:30:45.124] [WARN ] 配置文件未找到,使用默认值 [2024-01-15 10:30:45.124] [ERROR] 数据库连接失败
|
5. 解析时间字符串
5.1 用 time.Parse 把字符串变成 time.Time
解析和格式化是相反的过程。格式化用的参考时间,解析也要用同样的参考时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "time" )
func main() { t, err := time.Parse("2006-01-02 15:04:05", "2024-03-15 14:30:00") if err != nil { fmt.Println("解析失败:", err) return }
fmt.Println("解析结果:", t) fmt.Println("年:", t.Year()) fmt.Println("月:", t.Month()) fmt.Println("日:", t.Day()) fmt.Println("是星期几:", t.Weekday()) }
|
输出:
1 2 3 4 5
| 解析结果: 2024-03-15 14:30:00 +0000 UTC 年: 2024 月: March 日: 15 是星期几: Friday
|
注意:time.Parse 解析出的时间默认是 UTC 时区(+0000 UTC),不是你本地时区!要用 time.ParseInLocation 来指定时区(下面讲)。
5.2 解析不同格式的时间字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| t, _ := time.Parse("2006-01-02", "2024-03-15") fmt.Println(t)
t, _ := time.Parse("15:04:05", "14:30:00") fmt.Println(t)
t, _ := time.Parse("2006/01/02", "2024/03/15") fmt.Println(t)
t, _ := time.Parse("2006-01-02T15:04:05-07:00", "2024-03-15T14:30:00+08:00") fmt.Println(t)
|
5.3 time.ParseInLocation:指定时区解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" "time" )
func main() { loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { fmt.Println("加载时区失败:", err) return }
t, err := time.ParseInLocation("2006-01-02 15:04:05", "2024-03-15 14:30:00", loc) if err != nil { fmt.Println("解析失败:", err) return }
fmt.Println("解析结果:", t) }
|
这和 time.Parse 的区别:
time.Parse("...","2024-03-15 14:30:00") → 2024-03-15 14:30:00 +0000 UTC
time.ParseInLocation("...","2024-03-15 14:30:00", shanghai) → 2024-03-15 14:30:00 +0800 CST
同样一个字符串,解析出的时刻是不同的!前者认为是 UTC 时间,后者认为是东8区时间。
1 2
| UTC 版本: 2024-03-15 14:30:00 UTC = 2024-03-15 22:30:00 北京时间 上海版本: 2024-03-15 14:30:00 +0800 = 2024-03-15 14:30:00 北京时间
|
实际开发中:解析用户输入的时间(比如 “2024-03-15 14:30”),几乎总是要用 ParseInLocation,因为用户说的时间通常是本地时区。
5.4 常用时区
1 2 3 4 5 6 7 8
| loc, _ := time.LoadLocation("Asia/Shanghai") loc, _ := time.LoadLocation("America/New_York") loc, _ := time.LoadLocation("UTC") loc, _ := time.LoadLocation("Local")
local := time.Now().Location()
|
6. 时间计算
6.1 time.Duration:时间段
time.Duration 表示一段时间,本质是一个 int64(纳秒数)。Go 提供了方便的常量:
1 2 3 4 5 6
| time.Nanosecond time.Microsecond time.Millisecond time.Second time.Minute time.Hour
|
6.2 时间加减
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package main
import ( "fmt" "time" )
func main() { now := time.Now()
later := now.Add(1 * time.Hour) fmt.Println("1小时后:", later.Format("15:04:05"))
earlier := now.Add(-30 * time.Minute) fmt.Println("30分钟前:", earlier.Format("15:04:05"))
in2Days := now.Add(48 * time.Hour) fmt.Println("2天后:", in2Days.Format("2006-01-02"))
lastWeek := now.Add(-7 * 24 * time.Hour) fmt.Println("1周前:", lastWeek.Format("2006-01-02")) }
|
输出示例:
1 2 3 4
| 1小时后: 11:30:45 30分钟前: 10:00:45 2天后: 2024-01-17 1周前: 2024-01-08
|
6.3 更安全的时间加减:AddDate
用 Add 加天数需要自己算小时数(24 * time.Hour),而且处理跨月、闰年很麻烦。用 AddDate 更安全:
1 2 3 4 5 6 7 8 9 10 11 12 13
| now := time.Now()
fmt.Println(now.AddDate(1, 0, 0).Format("2006-01-02"))
fmt.Println(now.AddDate(0, 2, 0).Format("2006-01-02"))
fmt.Println(now.AddDate(0, 0, -10).Format("2006-01-02"))
fmt.Println(now.AddDate(1, 3, 5).Format("2006-01-02"))
|
AddDate(years, months, days) 能正确处理月末和闰年:
1 2 3 4 5 6 7 8
| t := time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC) fmt.Println(t.AddDate(0, 1, 0))
fmt.Println(t.AddDate(0, 2, 0))
|
6.4 计算两个时间之间的差值:Sub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "time" )
func main() { start := time.Now()
time.Sleep(1500 * time.Millisecond)
end := time.Now()
duration := end.Sub(start) fmt.Println("耗时:", duration) fmt.Println("耗时(秒):", duration.Seconds()) fmt.Println("耗时(毫秒):", duration.Milliseconds()) fmt.Println("耗时(微秒):", duration.Microseconds()) fmt.Println("耗时(纳秒):", duration.Nanoseconds()) }
|
Sub 返回的是 time.Duration,可以用各种方法转换成不同单位。
6.5 实战:函数执行计时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "fmt" "time" )
func slowOperation() { start := time.Now()
total := 0 for i := 0; i < 10000000; i++ { total += i }
elapsed := time.Since(start) fmt.Printf("操作完成,耗时 %v\n", elapsed) }
func main() { slowOperation() }
|
time.Since(t) 是 time.Now().Sub(t) 的简写,非常常用。
6.6 time.Until:到某个时间还有多久
1 2 3
| deadline := time.Date(2024, 12, 31, 23, 59, 59, 0, time.Local) remaining := time.Until(deadline) fmt.Printf("距离年底还有 %v\n", remaining)
|
7. 时间比较
7.1 Before、After、Equal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main
import ( "fmt" "time" )
func main() { t1 := time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC) t2 := time.Date(2024, 3, 15, 14, 0, 0, 0, time.UTC)
fmt.Println("t1 在 t2 之前?", t1.Before(t2)) fmt.Println("t1 在 t2 之后?", t1.After(t2)) fmt.Println("t1 和 t2 相等?", t1.Equal(t2))
t3 := time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC) fmt.Println("t1 和 t3 相等?", t1.Equal(t3)) }
|
注意:比较时间用 Equal,不要用 ==。因为 time.Time 内部可能包含不同的时区信息,== 会比较所有字段,可能意外返回 false。
7.2 判断时间是否在某个区间内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import ( "fmt" "time" )
func main() { now := time.Now() start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.Local) end := time.Date(2024, 12, 31, 23, 59, 59, 0, time.Local)
if now.After(start) && now.Before(end) { fmt.Println("当前时间在 2024 年内") } else { fmt.Println("当前时间不在 2024 年内") } }
|
7.3 判断是否是今天
1 2 3 4 5 6
| func isToday(t time.Time) bool { now := time.Now() y1, m1, d1 := t.Date() y2, m2, d2 := now.Date() return y1 == y2 && m1 == m2 && d1 == d2 }
|
8. 时区处理
8.1 time.UTC 和 time.Local
1 2 3 4 5 6 7 8 9 10 11
| now := time.Now()
utc := now.UTC() fmt.Println("本地时间:", now) fmt.Println("UTC 时间:", utc)
loc, _ := time.LoadLocation("America/New_York") nyTime := now.In(loc) fmt.Println("纽约时间:", nyTime)
|
8.2 时间戳
Unix 时间戳是从 1970-01-01 00:00:00 UTC 到现在的秒数(或纳秒数)。这是很多系统和数据库交互的通用格式:
1 2 3 4 5 6 7 8 9 10 11 12
| now := time.Now()
fmt.Println("秒级时间戳:", now.Unix()) fmt.Println("毫秒级时间戳:", now.UnixMilli()) fmt.Println("微秒级时间戳:", now.UnixMicro()) fmt.Println("纳秒级时间戳:", now.UnixNano())
ts := int64(1705285845) t := time.Unix(ts, 0) fmt.Println("从时间戳还原:", t.Format("2006-01-02 15:04:05"))
|
8.3 实战:数据库时间戳处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main
import ( "fmt" "time" )
func main() { dbTimestamp := int64(1705285845)
t := time.Unix(dbTimestamp, 0) fmt.Println("数据库时间:", t.Format("2006-01-02 15:04:05"))
now := time.Now() fmt.Println("存入时间戳:", now.Unix())
fmt.Println("存入毫秒:", now.UnixMilli()) }
|
9. 定时器与休眠
9.1 time.Sleep:暂停执行
最简单的"等待"操作:
1 2 3 4 5 6 7 8 9 10 11 12
| package main
import ( "fmt" "time" )
func main() { fmt.Println("开始等待...") time.Sleep(3 * time.Second) fmt.Println("3秒过去了!") }
|
time.Sleep 会阻塞当前 goroutine,参数是 time.Duration。
1 2 3
| time.Sleep(500 * time.Millisecond) time.Sleep(2 * time.Minute) time.Sleep(1 * time.Hour)
|
9.2 time.Tick:周期性触发(定时器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main
import ( "fmt" "time" )
func main() { ticker := time.Tick(1 * time.Second)
count := 0 for t := range ticker { count++ fmt.Printf("[%s] 第 %d 次触发\n", t.Format("15:04:05"), count) if count >= 5 { break } }
fmt.Println("结束") }
|
输出:
1 2 3 4 5 6
| [10:30:01] 第 1 次触发 [10:30:02] 第 2 次触发 [10:30:03] 第 3 次触发 [10:30:04] 第 4 次触发 [10:30:05] 第 5 次触发 结束
|
注意:time.Tick 返回的 channel 永远不会关闭,不能用于需要停止的场景。如果需要停止定时器,用 time.NewTicker(下面讲)。
9.3 time.After:一次性延迟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import ( "fmt" "time" )
func main() { fmt.Println("等待 2 秒...")
<-time.After(2 * time.Second)
fmt.Println("2 秒到了!") }
|
time.After 常用于超时控制(后面会详细讲)。
9.4 time.NewTicker:可控制的定时器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "time" )
func main() { ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop()
count := 0 for { <-ticker.C count++ fmt.Printf("第 %d 次\n", count) if count >= 5 { break } } }
|
NewTicker 和 Tick 的区别:
|
time.Tick |
time.NewTicker |
| 能否停止 |
不能(channel 永不关闭) |
能(调用 ticker.Stop()) |
| 适用场景 |
简单一次性场景 |
需要控制生命周期的场景 |
| 内存泄漏风险 |
有(不用时仍占资源) |
无(Stop 后释放) |
经验法则:在生产代码中,永远用 NewTicker,不要用 Tick。
9.5 time.NewTimer:单次延迟触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "fmt" "time" )
func main() { timer := time.NewTimer(2 * time.Second) defer timer.Stop()
fmt.Println("等待 2 秒...")
<-timer.C fmt.Println("时间到!") }
|
Timer 和 Ticker 的区别:
Timer:触发一次就结束
Ticker:周期性触发
9.6 实战:倒计时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "time" )
func countdown(seconds int) { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
for i := seconds; i > 0; i-- { fmt.Printf("\r倒计时: %d 秒 ", i) <-ticker.C } fmt.Println("\r倒计时结束! ") }
func main() { fmt.Println("准备发射火箭!") countdown(5) fmt.Println("火箭已发射!") }
|
10. 超时控制
10.1 用 select + time.After 实现超时
这是最经典的 Go 超时模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package main
import ( "fmt" "time" )
func slowTask() string { time.Sleep(3 * time.Second) return "任务完成" }
func main() { resultCh := make(chan string, 1)
go func() { resultCh <- slowTask() }()
select { case result := <-resultCh: fmt.Println("收到结果:", result) case <-time.After(2 * time.Second): fmt.Println("超时!任务耗时太长") } }
|
输出:
这里 slowTask 需要 3 秒,但我们只等 2 秒就超时了。
10.2 实战:带超时的 HTTP 请求模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package main
import ( "fmt" "time" )
func fetchURL(url string) string { delay := time.Duration(1+time.Now().UnixNano()%3) * time.Second fmt.Printf("模拟请求 %s,预计耗时 %v\n", url, delay) time.Sleep(delay) return fmt.Sprintf("来自 %s 的响应", url) }
func fetchWithTimeout(url string, timeout time.Duration) (string, error) { resultCh := make(chan string, 1)
go func() { resultCh <- fetchURL(url) }()
select { case result := <-resultCh: return result, nil case <-time.After(timeout): return "", fmt.Errorf("请求 %s 超时(超过 %v)", url, timeout) } }
func main() { urls := []string{ "https://api.example.com/users", "https://api.example.com/orders", "https://api.example.com/products", }
for _, url := range urls { result, err := fetchWithTimeout(url, 2*time.Second) if err != nil { fmt.Println("错误:", err) } else { fmt.Println("成功:", result) } fmt.Println() } }
|
11. 综合示例:简易定时提醒工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package main
import ( "fmt" "time" )
type Reminder struct { Message string At time.Time }
func main() { now := time.Now()
reminders := []Reminder{ { Message: "该喝水了!", At: now.Add(5 * time.Second), }, { Message: "站起来活动一下!", At: now.Add(10 * time.Second), }, { Message: "休息一下眼睛!", At: now.Add(15 * time.Second), }, }
fmt.Println("定时提醒已设置:") for _, r := range reminders { fmt.Printf(" %s -> %s\n", r.At.Format("15:04:05"), r.Message) } fmt.Println()
for _, r := range reminders { wait := time.Until(r.At) if wait > 0 { time.Sleep(wait) } fmt.Printf("[%s] %s\n", time.Now().Format("15:04:05"), r.Message) }
fmt.Println("所有提醒完毕!") }
|
输出:
1 2 3 4 5 6 7 8 9
| 定时提醒已设置: 10:30:50 -> 该喝水了! 10:30:55 -> 站起来活动一下! 10:31:00 -> 休息一下眼睛!
[10:30:50] 该喝水了! [10:30:55] 站起来活动一下! [10:31:00] 休息一下眼睛! 所有提醒完毕!
|
12. 常见坑总结
12.1 格式化参考时间写错
最常见的错误:把参考时间写成自己习惯的格式,而不是 Go 的固定参考时间。
1 2 3 4 5 6
| now.Format("2024-01-15 10:30:45") now.Format("YYYY-MM-DD HH:mm:ss")
now.Format("2006-01-02 15:04:05")
|
12.2 time.Parse 不理解时区
1 2 3 4 5 6 7 8
| t, _ := time.Parse("2006-01-02 15:04:05", "2024-03-15 14:30:00") fmt.Println(t.Location())
loc, _ := time.LoadLocation("Asia/Shanghai") t, _ = time.ParseInLocation("2006-01-02 15:04:05", "2024-03-15 14:30:00", loc) fmt.Println(t.Location())
|
12.3 用 == 比较时间
1 2 3 4 5 6 7 8
| t1 := time.Now() t2 := time.Now()
if t1 == t2 { ... }
if t1.Equal(t2) { ... }
|
12.4 time.Tick 导致 goroutine 泄漏
1 2 3 4 5 6 7 8 9 10 11 12
| func doSomething() { tick := time.Tick(1 * time.Second) }
func doSomething() { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() }
|
12.5 加减天数用 Add 而不是 AddDate
1 2 3 4 5
| t.Add(24 * time.Hour)
t.AddDate(0, 0, 1)
|
严格来说 Add(24 * time.Hour) 在大多数情况下没问题,但 AddDate 语义更明确,处理边界情况更安全。
12.6 忘记 timer.Stop() 或 ticker.Stop()
1 2 3 4 5 6 7 8 9 10 11 12 13
| for { ticker := time.NewTicker(1 * time.Second) }
for { ticker := time.NewTicker(1 * time.Second) ticker.Stop() }
|
12.7 混淆 time.After 和 time.NewTimer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| for { select { case <-ch: case <-time.After(5 * time.Second): } }
timer := time.NewTimer(5 * time.Second) for { timer.Reset(5 * time.Second) select { case <-ch: case <-timer.C: } }
|
12.8 time.Parse 格式字符串和输入字符串不匹配
1 2 3 4 5 6
| t, err := time.Parse("2006-01-02 15:04:05", "2024-03-15 14:30")
t, err := time.Parse("2006-01-02 15:04", "2024-03-15 14:30")
|
13. time 包常用函数速查表
| 函数 |
用途 |
示例 |
time.Now() |
获取当前时间 |
now := time.Now() |
time.Parse(layout, str) |
解析时间字符串(UTC) |
t, _ := time.Parse("2006-01-02", "2024-03-15") |
time.ParseInLocation(layout, str, loc) |
解析时间字符串(指定时区) |
t, _ := time.ParseInLocation("...", str, loc) |
time.Date(y,m,d,h,m,s,ns,loc) |
构造时间 |
t := time.Date(2024,3,15,0,0,0,0,time.UTC) |
time.Unix(sec, nsec) |
时间戳 → 时间 |
t := time.Unix(1705285845, 0) |
time.Sleep(d) |
暂停 |
time.Sleep(time.Second) |
time.Tick(d) |
周期触发(不推荐) |
for t := range time.Tick(time.Second) |
time.After(d) |
延迟后触发 |
<-time.After(time.Second) |
time.Since(t) |
从 t 到现在过了多久 |
time.Since(start) |
time.Until(t) |
从现在到 t 还有多久 |
time.Until(deadline) |
time.LoadLocation(name) |
加载时区 |
loc, _ := time.LoadLocation("Asia/Shanghai") |
14. 本课练习
练习 1:格式化与解析
要求:
- 获取当前时间,格式化为
"2006年01月02日 15:04:05" 的中文格式
- 解析字符串
"2024/03/15",打印出是星期几
- 将当前时间转换为时间戳(秒级),再转换回来验证
练习 2:时间计算
要求:
- 计算从你出生到现在过了多少天
- 给定一个日期,计算下个月的同一天是几号(注意月末边界)
- 计算今天距离本年底还有多少天
练习 3:简易计时器
要求:
- 写一个函数,接收秒数,显示倒计时
- 倒计时结束时播放提示(打印
"时间到!")
- 用
time.NewTicker 实现
练习 4:超时控制
要求:
- 模拟一个随机耗时的操作(随机 1~5 秒)
- 设置 3 秒超时
- 如果在超时内完成,打印结果和耗时
- 如果超时,打印超时信息
练习 5:日志时间解析
要求:
- 写一个函数解析日志文件中的时间戳(格式:
[2024-01-15 10:30:45])
- 找出最早的和最晚的日志条目
- 计算日志跨越了多长时间
15. 自测题
15.1 概念题
time.Now() 返回什么类型的值?
- Go 的时间格式化参考时间是什么?为什么选这个时间?
time.Parse 和 time.ParseInLocation 有什么区别?
time.Duration 本质是什么类型?
time.Add 和 time.AddDate 各自适合什么场景?
time.Since 和 time.Until 分别等价于什么?
time.Tick 和 time.NewTicker 有什么区别?为什么推荐用后者?
- 比较两个时间是否相等应该用
== 还是 Equal?
- 怎么把
time.Time 转换成 Unix 时间戳?
time.After 常用于什么场景?
15.2 代码阅读题
下面的格式化输出是什么?
1 2 3 4 5 6
| t := time.Date(2024, 3, 15, 14, 5, 3, 0, time.UTC)
fmt.Println(t.Format("2006-01-02")) fmt.Println(t.Format("15:04:05")) fmt.Println(t.Format("2006/1/2 3:4:5 PM")) fmt.Println(t.Format("Monday"))
|
点击查看答案
1 2 3 4
| 2024-03-15 14:05:03 2024/3/15 2:5:3 PM Friday
|
解释:
"2006-01-02":年-月-日,月和日用两位补零
"15:04:05":24小时制的时:分:秒
"2006/1/2 3:4:5 PM":不补零的日期 + 12小时制 + AM/PM
"Monday":星期几的英文名(2024-03-15 是星期五)
16. 本课总结
这一课你学到了 Go 中时间处理的完整知识。
你现在应该已经理解:
| 场景 |
推荐方式 |
| 获取当前时间 |
time.Now() |
| 格式化输出 |
t.Format("2006-01-02 15:04:05") |
| 解析时间字符串 |
time.Parse 或 time.ParseInLocation |
| 时间加减 |
Add(duration) 或 AddDate(y, m, d) |
| 计算时间差 |
t1.Sub(t2) 或 time.Since(t) |
| 时间比较 |
Before、After、Equal(不用 ==) |
| 时区转换 |
t.In(loc) |
| 时间戳转换 |
Unix() / time.Unix(ts, 0) |
| 暂停执行 |
time.Sleep |
| 周期执行 |
time.NewTicker(记得 Stop) |
| 超时控制 |
select + time.After |
最重要的三件事:
- 格式化用
"2006-01-02 15:04:05",不要写成其他格式
- 解析用户输入的时间用
ParseInLocation,不用 Parse
- 比较时间用
Equal,不用 ==
17. 下一课预告
下一课我们学习字符串处理与常用工具库。
会重点讲:
strings 包:拼接、拆分、查找、替换
strconv 包:字符串与数字互转
unicode 包:字符判断
- 常见文本清洗和转换场景
学完下一课,你就能高效处理各种文本数据了。