Vấn đề
Ít có điều gì gây khó chịu hơn việc một tập lệnh bị lỗi giữa chừng do sự không đồng nhất về múi giờ. Python kích hoạt lỗi này khi bạn cố gắng so sánh hai đối tượng datetime không "cùng ngôn ngữ". Một cái có đính kèm múi giờ (aware), và cái còn lại thì không (naive). Python coi chúng là các kiểu dữ liệu khác nhau và từ chối dự đoán xem chúng liên quan như thế nào.
Bạn có thể sẽ gặp phải lỗi này khi lấy dấu thời gian 2024-05-10 14:00:00+00:00 từ cơ sở dữ liệu như PostgreSQL và so sánh nó với datetime.now(). Vì giá trị từ cơ sở dữ liệu bao gồm độ lệch UTC còn lệnh cục bộ thì không, nên việc so sánh sẽ thất bại.
import datetime
import pytz
# Datetime aware: Nó biết mình thuộc múi giờ UTC
aware_dt = datetime.datetime.now(pytz.utc)
# Datetime naive: Nó không có ngữ cảnh múi giờ
naive_dt = datetime.datetime.now()
# Việc so sánh này sẽ làm hỏng tập lệnh
if aware_dt > naive_dt:
print("Dòng này sẽ không bao giờ được thực thi")
Cách khắc phục
Bước 1: Kiểm tra thuộc tính tzinfo
Bắt đầu bằng cách xác định biến nào là nguyên nhân. Bạn có thể kiểm tra thuộc tính tzinfo của bất kỳ đối tượng datetime nào. Nếu nó trả về None, đối tượng đó là naive và cần được chuyển đổi.
print(f"Aware TZ: {aware_dt.tzinfo}") # Kết quả: UTC
print(f"Naive TZ: {naive_dt.tzinfo}") # Kết quả: None
Bước 2: Chuẩn hóa theo UTC
Trộn lẫn các múi giờ là nguồn cơn của các lỗi phần mềm. Tiêu chuẩn công nghiệp là giữ cho tất cả logic nội bộ ở dạng UTC và chỉ chuyển đổi sang giờ địa phương (như 'Asia/Ho_Chi_Minh') khi hiển thị dữ liệu cho người dùng.
Nếu bạn đang sử dụng Python 3.9 trở lên, hãy sử dụng timezone.utc có sẵn. Tránh dùng pytz trừ khi bạn đang bảo trì mã nguồn cũ, vì zoneinfo hiện là thư viện được ưu tiên.
from datetime import datetime, timezone
# Cách khắc phục A: Tạo đối tượng aware ngay từ đầu
now_aware = datetime.now(timezone.utc)
# Cách khắc phục B: Thêm thông tin múi giờ vào đối tượng naive (ví dụ: từ CSV hoặc API)
# Giả sử chúng ta có một ngày naive: ngày 1 tháng 5 năm 2024, lúc 10:30 sáng
my_naive_date = datetime(2024, 5, 1, 10, 30, 0)
my_aware_date = my_naive_date.replace(tzinfo=timezone.utc)
# Việc so sánh bây giờ hoạt động hoàn hảo
if now_aware > my_aware_date:
print("Thành công: Cả hai đối tượng hiện đã là aware.")
Bước 3: Khắc phục dành cho lập trình viên Django
Django thường đưa ra lỗi này nếu USE_TZ = True được bật trong file settings.py của bạn. Cài đặt này buộc cơ sở dữ liệu lưu trữ mọi thứ theo UTC. Nếu bạn sử dụng datetime.now() tiêu chuẩn của Python, bạn sẽ tạo ra một đối tượng naive không khớp với các trường trong cơ sở dữ liệu của mình.
Cách làm sai:
from datetime import datetime
# Đây là đối tượng naive và sẽ thất bại khi so sánh với các trường PostgreSQL/MySQL
expired = MyModel.objects.filter(created_at__lt=datetime.now())
Cách làm đúng:
from django.utils import timezone
# Tiện ích của Django tự động tạo một đối tượng aware theo UTC
expired = MyModel.objects.filter(created_at__lt=timezone.now())
Xác minh nhanh
Trước khi triển khai bản sửa lỗi của bạn, hãy thực hiện ba bước kiểm tra sau:
- Kiểm tra None: Đảm bảo
dt.tzinfokhông phải làNonecho cả hai biến. - Kiểm tra toán học: Thử thực hiện
dt1 - dt2. Nếu nó trả về mộttimedeltamà không có TypeError, bạn đã an toàn. - Kiểm tra định dạng: In các đối tượng ra. Các đối tượng aware có dạng như
2024-05-10 10:00:00+00:00, trong khi các đối tượng naive sẽ dừng lại sau phần giây.
Mẹo chuyên nghiệp & Các bẫy thường gặp
- Ngừng sử dụng
utcnow(): Kể từ Python 3.12,datetime.utcnow()đã chính thức bị loại bỏ (deprecated). Nó tạo ra một đối tượng naive đại diện cho UTC, điều này gây nhầm lẫn và dẫn đến chính lỗi mà chúng ta đang khắc phục. Hãy sử dụngdatetime.now(timezone.utc)để thay thế. - Dữ liệu API bên ngoài: Khi phân tích cú pháp ngày từ JSON (thường là chuỗi), hãy sử dụng
dateutil.parser.parse(). Nó đủ thông minh để phát hiện hậu tố+00:00hoặcZvà tạo đối tượng aware cho bạn. - Gỡ lỗi trực quan: Nếu bạn gặp khó khăn khi nhìn vào một Unix timestamp như
1715349600và không thể biết nó có độ lệch chính xác hay không, hãy sử dụng một công cụ như Timestamp Converter của ToolCraft. Nó giúp xác minh rằng dấu thời gian số nguyên của bạn thực sự khớp với thời gian UTC mà con người có thể đọc được trước khi bạn viết các trường hợp kiểm thử.

