Cách khắc phục lỗi 'can't compare offset-naive and offset-aware datetimes' trong Python

beginner🐍 Python2026-06-09| Python 3.x (bao gồm 3.12+), Django, Flask, FastAPI và các tập lệnh xử lý dữ liệu trên Linux, Windows hoặc macOS.

Error Message

TypeError: can't compare offset-naive and offset-aware datetimes
#python#datetime#timezone#django#backend

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.tzinfo không phải là None cho cả hai biến.
  • Kiểm tra toán học: Thử thực hiện dt1 - dt2. Nếu nó trả về một timedelta mà 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ụng datetime.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:00 hoặc Z và 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ư 1715349600 và 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ử.

Related Error Notes