Khi một hệ thống chỉ phục vụ người dùng trong cùng một khu vực địa lý, time zone thường chưa gây ra nhiều vấn đề. Developer, user, server và business có thể vô tình cùng nằm trong một múi giờ, khiến code tưởng như luôn đúng.
Nhưng khi hệ thống bắt đầu phục vụ khách hàng toàn cầu, có team remote xuyên quốc gia, hoặc có nhiều office/market ở các quốc gia khác nhau, time zone trở thành một phần của domain design.
Một hệ thống thực tế có thể đồng thời có:
Customer → Tokyo
Operations team → London
Developer → Vietnam
Office A → New York
Office B → Los Angeles
Server → Singapore
Database → UTCLúc này câu:
“Sự kiện diễn ra lúc 9 giờ sáng”
không còn đủ thông tin. Câu hỏi bắt buộc phải là:
9 giờ sáng ở đâu?
Đó là điểm bắt đầu của mọi bài toán timezone.
1. Time zone không chỉ là chuyện format Date
Một cách tiếp cận sai khá phổ biến là coi timezone như vấn đề presentation:
Backend lưu date
Frontend format theo timezoneCách nghĩ này chỉ đúng một phần.
Time zone ảnh hưởng đến rất nhiều khía cạnh khác:
business hours
calendar date
daily report
recurring schedule
notification
deadline
payment timestamp
user activity
cron job
analytics
monthly KPI
remote collaborationVí dụ một công ty ở New York tổ chức webinar:
September 15
10:00 AM
America/New_YorkSpeaker ở New York.
Người tham gia có thể ở:
London
Tokyo
Vietnam
Sydney
SingaporeInfrastructure lại có thể chạy tại Singapore.
Cùng một webinar nhưng mỗi người sẽ nhìn thấy một giờ khác nhau.
Tuy nhiên tất cả đều đang nói về cùng một thời điểm tuyệt đối.
Đây là distinction quan trọng nhất cần hiểu trước khi thiết kế hệ thống timezone.
2. Ba loại thời gian cần phân biệt
Không phải mọi field liên quan tới thời gian đều có cùng semantic.
Trong phần lớn hệ thống, nên phân biệt ít nhất ba loại.
2.1 Absolute time — một thời điểm cụ thể
Ví dụ:
2026-09-15T14:00:00Z
Đây là một instant.
Nó đại diện cho đúng một thời điểm trên timeline toàn cầu.
Có thể hiển thị:
New York → 10:00
London → 15:00
Vietnam → 21:00
Tokyo → 23:00nhưng underlying instant không thay đổi.
Các field thường thuộc nhóm này:
created_at
updated_at
published_at
paid_at
logged_in_at
event_start_at
event_end_at
notification_sent_atKhi nói đến:
“Sự kiện thực sự xảy ra lúc nào?”
thì đang nói về absolute time.
2.2 Local business time — thời gian mang ý nghĩa địa phương
Giả sử một office hoạt động:
Monday
09:00 → 18:0009:00 ở đây không phải một instant.
Nó có nghĩa:
Văn phòng mở cửa lúc 9 giờ sáng theo giờ địa phương.
Semantic đầy đủ là:
Monday
09:00
America/New_YorkKhông nên convert recurring business rule này sang UTC rồi lưu cố định.
Ví dụ:
09:00 New Yorkcó lúc tương ứng với:
13:00 UTCvà có lúc:
14:00 UTCdo DST.
Vì vậy local business time nên giữ dưới dạng local wall-clock time cùng timezone.
2.3 Duration — khoảng thời gian
Ví dụ:
Meeting duration = 90 minutes
Session timeout = 30 minutes
Trial period = 14 daysDuration không có timezone.
90 minutesở New York vẫn là 90 phút ở Tokyo.
Đây là một distinction nhỏ nhưng quan trọng.
Nếu một field đơn giản là elapsed duration thì không cần gắn timezone vào nó.
3. UTC nên là canonical timeline của hệ thống
Một convention rất hữu ích cho distributed systems là:
Absolute time nên được normalize về UTC trong core system.
Quay lại webinar:
September 15
10:00
America/New_YorkBackend có thể resolve thành:
2026-09-15T14:00:00ZSau đó core system chỉ cần làm việc với instant này.
Ví dụ:
Database
event_start_at = 2026-09-15T14:00:00Z
Queue
execute_at = 2026-09-15T14:00:00Z
Notification
send_at = 2026-09-15T13:30:00Z
Analytics
timestamp = 2026-09-15T14:00:00ZĐiểm mạnh của UTC là toàn bộ hệ thống có cùng một timeline.
Không quan trọng:
server đang ở đâu
developer đang ở đâu
user đang ở đâu
worker chạy ở region nàoMột instant vẫn là một instant.
4. Nhưng “convert everything to UTC” vẫn chưa đủ
Một câu thường nghe là:
“Cứ lưu tất cả bằng UTC là hết timezone bug.”
Không hoàn toàn đúng.
UTC rất tốt để trả lời:
Sự kiện xảy ra vào thời điểm nào?
Nhưng business thường hỏi:
Sự kiện thuộc ngày nào?
Hai câu này khác nhau.
Giả sử một transaction xảy ra:
2026-09-01T03:30:00ZTại London:
September 1
04:30Tại Los Angeles:
August 31
20:30Nếu manager của Los Angeles hỏi:
Revenue ngày August 31 là bao nhiêu?
thì query theo UTC day:
August 31 00:00 UTC → September 1 00:00 UTCsẽ không đại diện chính xác business day của Los Angeles.
Đúng hơn phải là:
August 31 00:00 America/Los_Angeles -> UTC
September 1 00:00 America/Los_Angeles -> UTCSau đó query database bằng UTC range. Mental model:
Business date -> Business timezone -> Resolve UTC boundaries -> Query UTC data5. Server timezone không phải business timezone
Giả sử:
Developer → Vietnam
Server → Singapore
Business → New YorkCode:
const now = new Date();không có gì sai nếu mục tiêu là lấy instant hiện tại.
Nhưng code kiểu:
const today = dayjs();
const day = today.format('dddd');có thể sai nếu ý nghĩa thực sự là:
Hôm nay ở New York là thứ mấy?
Ví dụ:
Singapore → Tuesday 01:00
New York → Monday 13:00Server đã sang Tuesday.
Business vẫn còn Monday.
Nếu lấy working schedule bằng server local date thì business logic có thể chọn sai ngày.
Do đó server timezone không nên tham gia vào domain decision.
Setting server thành UTC vẫn là practice tốt:
Application runtime → UTC
Database → UTC
Container → UTC
Scheduler → UTCnhưng business logic vẫn phải explicit timezone.
6. Browser timezone cũng không phải source of truth
Frontend thường tự biết timezone user thông qua:
Intl.DateTimeFormat().resolvedOptions().timeZoneVí dụ:
Asia/Ho_Chi_MinhĐiều này rất hữu ích cho presentation.
Nhưng browser timezone không phải lúc nào cũng là business timezone.
Ví dụ: một manager đang đi công tác Tokyo nhưng quản lý office London.
Browser timezone → Asia/Tokyo
Business timezone → Europe/LondonNếu dashboard sử dụng browser timezone để xác định:
Today
Yesterday
This week
Current monththì report có thể sai. Do đó trước mỗi UI cần xác định rõ:
Thời gian này phải hiển thị theo viewer timezone hay business timezone?
Hai loại UI có thể cần behavior khác nhau.
Ví dụ messaging app thường ưu tiên viewer timezone.
Operational dashboard thường ưu tiên business timezone.
7. Hãy lưu IANA timezone
Không nên model timezone bằng fixed offset:
UTC+7
UTC-5
GMT+1Thay vào đó nên sử dụng IANA timezone:
Asia/Ho_Chi_Minh
America/New_York
America/Los_Angeles
Europe/London
Asia/TokyoLý do lớn nhất là DST.
Ví dụ:
America/New_Yorkkhông phải luôn:
UTC-5Trong một số thời điểm nó là:
UTC-4Nếu hệ thống chỉ lưu:
offset = -5thì nó không có đủ thông tin để xác định timezone behavior tương lai.
Có thể hiểu đơn giản:
Offset = kết quả tại một thời điểm
Timezone = rule dùng để tính offsetDo đó source of truth nên là timezone identifier.
8. Database nên model absolute time thế nào?
Với PostgreSQL, absolute timestamp thường phù hợp với:
TIMESTAMPTZVí dụ:
CREATE TABLE webinar_events (
id UUID PRIMARY KEY,
organizer_timezone VARCHAR(100) NOT NULL,
start_at TIMESTAMPTZ NOT NULL,
end_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Một record có thể conceptually là:
organizer_timezone: America/New_York
start_at: 2026-09-15T14:00:00ZĐiểm quan trọng:
TIMESTAMPTZ không có nghĩa PostgreSQL lưu:
America/New_YorkTrong timestamp. Nó lưu một instant. Timezone có business meaning vẫn nên được lưu riêng.
Có thể hiểu data model thành:
start_at → WHEN
organizer_timezone → BUSINESS CONTEXTHai field phục vụ hai câu hỏi khác nhau.
9. API contract phải làm rõ semantic
Absolute instant nên được truyền bằng ISO-8601 có timezone rõ ràng.
Ví dụ:
{
"startAt": "2026-09-15T14:00:00Z",
"endAt": "2026-09-15T15:30:00Z"
}Frontend có thể format:
New York → 10:00 AM
Tokyo → 11:00 PMnhưng API vẫn truyền cùng một value.
Không nên truyền:
{
"startAt": "2026-09-15 10:00"
}vì backend không biết:
10:00 New York?
10:00 London?
10:00 Tokyo?
10:00 server time?Nếu input thực sự là local datetime thì contract nên explicit:
{
"date": "2026-09-15",
"time": "10:00",
"timezone": "America/New_York"
}Backend sau đó mới resolve thành instant.
10. Local date không phải timestamp
Giả sử user chọn trên calendar:
September 15Đây chưa phải instant.
Nó chỉ là:
2026-09-15Nếu developer lập tức chuyển thành:
2026-09-15T00:00:00Zthì semantic đã thay đổi.
Local date trả lời:
Ngày nào trên business calendar?
Timestamp trả lời:
Thời điểm nào trên global timeline?
Nên API có thể giữ:
{
"date": "2026-09-15"
}và chỉ resolve timezone khi thực sự cần một instant.
Ví dụ:
2026-09-15 + Europe/London + 09:00 -> absolute instant -> UTC11. Recurring schedules nên giữ local wall-clock time
Giả sử một distributed company có daily standup:
Every Monday
09:00
Europe/LondonKhông nên convert một lần thành:
09:00 London → 09:00 UTCrồi assume luôn đúng.
DST có thể làm UTC representation thay đổi. Recurring rule nên giữ:
day_of_week = MONDAY
time = 09:00
timezone = Europe/LondonSau đó mỗi occurrence cụ thể mới được resolve sang UTC.
Concept:
Recurring rule -> Local schedule -> Timezone rules -> Concrete occurrence -> UTC instantĐiều này áp dụng cho:
Meetings
Office hours
Scheduled reports
Maintenance windows
Notification campaigns
Remote standups
Market opening times
12. DST là lý do không nên hard-code offset
Daylight Saving Time là nơi nhiều implementation tưởng đúng bắt đầu sai.
Giả sử:
09:00 America/New_Yorkở một thời điểm:
= 14:00 UTCnhưng ở thời điểm khác:
= 13:00 UTCNếu developer lưu cố định:
09:00 New York = 14:00 UTCthì hệ thống sẽ lệch một giờ khi DST thay đổi.
Developer không nên tự maintain DST rule.
Timezone database và mature datetime libraries đã tồn tại để xử lý việc đó.
Nhiệm vụ của application chủ yếu là cung cấp đúng:
local datetime + IANA timezone13. Notification nên schedule bằng absolute time
Giả sử event bắt đầu:
14:00 UTCVà cần notification trước 30 phút.
Scheduler có thể tính:
13:30 UTCLúc này scheduler không cần biết participant ở Tokyo hay New York.
Đây là một lợi ích lớn của UTC.
Core:
Event instant -> minus 30 minutes -> Notification instantSau đó khi render message mới convert.
Ví dụ participant ở Tokyo:
Event starts at 11:00 PMParticipant ở New York:
Event starts at 10:00 AMScheduling và presentation được tách riêng.
14. Remote work cũng là một timezone domain
Timezone không chỉ dành cho SaaS có global customers.
Remote team cũng gặp cùng một vấn đề.
Ví dụ:
Product Owner → San Francisco
Tech Lead → London
Developer → Vietnam
QA → IndiaProduct Owner nói:
Deploy Monday at 9 AM.
Thông tin này chưa đủ. Cần:
Monday
09:00
America/Los_AngelesKhi schedule được xác nhận, system resolve thành UTC.
Sau đó mỗi member nhìn:
San Francisco → 09:00
London → 17:00
Vietnam → 00:00 next day
India → 22:30Một event.
Một instant.
Nhiều local representations.
15. Reporting phải bắt đầu từ business timezone
Reporting là nơi timezone bugs rất dễ tồn tại lâu vì dữ liệu nhìn vẫn “gần đúng”.
Giả sử muốn thống kê:
Daily active users của Tokyo ngày July 20.
Không nên query trực tiếp:
2026-07-20T00:00:00Z → 2026-07-21T00:00:00ZMà phải resolve:
2026-07-20 00:00 Asia/Tokyo -> UTC
2026-07-21 00:00 Asia/Tokyo -> UTCSau đó:
WHERE created_at >= :utcStart
AND created_at < :utcEndPattern này áp dụng cho:
daily revenue
daily active users
monthly report
attendance
conversion
transactions
KPI
Nếu business question chứa:
day
week
month
quarterthì luôn nên hỏi:
Theo timezone nào?
16. Dùng half-open interval cho date range
Thay vì:
00:00:00 → 23:59:59nên model:
[start, nextStart)Ví dụ:
WHERE created_at >= :startOfDayUtc
AND created_at < :startOfNextDayUtcKhông cần quan tâm:
milliseconds
microseconds
database precisionĐây là một convention đơn giản nhưng rất hữu ích.
17. Centralize timezone logic
Một codebase rất dễ trở nên khó kiểm soát khi khắp nơi xuất hiện:
new Date(...)
dayjs(...)
dayjs.utc(...)
.tz(...)
moment(...)
moment.utc(...)và mỗi developer tự chọn cách convert.
Thay vì vậy nên có shared abstraction.
Ví dụ:
toUtcInstant(...)
toZonedTime(...)
resolveLocalDateTime(...)
getBusinessDayUtcRange(...)
getBusinessLocalDate(...)Ví dụ API nội bộ:
resolveLocalDateTime({
date: '2026-09-15',
time: '10:00',
timezone: 'America/New_York',
});trả:
2026-09-15T14:00:00ZLợi ích lớn nhất không phải là giảm vài dòng code.
Mà là giúp toàn team sử dụng cùng một convention về timezone .
18. Naming cũng giúp tránh timezone bugs
Những variable như: date, time, start, end, now rất dễ gây nhầm.
Tên rõ semantic hơn như :
eventStartUtc
businessTimezone
localBusinessDate
displayTime
utcRangeStart
utcRangeEnd
Một developer nhìn: eventStartUtc biết ngay đây là instant.
Nhìn: localBusinessDate biết đây không phải UTC timestamp.
Timezone bugs thường xuất phát từ việc semantic bị mất trong code.
Naming tốt giúp giữ semantic đó.
19. Một architecture tổng quát
Có thể tóm tắt toàn bộ design bằng flow:

Core system có một timeline.
Presentation có nhiều timezone views.
20. Một bộ convention cho team
Nếu cần định nghĩa timezone rules cho một project, có thể bắt đầu bằng bộ nguyên tắc sau:
Absolute datetime → canonical UTC.
Absolute database timestamps → TIMESTAMPTZ.
Absolute datetime trong API → ISO-8601 có UTC/offset rõ ràng.
Business entity có timezone → lưu IANA timezone.
Recurring schedule → lưu local wall-clock time + timezone.
Calendar date → giữ YYYY-MM-DD khi semantic chỉ là ngày.
Duration → không có timezone.
Server timezone → UTC.
Browser timezone → presentation context, không mặc định là business context.
Business date ranges → resolve timezone trước, query UTC sau.
DST → giao cho timezone database/library xử lý.
Timezone conversion → centralize thay vì rải rác trong codebase.
21. Mental model đơn giản trước khi code
Trước khi xử lý một field thời gian, hãy hỏi hai câu.
Câu 1: Đây là instant hay local time?
Nếu là:
payment happened
event started
message sent
record createdthì đây là absolute instant.
Thông thường nên normalize thành UTC.
Nếu là:
office opens at 9 AM
every Monday at 10 AM
September 15 on the calendarthì đây là local business time.
Không nên convert thành UTC quá sớm.
Câu 2: Timezone thuộc về ai?
Có thể là:
Office
Organization
User
Market
Event
ProjectTimezone nên thuộc về entity mang business meaning.
Không nên để:
server timezone
browser timezone
developer timezonevô tình quyết định logic.
Kết luận
Một timezone architecture tốt không đơn giản là:
Convert tất cả thành UTC.
Đúng hơn phải là:
Hiểu dữ liệu thời gian đang mang semantic gì, timezone thuộc về domain object nào, và thời điểm nào nên resolve thành absolute instant.
Mental model có thể rút gọn thành tuần tự:
Local business time + IANA timezone
Absolute instant
UTC
Storage / API / Queue
Convert for presentation
Trong đó:
Business timezone → định nghĩa ý nghĩa địa phương của thời gian
UTC → tạo một timeline thống nhất cho core system
Display timezone → giúp người dùng nhìn cùng instant theo đúng context
Khi ba layer này được tách rõ, việc customer ở Tokyo, operations ở London, developer ở Vietnam và infrastructure chạy tại Singapore không còn là một case đặc biệt.
Nó chỉ là nhiều góc nhìn khác nhau của cùng một timeline.Và đó mới là cách nên nghĩ về time zone trong một hệ thống global.