Lỗi "lệch 7 giờ" là kinh điển với dev Odoo ở Việt Nam. Đơn tạo lúc 5h chiều, mở database lại thấy 10h sáng. Không phải bug — đó là vì Odoo lưu mọi Datetime theo UTC và chỉ đổi sang giờ địa phương khi hiển thị. Hiểu đúng cơ chế này là hết lệch giờ mãi mãi. Bài này mổ xẻ Date/Datetime và múi giờ.
Quy tắc vàng: lưu UTC, hiện theo tz người dùng

Hình 1: Hai nguyên tắc. Datetime lưu UTC (naive — không kèm ký hiệu tz) trong CSDL, giao diện tự đổi sang giờ người dùng. Date chỉ là ngày — không giờ, không múi giờ. Trong code, luôn dùng fields.Datetime.now() (UTC) và chỉ đổi tz khi hiển thị bằng context_timestamp.
Nhìn tận mắt: lệch 7 giờ từ đâu
Đây là output thật trên db blog19 (VN = UTC+7):

Hình 2: Output thật. fields.Datetime.now() trả 2026-09-17 10:18:43 — đây là giờ UTC, và CSDL lưu đúng chuỗi này. Cùng thời điểm đó đổi sang giờ VN qua context_timestamp cho 17:18:43 +0700 — chênh đúng 7 giờ. Đây chính là "lệch 7 giờ": xem CSDL thô thấy 10:18, mở giao diện (giờ VN) thấy 17:18. Không ai sai — chỉ là hai cách nhìn cùng một thời điểm.
Các hàm phải dùng đúng
from odoo import fields
fields.Datetime.now() # UTC hiện tại (naive) — LUÔN dùng cái này
fields.Date.today() # ngày hôm nay (không tz)
fields.Date.context_today(self) # ngày theo tz NGƯỜI DÙNG (có thể khác today!)
fields.Datetime.context_timestamp(self, dt) # đổi UTC → giờ người dùng để HIỂN THỊ
Đừng bao giờ dùng datetime.datetime.now() của Python — nó trả giờ máy chủ, sai nếu server không chạy UTC. Luôn fields.Datetime.now().
Bẫy context_today khác today
Một điểm tinh tế: Date.today() cho ngày theo UTC, còn Date.context_today(self) cho ngày theo tz người dùng — hai cái có thể khác nhau quanh nửa đêm!
Ví dụ lúc 23:30 giờ VN ngày 17/09 = 16:30 UTC ngày 17/09 (cùng ngày). Nhưng lúc 06:30 giờ VN ngày 18/09 = 23:30 UTC ngày 17/09 — today() (UTC) trả 17/09, còn context_today() (VN) trả 18/09. Với trường Date "ngày hôm nay theo người dùng" (như ngày đặt hàng), dùng context_today để không lệch ngày.
Vì sao Date không có tz?
Date biểu diễn một ngày lịch (sinh nhật, hạn thanh toán) — nó không gắn với một thời điểm cụ thể trong ngày, nên không đổi theo múi giờ. 2026-09-17 là 17/09 ở mọi nơi. Đừng cố "đổi tz" cho Date — vô nghĩa và gây lỗi.
So sánh và tính toán
from datetime import timedelta
# Hạn 30 ngày sau (làm việc trong UTC):
han = fields.Datetime.now() + timedelta(days=30)
# So sánh: cả hai đều UTC nên so trực tiếp đúng
if record.deadline < fields.Datetime.now():
record.qua_han = True
Vì mọi Datetime trong Odoo đều UTC, so sánh trực tiếp luôn đúng — miễn đừng trộn với giờ địa phương.
Nhớ ba ý
- Datetime lưu UTC (naive), UI hiện theo tz người dùng — "lệch 7 giờ" chỉ là hai cách nhìn cùng thời điểm (đo thật: UTC 10:18 = VN 17:18).
- Luôn dùng
fields.Datetime.now()(không phảidatetime.now()của Python); đổi tz chỉ khi hiển thị bằngcontext_timestamp. - Date không có giờ/tz; cẩn thận
context_today(theo người dùng) kháctoday(UTC) quanh nửa đêm — dùngcontext_todaycho "ngày của người dùng".
Còn hai kiểu trường quan hệ đặc biệt ít gặp nhưng mạnh. Phần sau đi vào Reference và Many2oneReference — trường trỏ tới bản ghi của model bất kỳ (không cố định một model như Many2one).