Go Zero to Hero - Bài 50: Làm việc với thời gian: Package time
Trong phát triển hệ thống backend, việc quản lý thời gian, múi giờ và khoảng thời gian (duration) đòi hỏi sự chính xác tuyệt đối để tránh sai sót giao dịch . Golang cung cấp package chuẩn time với các cơ chế xử lý cực kỳ chặt chẽ .
1. Lấy thời gian hiện tại và bài toán Timezone
Trong Go, đối tượng cốt lõi chứa thông tin thời gian là time.Time . Nó không chỉ lưu con số tích tắc, mà lưu luôn cả Múi giờ (Location) .
package main
import (
"fmt"
"time"
)
func main() {
// 1. Lấy giờ hệ thống (Local Time) - Phụ thuộc vào máy chủ chạy code
now := time.Now()
fmt.Println("Giờ Local:", now)
// 2. Lấy giờ UTC (Chuẩn thực chiến Backend)
// Khi lưu xuống Database (như PostgreSQL), BẮT BUỘC phải đưa về UTC
// để tránh loạn múi giờ khi server scale qua nhiều quốc gia.
utcNow := now.UTC()
fmt.Println("Giờ UTC:", utcNow)
}
2. "Cú lừa" Format Time: Tạm biệt YYYY-MM-DD
Nếu bạn muốn định dạng ngày tháng thành chuỗi, các ngôn ngữ khác sẽ cho bạn dùng YYYY-MM-DD HH:mm:ss. Nhưng Go thì không! Tác giả của Go (Rob Pike) cho rằng chuỗi YYYY-MM quá trừu tượng . Thay vào đó, Go yêu cầu bạn dùng một thời điểm mốc cố định để làm format: Ngày 2, Tháng 1, Năm 2006, 15 giờ 04 phút 05 giây .
Mẹo ghi nhớ siêu cấp: 1 - 2 - 3 - 4 - 5 - 6 - 7
- Tháng 1 (
01hoặcJan) - Ngày 2 (
02) - Giờ 3 chiều (
15- dùng định dạng 24h) - Phút 4 (
04) - Giây 5 (
05) - Năm 2006 (
2006) - Múi giờ 7 (
-0700hoặcMST)
| Bạn muốn định dạng... | Cú pháp trong Go (Format String) |
|---|---|
| Ngày/Tháng/Năm | 02/01/2006 |
| Năm-Tháng-Ngày Giờ:Phút | 2006-01-02 15:04 |
| Chuẩn ISO 8601 (API) | 2006-01-02T15:04:05Z07:00 |
| AM/PM | 03:04 PM |
func main() {
now := time.Now()
// Xuất ra chuỗi dùng hàm Format()
dateStr := now.Format("02/01/2006 - 15:04:05")
fmt.Println("Vé xuất lúc:", dateStr)
}
3. Parse chuỗi thành thời gian: Cẩn thận bẫy Location!
Khi Frontend hoặc máy quét vé gửi lên một chuỗi "2026-08-09 14:30:00", bạn cần chuyển nó thành kiểu time.Time để tính toán . Có 2 hàm để Parse: time.Parse và time.ParseInLocation .
⚠️ Luật sinh tử: Nếu chuỗi thời gian KHÔNG CHỨA thông tin múi giờ (như
+07:00), hàmtime.Parsesẽ tự động coi đó là giờ UTC . Nếu bạn đang ở Việt Nam, vé sẽ bị lệch đi 7 tiếng!
func main() {
rawTime := "2026-08-09 14:30:00"
layout := "2006-01-02 15:04:05"
// ❌ Sai lầm: Parse thông thường sẽ ra giờ UTC
wrongTime, _ := time.Parse(layout, rawTime)
fmt.Println("Parse sai:", wrongTime)
// Kết quả: 2026-08-09 14:30:00 +0000 UTC
// ✅ Chuẩn Backend: Bắt buộc chỉ định rõ Múi giờ
hcmZone, _ := time.LoadLocation("Asia/Ho_Chi_Minh")
correctTime, _ := time.ParseInLocation(layout, rawTime, hcmZone)
fmt.Println("Parse chuẩn:", correctTime)
// Kết quả: 2026-08-09 14:30:00 +0700 +07
}
4. Tính toán khoảng thời gian (Duration)
Khoảng thời gian trong Go được biểu diễn bằng kiểu time.Duration . Bản chất nó là một số nguyên đại diện cho số Nanosecond (1 phần tỷ giây) . Các hằng số có sẵn: time.Second, time.Minute, time.Hour .
Thêm / Bớt thời gian (Add)
Dùng để cấp thời hạn (TTL) cho vé hoặc token .
func main() {
now := time.Now()
// Vé tàu điện có hạn 2 tiếng rưỡi kể từ lúc mua
ttl := (2 * time.Hour) + (30 * time.Minute)
expireTime := now.Add(ttl)
fmt.Println("Vé hết hạn lúc:", expireTime.Format("15:04 02/01/2006"))
// Quay ngược quá khứ: Cộng số âm
yesterday := now.Add(-24 * time.Hour)
_ = yesterday
}
Tính khoảng cách giữa 2 thời điểm (Sub / Since)
Cực kỳ hữu dụng để đo tốc độ chạy của một hàm, hoặc tính toán tiền phạt nán lại trạm quá giờ .
func processPayment() {
// Lưu lại thời điểm bắt đầu chạy hàm
start := time.Now()
// Giả lập hàm tốn 2 giây
time.Sleep(2 * time.Second)
// time.Since(start) tương đương với time.Now().Sub(start)
duration := time.Since(start)
fmt.Printf("⏱ Thời gian xử lý giao dịch: %v\n", duration)
}
5. So sánh thời gian (Before / After / Equal)
Tuyệt đối không dùng toán tử <, >, == để so sánh 2 biến time.Time (trình biên dịch sẽ báo lỗi vì nó là Struct) . Bạn phải dùng các Method tích hợp sẵn .
func checkTicketValidity(expireTime time.Time) {
now := time.Now()
if now.After(expireTime) {
fmt.Println("❌ Thẻ đã hết hạn! Vui lòng nạp thêm tiền.")
} else if now.Before(expireTime) {
fmt.Println("✅ Thẻ hợp lệ, mở cổng!")
} else {
fmt.Println("Thời gian trùng khớp từng Nanosecond (Rất hiếm gặp)")
}
}
6. Giao tiếp ngoại vi với Unix Timestamp (Epoch Time)
Một số hệ thống nhúng (như máy quét) hoặc database (như Redis) không lưu thời gian dạng chuỗi, mà lưu bằng một con số nguyên đại diện cho số giây (hoặc mili-giây) trôi qua kể từ ngày 01/01/1970 00:00:00 UTC . Con số này gọi là Unix Timestamp .
- Lấy số giây hiện tại:
time.Now().Unix() - Lấy số mili-giây:
time.Now().UnixMilli() - Chuyển từ con số ngược lại thành thời gian:
time.Unix(số_giây, số_nano_giây)
func main() {
// 1. Chuyển time.Time thành số nguyên để đẩy vào Kafka/Redis
unixSecs := time.Now().Unix()
fmt.Println("Timestamp (giây):", unixSecs)
// 2. Chuyển con số ngược lại thành time.Time
restoredTime := time.Unix(1754631051, 0)
fmt.Println("Giải mã Timestamp:", restoredTime.Format("02/01/2006 15:04:05"))
}
Tổng kết
Xử lý time trong Go chỉ cần ghi nhớ 3 quy tắc thép :
- Format lỳ lạ: Thuộc nằm lòng con số
2006-01-02 15:04:05. - Luôn ghim Location: Khi Parse chuỗi từ Frontend gửi lên, hãy dùng
ParseInLocationđể tránh vé bị lệch sang múi giờ UTC . - Lưu Database: Nên đưa mọi thứ về
.UTC()trước khi Insert vào CSDL, sau đó kéo lên và convert về giờ Local ở tầng hiển thị .
All rights reserved