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

Date và Datetime trong Odoo 19 Odoo lưu UTC hiện theo giờ người dùng: quy tắc vàng, Datetime thì cơ sở dữ liệu lưu UTC naive còn giao diện hiện theo tz người dùng, Date thì chỉ ngày không giờ không múi giờ; trong code luôn làm việc với UTC, fields.Datetime.now trả thời điểm hiện tại theo UTC naive, fields.Date.today trả ngày hôm nay không tz, fields.Date.context_today self trả ngày theo tz người dùng, fields.Datetime.context_timestamp self dt đổi UTC sang giờ người dùng để hiển thị; bẫy lệch giờ, sai là datetime.now giờ máy chủ sai nếu server khác tz còn đúng là fields.Datetime.now luôn UTC nhất quán, sai là so sánh Datetime với giờ địa phương thì lệch còn đúng là so sánh trong UTC chỉ đổi tz khi hiển thị

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):

Múi giờ chạy thật trên database blog19 VN bằng UTC cộng 7: Datetime.now luôn trả UTC naive không kèm tz, fields.Datetime.now ra 2026-09-17 10:18:43 đây là giờ UTC cơ sở dữ liệu lưu đúng chuỗi này; cùng thời điểm đó đổi sang giờ Việt Nam để hiển thị, context_timestamp với user_tz Asia Ho Chi Minh và now ra 2026-09-17 17:18:43 cộng 0700, 10:18 UTC bằng 17:18 giờ VN, chênh lệch VN bằng UTC cộng 7 giờ; Date thì khác chỉ ngày không giờ không múi giờ, fields.Date.today ra 2026-09-17 kiểu date, the.ngay_cap gán today lưu 2026-09-17 không đổi theo tz; cơ sở dữ liệu lưu UTC hoặc ngày trần mỗi người xem thấy giờ theo tz của mình

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 ý

  1. 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).
  2. Luôn dùng fields.Datetime.now() (không phải datetime.now() của Python); đổi tz chỉ khi hiển thị bằng context_timestamp.
  3. Date không có giờ/tz; cẩn thận context_today (theo người dùng) khác today (UTC) quanh nửa đêm — dùng context_today cho "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).