時間處理的核心規則是:時間點使用 time.Time,時間長度使用 time.Duration。本章將說明 now、parse、format、duration、timer 與 ticker 的基本用法。

time.Time 表示時間點

time.Time 的核心意義是一個具體時間點。它可以代表現在、某個解析出來的時間、資料庫中的時間戳,或 API 回傳的建立時間。

1now := time.Now()
2fmt.Println(now)

time.Now() 會取得目前時間。它很方便,但也會讓測試變得不穩定;需要可測試的邏輯時,通常會把時間來源包成參數或介面。

1func isExpired(now time.Time, deadline time.Time) bool {
2    return now.After(deadline)
3}

這個函式不自己呼叫 time.Now(),而是由呼叫端傳入現在時間。測試時就能提供固定時間點,避免測試結果受執行時間影響。

time.Duration 表示時間長度

time.Duration 的核心意義是一段時間長度,不是某個時間點。它常用於 timeout、interval、重試等待與效能測量。

1timeout := 5 * time.Second
2interval := 200 * time.Millisecond
3
4fmt.Println(timeout)
5fmt.Println(interval)

time.Secondtime.Millisecond 這些常數本身是 Duration,可以用乘法組出可讀的時間長度。這比直接寫奈秒數清楚很多。

1// 可讀性差:
2timeout := time.Duration(5000000000)
3
4// 可讀性好:
5timeout := 5 * time.Second

直接寫數字會讓讀者無法立即看出單位。Go 的時間 API 以奈秒為底層單位,但程式碼應該使用明確單位表達意圖。

時間加減要區分時間點與長度

時間運算的核心規則是:時間點加上 duration 會得到另一個時間點,兩個時間點相減會得到 duration。

1start := time.Now()
2deadline := start.Add(30 * time.Second)
3elapsed := time.Since(start)
4
5fmt.Println(deadline)
6fmt.Println(elapsed)

Add 適合計算截止時間,Since 適合計算從某個時間點到現在經過多久。time.Until(deadline) 則可以計算距離某個未來時間還有多久。

1remaining := time.Until(deadline)
2if remaining <= 0 {
3    fmt.Println("expired")
4}

這些 API 讓程式直接表達時間語意,而不是把時間轉成數字後自行相減。

parse 與 format 使用 layout

Go 時間格式化的核心規則是使用固定參考時間 2006-01-02 15:04:05 作為 layout。layout 用這個參考時間的各個部分代表輸出形狀。

1now := time.Date(2026, 4, 22, 9, 30, 0, 0, time.UTC)
2
3fmt.Println(now.Format("2006-01-02"))
4fmt.Println(now.Format("2006-01-02 15:04:05"))

如果你想輸出年月日,就在 layout 裡寫 2006-01-02;如果想輸出 24 小時制時間,就寫 15:04:05。這和許多語言使用 YYYY-MM-DD 的方式不同,是 Go 時間 API 最容易混淆的地方。

解析時間也使用同一套 layout。

1input := "2026-04-22 09:30:00"
2
3createdAt, err := time.Parse("2006-01-02 15:04:05", input)
4if err != nil {
5    return err
6}
7
8fmt.Println(createdAt)

time.Parse 預設會以 UTC 解讀不含時區的時間。若資料代表某個本地時區,應使用 time.ParseInLocation

timer 表示一次性的未來事件

time.Timer 的核心用途是在一段時間後發出一次訊號。它常用於 timeout、延遲執行與 select 控制。

1timer := time.NewTimer(2 * time.Second)
2defer timer.Stop()
3
4select {
5case <-timer.C:
6    fmt.Println("timeout")
7}

timer.C 是一個 channel,到時間後會收到一個時間值。若 timer 可能提前不再使用,應呼叫 Stop 釋放資源。

在很多簡單情境中,time.After 更短。

1select {
2case <-time.After(2 * time.Second):
3    fmt.Println("timeout")
4}

time.After 適合一次性的簡單 timeout;在高頻迴圈或需要取消、重設的情境中,time.NewTimer 通常比較適合。

ticker 表示週期性事件

time.Ticker 的核心用途是固定間隔發出訊號。它常用於定期清理、輪詢、健康檢查與背景工作。

1ticker := time.NewTicker(1 * time.Second)
2defer ticker.Stop()
3
4for i := 0; i < 3; i++ {
5    <-ticker.C
6    fmt.Println("tick")
7}

ticker.C 每隔指定 duration 會收到一次訊號。只要不再使用 ticker,就應呼叫 Stop,避免背景資源持續運作。

週期性工作要有明確的退出條件。實務上常搭配 context.Context

 1func run(ctx context.Context) {
 2    ticker := time.NewTicker(1 * time.Second)
 3    defer ticker.Stop()
 4
 5    for {
 6        select {
 7        case <-ctx.Done():
 8            return
 9        case <-ticker.C:
10            fmt.Println("work")
11        }
12    }
13}

這段程式每秒執行一次工作,直到 context 被取消。時間控制與生命週期控制分開後,程式會比較容易測試與關閉。

小結

下一章會進入 osio,說明檔案、輸入輸出與 streaming API 的共同抽象。