Gói time của Go thiết kế tốt hơn Date cũ của Java, nhưng vẫn có vài chỗ đặc trưng cần biết.

Duration là một kiểu

d := 90 * time.Second
  90*time.Second = 1m30s | .Minutes()=1.5 | int64=90000000000

Durationint64 đếm nano giây, có kiểu riêng nên đơn vị không lẫn được. In ra tự động thành dạng người đọc được.

Cái bẫy hay gặp:

n := 30
d := n * time.Second        // LỖI BIÊN DỊCH
d := time.Duration(n) * time.Second   // đúng

30 * time.Second chạy được vì 30 là hằng không kiểu (bài 4), còn n là biến int — và bài 5 đã nói Go không ép kiểu ngầm.

Layout định dạng: 2006-01-02

t.Format("02/01/2006 15:04")
  13/08/2026 14:30

Go không dùng yyyy-MM-dd. Nó dùng một thời điểm tham chiếu cụ thể:

  Mon Jan 2 15:04:05 MST 2006
   1   2   3  4  5  6      7

Bạn viết thời điểm đó theo định dạng mong muốn, và Go suy ra quy tắc. Ban đầu rất khó nhớ, nhưng đổi lại là không cần thuộc bảng mã ký tự.

Dùng hằng có sẵn khi được: time.RFC3339 cho API, time.DateOnlytime.TimeOnly từ Go 1.20.

So sánh Time bằng == thì sai

  t.Equal(t.In(vn)) = true      <- cùng thời điểm
  t == t.In(vn)     = false     <<< khác

== so cả thời điểm, múi giờ, và phần monotonic clock. Hai giá trị cùng chỉ một khoảnh khắc nhưng khác múi giờ sẽ không bằng nhau.

Luôn dùng Equal, Before, After. Đây đúng bài học ở bài 62 sê-ri Java với ZonedDateTime.

Cùng lý do, Time không nên dùng làm khoá map.

Cộng ngày khác cộng 24 giờ

  gốc            : 28/03 12:00 GMT
  AddDate(0,0,1) : 29/03 12:00 BST
  Add(24h)       : 29/03 13:00 BST   <<< lệch 1 giờ

Ngày 29/03 ở London chỉ có 23 giờ vì đổi sang giờ mùa hè.

AddDate làm việc theo lịch — "cùng giờ ngày mai". Add(24*time.Hour) làm việc theo thời gian tuyệt đối — đúng 86400 giây.

Chọn theo ý định: nhắc nhở "mỗi ngày 8 giờ sáng" là AddDate; token "hết hạn sau 24 giờ" là Add.

Việt Nam không có giờ mùa hè nên nhiều người Việt chưa từng gặp — cho tới khi làm sản phẩm cho thị trường châu Âu hoặc Mỹ.

Múi giờ

vn, err := time.LoadLocation("Asia/Ho_Chi_Minh")

Luôn dùng tên vùng IANA, không dùng offset cố định. time.FixedZone("GMT+7", 7*3600) tình cờ đúng cho Việt Nam nhưng sai về nguyên tắc.

Một cái bẫy triển khai: LoadLocation đọc cơ sở dữ liệu múi giờ của hệ điều hành. Ảnh Docker tối giản không có tzdata thì nó trả lỗi. Hai cách chữa: cài tzdata vào ảnh, hoặc nhúng vào binary:

import _ "time/tzdata"

Dòng đó thêm khoảng 450KB vào binary và bỏ hẳn phụ thuộc vào hệ điều hành — rất đáng cho ảnh scratch.

Và nhớ đặt TZ trong container, đúng bài học ở bài 97 sê-ri Java: mặc định là UTC, nên cron chạy 6 giờ sáng giờ Việt Nam sẽ thấy máy chủ đang là 23 giờ hôm trước.

Monotonic clock

  time.Since(t0) = 21ms
  Now() có phần m=+...

time.Now() mang theo hai đồng hồ: wall clock (có thể nhảy khi NTP chỉnh) và monotonic (chỉ tăng).

Sub, Since, Until dùng monotonic, nên đo khoảng thời gian không bao giờ ra số âm vì đồng hồ hệ thống bị chỉnh. Đây là thứ Java phải dùng System.nanoTime() riêng mới có.

Nhưng phần monotonic mất khi bạn Round, Truncate, In, hoặc serialize. Nên đừng đo khoảng thời gian bằng Time đã đi qua JSON.

Ba cái bẫy Timer

Quên Stop():

tk := time.NewTicker(time.Second)
defer tk.Stop()

Ticker không dừng là goroutine và timer rò rỉ. Timer cũng nên Stop nếu bạn thoát sớm.

time.After trong vòng lặp — bài 33 đã nói: mỗi vòng tạo một timer mới. Từ Go 1.23, GC dọn được timer chưa hết hạn nên nhẹ đi nhiều, nhưng thói quen vẫn nên giữ.

Ticker không bù nhịp bị lỡ. Nếu xử lý lâu hơn chu kỳ, nhịp bị bỏ qua chứ không dồn lại. Thường đó là hành vi bạn muốn — nhưng phải biết.

Test mã phụ thuộc thời gian

Đừng gọi time.Now() rải rác. Tiêm một hàm:

type Dich struct {
	now func() time.Time
}

func New() *Dich { return &Dich{now: time.Now} }
// trong test: d.now = func() time.Time { return coDinh }

Đây là tương đương Clock của Java ở bài 61, và nó làm logic hết hạn test được mà không phải chờ.

Thử ba mươi giây

t := time.Now()
j, _ := json.Marshal(t)
var t2 time.Time
json.Unmarshal(j, &t2)
fmt.Println(t == t2, t.Equal(t2))

In ra false true. Qua JSON, phần monotonic mất và múi giờ có thể đổi — nhưng thời điểm vẫn đúng.

Ba mươi giây đó là lý do Equal tồn tại.

Ngày mai: viết test kiểu Go — bảng test và những gì thư viện chuẩn đã đủ.