Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 28 — Dates, Times, Timezones

Data Modeling and ERD Mastery Bài 28/60

Mở đầu — vì sao bài này quan trọng

Nếu bạn từng thấy một hệ thống đặt vé hiển thị "đơn hàng được tạo lúc 3 giờ sáng" trong khi khách hàng rõ ràng bấm mua lúc 10 giờ tối, hoặc một báo cáo doanh thu cuối tháng bị lệch một ngày khiến kế toán cãi nhau cả buổi, thì bạn đã chạm vào đúng vùng đất chông gai mà bài học này nói tới. Ngày, giờ và múi giờ (date, time, timezone) nghe có vẻ là chuyện nhỏ — ai mà chẳng biết hôm nay là ngày mấy — nhưng trong thiết kế dữ liệu, đây là một trong những nguồn gây lỗi âm thầm và tốn kém nhất.

Lý do là vì thời gian không phải một con số đơn giản. Cùng một "thời điểm" nhưng được nhìn từ Hà Nội, Singapore hay California sẽ ra ba con số khác nhau. Một "ngày sinh" thì không cần giờ, nhưng một "thời điểm thanh toán" thì bắt buộc phải có giờ và phải biết giờ đó tính theo đâu. Là một Business Analyst làm việc với mô hình dữ liệu, bạn là người quyết định mỗi cột thời gian sẽ dùng kiểu dữ liệu nào, lưu cái gì, và diễn giải ra sao. Quyết định sai ở giai đoạn modeling sẽ kéo theo lỗi tận tầng báo cáo, đối soát và pháp lý.

Trong bài này, chúng ta sẽ đi sâu vào ba kiểu dữ liệu thời gian cơ bản, sự khác biệt sống còn giữa lưu "có múi giờ" và "không múi giờ", và cách chọn đúng cho từng nghiệp vụ thực tế ở Việt Nam.

Khái niệm cốt lõi

DATE — chỉ ngày, không giờ, không múi giờ

Kiểu DATE lưu một ngày lịch thuần túy, ví dụ 2026-05-12. Nó không chứa thông tin giờ phút giây, cũng không gắn với múi giờ nào cả. Khi bạn nói "ngày 12 tháng 5", thì dù bạn ở Hà Nội hay Tokyo, ngày 12 tháng 5 vẫn là ngày 12 tháng 5 — nó là một khái niệm độc lập với vị trí địa lý.

Dùng DATE cho những thứ mang bản chất "ngày lịch" thuần: ngày sinh (birthdate), ngày hết hạn hợp đồng, ngày nghỉ lễ, ngày đáo hạn khoản vay, ngày phát hành sản phẩm. Một người sinh ngày 12/05/1990 thì thông tin đó không đổi dù họ đang đứng ở múi giờ nào. Nếu bạn lỡ thêm giờ và múi giờ vào ngày sinh, bạn tự tạo ra rủi ro: một người sinh lúc nửa đêm có thể bị hệ thống "đổi ngày sinh" khi quy đổi múi giờ.

TIME — chỉ giờ trong ngày

Kiểu TIME lưu một thời điểm trong ngày mà không gắn với ngày cụ thể, ví dụ 09:30:00. Nó trả lời câu hỏi "mấy giờ" chứ không phải "khi nào". Dùng TIME cho giờ mở cửa cửa hàng (8:00 sáng mỗi ngày), giờ bắt đầu ca làm việc, khung giờ giao hàng (14:00–17:00), giờ lên lịch nhắc nhở hàng ngày. Khi một quán cà phê mở cửa lúc 7:00 mỗi ngày, bạn không cần biết là ngày nào — chỉ cần biết giờ.

DATETIME / TIMESTAMP — kết hợp ngày và giờ

Đây là kiểu phổ biến nhất và cũng dễ gây nhầm nhất. Nó lưu cả ngày lẫn giờ, ví dụ 2026-05-12 09:30:00. Đây là kiểu bạn dùng cho mọi thứ mang bản chất "một sự kiện đã xảy ra hoặc sẽ xảy ra tại một thời điểm cụ thể": thời điểm tạo đơn hàng, thời điểm đăng nhập, thời điểm gửi tin nhắn, lịch hẹn cuộc họp.

Vấn đề mấu chốt nằm ở đây: một DATETIME trần trụi như 2026-05-12 09:30:00 thì 9:30 đó là giờ ở đâu? Hà Nội, London hay New York? Nếu mô hình dữ liệu không trả lời được câu hỏi này, bạn đang ngồi trên một quả bom hẹn giờ.

Múi giờ — trái tim của vấn đề

Có hai cách tiếp cận chính, và bạn phải hiểu rõ cả hai:

Cách 1 — Lưu kèm múi giờ (timezone-aware). Phần lớn các CSDL hiện đại có kiểu như TIMESTAMP WITH TIME ZONE (PostgreSQL gọi là timestamptz). Bản chất: dù bạn ghi vào giờ gì, hệ thống quy chuẩn về UTC để lưu, rồi khi đọc ra sẽ chuyển về múi giờ của người xem. Đây là lựa chọn an toàn nhất cho các sự kiện toàn cầu.

Cách 2 — Lưu UTC + cột múi giờ riêng. Một số nhóm chọn lưu thời điểm dưới dạng UTC trong một cột TIMESTAMP thường, đồng thời lưu múi giờ của người dùng/giao dịch ở một cột riêng (ví dụ Asia/Ho_Chi_Minh). Cách này linh hoạt khi bạn cần biết "người dùng đã ở múi giờ nào lúc giao dịch" cho mục đích pháp lý hay phân tích.

Một nguyên tắc vàng mà tôi muốn bạn khắc cốt ghi tâm: lưu thời điểm dưới dạng UTC, chỉ chuyển sang giờ địa phương ở tầng hiển thị. UTC (Coordinated Universal Time) là điểm tham chiếu chung không bị ảnh hưởng bởi giờ mùa hè hay đường biên giới. Việt Nam dùng múi giờ UTC+7 cố định quanh năm (không có daylight saving), nên giờ Việt Nam = giờ UTC + 7 tiếng. Nhưng đừng vì Việt Nam đơn giản mà lưu thẳng giờ Việt Nam — vì rồi một ngày hệ thống của bạn sẽ phục vụ khách Singapore (UTC+8), Thái Lan (UTC+7) hay khách Việt kiều ở Mỹ.

Tình huống thực tế

Tình huống 1 — Sàn TMĐT lệch giờ chốt flash sale

Một sàn thương mại điện tử giả định tên "ChợViệt" tổ chức flash sale lúc 00:00 ngày 11/11. Đội kỹ thuật lưu thời điểm bắt đầu sale bằng DATETIME trần là 2026-11-11 00:00:00, mặc định hiểu là giờ Việt Nam. Mọi thứ chạy ổn cho tới khi họ mở rộng sang Singapore và thuê một cụm máy chủ đặt ở data center Singapore. Máy chủ đó cấu hình mặc định theo giờ hệ thống là UTC.

Kết quả: khi mã chương trình so sánh "giờ hiện tại" (đọc theo UTC trên server Singapore) với mốc 2026-11-11 00:00:00 (vốn được nhập theo ý là giờ Việt Nam), flash sale thực tế kích hoạt trễ 7 tiếng so với dự kiến. Khách hàng Việt Nam vào lúc nửa đêm thấy giá vẫn nguyên, mạng xã hội bùng nổ phàn nàn.

Diễn giải: lỗi không nằm ở code, mà nằm ở mô hình dữ liệu không quy định rõ múi giờ. Một DATETIME không nói cho ai biết 00:00 đó là giờ gì, nên mỗi tầng diễn giải một kiểu.

Bài học: với các sự kiện có thời điểm tuyệt đối, dùng TIMESTAMP WITH TIME ZONE hoặc quy ước cứng "luôn lưu UTC". Nếu đã lưu UTC, mốc sale lẽ ra phải là 2026-11-10 17:00:00 UTC, và mọi server ở đâu cũng diễn giải giống hệt nhau.

Tình huống 2 — Ngân hàng và bài toán "ngày giao dịch"

Một ngân hàng số tại Việt Nam xử lý giao dịch chuyển khoản. Một khách hàng thực hiện chuyển tiền lúc 23:50 ngày 31/03 (giờ Việt Nam). Hệ thống core lưu thời điểm theo UTC, tức 2026-03-31 16:50:00 UTC. Khi bộ phận đối soát cuối tháng 3 chạy báo cáo và nhóm thẳng theo ngày của cột UTC, giao dịch này rơi vào... vẫn ngày 31/03 UTC (16:50). May. Nhưng một giao dịch khác lúc 06:30 sáng ngày 01/04 giờ Việt Nam lại tương ứng 2026-03-31 23:30:00 UTC — và bị nhóm nhầm vào tháng 3!

Diễn giải: khi tính "ngày giao dịch" cho báo cáo kế toán Việt Nam, bạn phải chuyển UTC về giờ Việt Nam (+7) rồi mới lấy phần ngày. Nếu nhóm trực tiếp theo ngày của UTC, doanh thu đầu giờ sáng đầu tháng sẽ bị đẩy nhầm sang tháng trước.

Bài học: lưu UTC là đúng, nhưng logic báo cáo phải quy đổi về múi giờ nghiệp vụ trước khi cắt theo ngày. Trong mô hình dữ liệu, BA nên ghi rõ trong data dictionary rằng cột transaction_at lưu UTC, và mọi báo cáo theo "ngày làm việc" phải áp dụng phép chuyển AT TIME ZONE 'Asia/Ho_Chi_Minh'.

Tình huống 3 — Ứng dụng đặt lịch khám xuyên múi giờ

Một phòng khám quốc tế tại TP.HCM dùng ứng dụng đặt lịch cho cả bệnh nhân Việt và chuyên gia tư vấn từ xa ở Úc (Sydney, UTC+10/+11 tùy mùa). Một bệnh nhân đặt lịch tư vấn online "10:00 sáng giờ Việt Nam ngày 15/07". Bác sĩ ở Sydney cần thấy đúng giờ của họ là 13:00 chiều cùng ngày.

Nhóm phát triển ban đầu lưu DATETIME2026-07-15 10:00:00 không kèm múi giờ, và hiển thị y nguyên cho cả hai bên. Bác sĩ Úc tưởng 10:00 là giờ Sydney nên vào trễ 3 tiếng, bệnh nhân chờ mòn mỏi.

Diễn giải: đây là tình huống điển hình cần lưu thời điểm tuyệt đối (UTC: 2026-07-15 03:00:00 UTC) và chuyển về giờ địa phương của từng người xem ở tầng giao diện. Úc lại có giờ mùa hè (daylight saving), nên việc tự cộng trừ tay là cực kỳ rủi ro — phải dùng tên múi giờ chuẩn IANA như Australia/Sydney để thư viện tự xử lý.

Bài học: khi có người dùng ở nhiều múi giờ, kiểu dữ liệu phải là timezone-aware, và đừng bao giờ lưu offset cứng kiểu "+10" mà hãy lưu tên vùng (region) để xử lý đúng giờ mùa hè.

Hướng dẫn từng bước

Khi bạn đứng trước một cột thời gian trong mô hình dữ liệu, hãy đi theo quy trình quyết định sau:

  • Hỏi: thứ này cần giờ không? Nếu chỉ là ngày lịch (sinh nhật, hết hạn, ngày lễ) thì dùng DATE. Đừng nhồi giờ vô nghĩa vào.
  • Hỏi: thứ này có gắn với một ngày cụ thể không? Nếu chỉ là giờ lặp lại mỗi ngày (giờ mở cửa, giờ ca làm) thì dùng TIME.
  • Nếu cần cả ngày lẫn giờ — hỏi tiếp: đây có phải sự kiện ở một thời điểm tuyệt đối không? Hầu hết các trường như "thời điểm tạo", "thời điểm thanh toán", "thời điểm đăng nhập" đều là sự kiện tuyệt đối. Với chúng, hãy lưu UTC và ưu tiên kiểu timezone-aware (timestamptz).
  • Quyết định chiến lược múi giờ và ghi vào data dictionary. Ghi rõ: cột này lưu UTC hay giờ địa phương? Nếu cần biết múi giờ gốc của người dùng, thêm một cột user_timezone (kiểu chuỗi, lưu tên IANA như Asia/Ho_Chi_Minh).
  • Chuẩn hóa định dạng ISO 8601. Luôn dùng YYYY-MM-DD cho ngày và YYYY-MM-DDTHH:MM:SSZ cho thời điểm có múi giờ. Định dạng này không gây mơ hồ giữa ngày/tháng (tránh thảm họa 03/04 là mùng 3 tháng 4 hay mùng 4 tháng 3).
  • Quy ước hiển thị tách khỏi lưu trữ. Trong tài liệu mô hình, nêu rõ: lưu UTC, hiển thị theo múi giờ người dùng. Tầng ứng dụng chịu trách nhiệm chuyển đổi.
  • Cân nhắc độ chính xác (precision). Một số nghiệp vụ cần tới mili-giây hoặc micro-giây (giao dịch chứng khoán, log hệ thống), số khác chỉ cần tới giây. Khai báo precision rõ trong physical model.

Lỗi thường gặp & mẹo

Lỗi 1 — Lưu giờ địa phương rồi quên mất nó là giờ gì. Đây là lỗi kinh điển. Sáu tháng sau không ai nhớ cột created_at lưu giờ Việt Nam hay UTC. Mẹo: chọn một quy ước (UTC) và áp dụng nhất quán toàn hệ thống, ghi vào data dictionary.

Lỗi 2 — Tự cộng trừ offset bằng tay. Viết kiểu "cộng 7 tiếng" có thể đúng cho Việt Nam nhưng sẽ sai ngay khi gặp quốc gia có daylight saving. Mẹo: luôn dùng tên múi giờ IANA và để thư viện/CSDL xử lý.

Lỗi 3 — Nhồi giờ vào trường ngày thuần. Lưu ngày sinh dưới dạng DATETIME với giờ 00:00:00 rồi quy đổi múi giờ khiến ngày sinh bị lùi một ngày. Mẹo: ngày lịch thì dùng DATE, chấm hết.

Lỗi 4 — Cắt báo cáo theo ngày trên cột UTC mà quên quy đổi. Như tình huống ngân hàng, doanh thu rạng sáng bị nhảy nhầm ngày. Mẹo: luôn AT TIME ZONE về múi giờ nghiệp vụ trước khi GROUP BY theo ngày.

Lỗi 5 — Dùng kiểu chuỗi (VARCHAR) để lưu thời gian. Lưu "12/05/2026 9h30" dưới dạng text khiến không sort được, không tính toán được, và mỗi người nhập một định dạng. Mẹo: luôn dùng kiểu thời gian gốc của CSDL.

Mẹo bổ sung: với các trường có thể chưa biết giờ (ví dụ "ngày dự kiến giao hàng" mới chỉ biết ngày), đừng ép giờ giả. Tách thành DATE riêng, hoặc cho phép NULL phần giờ, và mô tả rõ ý nghĩa.

Bài tập thực hành

  • Cho hệ thống đặt phòng khách sạn tại Đà Nẵng phục vụ cả khách quốc tế. Hãy liệt kê 6 trường thời gian (ví dụ: ngày nhận phòng, thời điểm đặt phòng, giờ check-in tiêu chuẩn...) và chọn kiểu dữ liệu phù hợp (DATE, TIME, hay TIMESTAMP WITH TIME ZONE) cho từng trường, kèm lý do.
  • Một giao dịch được lưu là 2026-12-31 18:30:00 UTC. Hỏi: theo giờ Việt Nam, giao dịch này thuộc ngày nào, năm nào? Báo cáo doanh thu năm 2026 (theo lịch Việt Nam) có nên tính giao dịch này không?
  • Viết phần mô tả data dictionary cho một cột tên order_placed_at của sàn TMĐT đa quốc gia: ghi rõ kiểu dữ liệu, lưu UTC hay địa phương, định dạng, và cách báo cáo phải quy đổi.
  • Tìm một lỗi múi giờ trong một ứng dụng bạn từng dùng (app ngân hàng, đặt vé, lịch...) và phân tích: gốc rễ nằm ở lưu trữ hay hiển thị?

Tóm tắt

Thời gian là dữ liệu dễ chủ quan nhưng gây lỗi đắt nhất. Hãy nhớ ba kiểu nền tảng: DATE cho ngày lịch thuần (sinh nhật, hết hạn), TIME cho giờ lặp lại trong ngày (giờ mở cửa), và DATETIME/TIMESTAMP cho sự kiện tại một thời điểm cụ thể. Nguyên tắc sống còn: với sự kiện tuyệt đối, lưu UTC, hiển thị theo giờ địa phương, và ưu tiên kiểu timezone-aware. Đừng tự cộng trừ offset bằng tay — dùng tên múi giờ IANA. Luôn ghi rõ quy ước múi giờ vào data dictionary để sáu tháng sau không ai phải đoán. Và khi làm báo cáo theo ngày, nhớ quy đổi UTC về múi giờ nghiệp vụ trước khi cắt theo ngày. Nắm vững những điều này, bạn đã tránh được cả một loạt lỗi âm thầm mà nhiều hệ thống lớn vẫn vấp phải.