Khi code Python làm việc với giờ giấc ở nhiều múi giờ, cách đúng là luôn dùng datetime “aware” — tức datetime có gắn tzinfo — với zoneinfo.ZoneInfo, lưu trữ ở UTC và chỉ chuyển sang giờ địa phương khi hiển thị. Cách này áp dụng khi bạn lưu timestamp vào database, ghi log, đặt lịch hẹn, hay so sánh thời gian giữa các múi giờ.
Vấn đề
Hàm datetime.now() không tham số trả về datetime “naive” — không gắn múi giờ nào cả. Một giờ naive trông giống hệt giờ thật, nhưng Python không biết nó thuộc múi giờ nào, nên bạn không thể chuyển đổi nó sang múi giờ khác một cách đáng tin cậy. Tệ hơn, so sánh datetime naive với datetime aware (ví dụ timestamp từ API trả về kèm +07:00) sẽ ném lỗi ngay:
from datetime import datetimedeadline = datetime(2026, 11, 4, 16, 0) # naive, không rõ múi giờnow = datetime.now() # cũng naive# Timestamp từ API, có kèm múi giờfrom_api = datetime.fromisoformat("2026-11-04T16:00:00+07:00")print(now > from_api) # TypeError: can't compare offset-naive and offset-aware datetimes
Một lỗi khác cũng hay gặp là cộng trừ tay + timedelta(hours=7) để “đổi” múi giờ. Cách này bỏ qua giờ mùa hè (DST) và vỡ khi quy tắc múi giờ thay đổi.
Giải pháp
Module zoneinfo có sẵn trong thư viện chuẩn từ Python 3.9, không cần cài thêm gói nào. Quy tắc chỉ có hai dòng: luôn tạo thời điểm ở UTC, và chỉ dùng astimezone() khi cần hiển thị theo giờ địa phương:
from datetime import datetime, timezonefrom zoneinfo import ZoneInfovn = ZoneInfo("Asia/Ho_Chi_Minh")# 1. Lấy "bây giờ" ở dạng aware, chuẩn UTCnow_utc = datetime.now(timezone.utc)# 2. Chuyển sang giờ Việt Nam chỉ khi hiển thịnow_vn = now_utc.astimezone(vn)print(now_utc.isoformat()) # 2026-11-04T08:57:31+00:00print(now_vn.isoformat()) # 2026-11-04T15:57:31+07:00# 3. Chuỗi ISO có kèm múi giờ: parse rồi đổi bằng astimezone()dt = datetime.fromisoformat("2026-11-04T16:00:00+07:00")print(dt.astimezone(timezone.utc).isoformat()) # 2026-11-04T09:00:00+00:00# 4. Giờ hẹn do người dùng nhập (giờ tường): gắn tzinfo lúc tạomeeting = datetime(2026, 11, 5, 9, 0, tzinfo=vn)print(meeting.astimezone(timezone.utc).isoformat()) # 2026-11-05T02:00:00+00:00
Phân biệt hai thao tác dễ nhầm: replace(tzinfo=...) chỉ gắn nhãn múi giờ cho một giờ tường đã biết, còn astimezone() mới là chuyển đổi thật sự giữa các múi giờ. Muốn đổi từ giờ Việt Nam sang UTC thì dùng astimezone(timezone.utc), đừng bao giờ trừ tay 7 tiếng.
Một lưu ý về môi trường: zoneinfo đọc cơ sở dữ liệu múi giờ của hệ điều hành. Nếu gặp lỗi ZoneInfoNotFoundError trên Windows hoặc container tối giản, chỉ cần cài thêm gói tzdata bằng pip là xong, code không cần sửa gì.
Áp dụng vào dự án của bạn
Trong dự án thật, hãy thống nhất một quy ước duy nhất: database và API luôn lưu, trao đổi thời gian ở UTC, dưới dạng aware datetime hoặc chuỗi ISO 8601 có kèm múi giờ. Chỉ ở tầng hiển thị — template, thông báo, email — mới chuyển sang múi giờ của người dùng bằng astimezone(). Nhờ vậy, log, lịch hẹn và job chạy định kỳ không bao giờ lệch chỉ vì server đặt ở một múi giờ khác với người dùng. Khi viết test, hãy thử cùng một đoạn code với hai múi giờ khác nhau (ví dụ Asia/Ho_Chi_Minh và UTC) để chắc chắn không còn chỗ nào cộng trừ giờ bằng tay.
Bài viết liên quan
- Python: thay os.path bằng pathlib — đọc, ghi và duyệt file gọn hơn trong 5 phút
- Python: viết context manager bằng @contextmanager — tài nguyên luôn được đóng, khỏi viết class dài
- Python: thay chuỗi if/elif dài bằng match statement — pattern matching trong 5 phút
Câu hỏi thường gặp
Datetime naive và aware khác nhau thế nào?
Naive là datetime không có tzinfo, Python không biết nó thuộc múi giờ nào. Aware là datetime có tzinfo, có thể chuyển đổi và so sánh chính xác giữa các múi giờ.
Nên lưu thời gian vào database ở dạng nào?
Lưu ở UTC, dạng aware datetime hoặc chuỗi ISO 8601 có kèm múi giờ. Khi hiển thị, chuyển sang múi giờ người dùng bằng astimezone().
Giờ mùa hè (DST) có làm zoneinfo tính sai không?
Không, nếu bạn chuyển đổi bằng astimezone(). zoneinfo tự áp dụng đúng độ lệch theo từng thời điểm. Chỉ khi bạn cộng trừ giờ bằng tay thì DST mới gây lệch.
Gặp lỗi ZoneInfoNotFoundError thì sửa thế nào?
Đó là dấu hiệu máy thiếu cơ sở dữ liệu múi giờ. Cài gói tzdata bằng pip, zoneinfo sẽ tự dùng nó mà không cần sửa code.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Python docs — zoneinfo: https://docs.python.org/3/library/zoneinfo.html
- Python docs — datetime: https://docs.python.org/3/library/datetime.html
- PEP 615 — Support for the IANA Time Zone Database in the Standard Library: https://peps.python.org/pep-0615/
