When a system serves users within a single geographic region, time zones may not seem like a serious problem. Developers, users, servers, and business operations may all happen to share the same time zone, making the code appear correct.
But once the system starts serving global customers, supporting distributed teams, or operating across offices and markets in different countries, time zones become part of the domain design.
A real-world system might look like this:
Customer → Tokyo
Operations team → London
Developer → Vietnam
Office A → New York
Office B → Los Angeles
Server → Singapore
Database → UTCAt that point, a statement such as:
“The event starts at 9:00 AM.”
is no longer enough.
The immediate question becomes:
9:00 AM where?
That is the starting point of almost every time zone problem.
1. Time Zones Are Not Just a Date Formatting Problem
A common mistake is to treat time zones purely as a presentation concern:
Backend stores a date
Frontend formats it for a time zoneThat is only partially correct.
Time zones affect many different parts of a system:
Business hours
Calendar dates
Daily reports
Recurring schedules
Notifications
Deadlines
Payment timestamps
User activity
Cron jobs
Analytics
Monthly KPIs
Remote collaboration
Consider a company in New York hosting a webinar:
September 15
10:00 AM
America/New_YorkThe speaker is in New York.
Participants may be located in:
London
Tokyo
Vietnam
Sydney
SingaporeMeanwhile, the infrastructure may be running in Singapore.
Each participant sees a different local time, but they are all referring to the same absolute moment.
Understanding that distinction is the foundation of reliable time zone design.
2. Three Types of Time You Should Distinguish
Not every time-related field represents the same thing.
In most systems, it is useful to distinguish at least three categories.
2.1 Absolute Time — A Specific Moment
For example:
2026-09-15T14:00:00ZThis represents an instant.
It points to exactly one moment on the global timeline.
The same instant may be displayed differently:
New York → 10:00
London → 15:00
Vietnam → 21:00
Tokyo → 23:00but the underlying instant does not change.
Typical fields in this category include:
created_at
updated_at
published_at
paid_at
logged_in_at
event_start_at
event_end_at
notification_sent_atWhenever the question is:
“When did this actually happen?”
you are usually dealing with absolute time.
2.2 Local Business Time — Time With Local Meaning
Suppose an office operates:
Monday. 09:00 → 18:00Here, 09:00 is not an instant.
It means:
The office opens at 9:00 AM according to the office's local clock.
The complete semantic value is closer to:
Monday
09:00
America/New_YorkYou generally should not convert this recurring business rule into UTC once and store it permanently.
For example:
09:00 New Yorkmay correspond to:
13:00 UTCduring one part of the year, and:
14:00 UTCduring another because of Daylight Saving Time.
Local business schedules should therefore remain expressed as local wall-clock time together with a time zone.
2.3 Duration — An Amount of Time
Examples:
Meeting duration = 90 minutes
Session timeout = 30 minutes
Trial period = 14 daysA duration does not have a time zone.
90 minutesis still 90 minutes whether you are in New York or Tokyo.
This is a small distinction, but an important one.
If a value simply represents elapsed time, attaching a time zone to it usually does not make sense.
3. UTC Should Be the Canonical Timeline of the System
A useful convention for distributed systems is:
Normalize absolute time to UTC inside the core system.
Consider the webinar again:
September 15
10:00
America/New_YorkThe backend can resolve it into:
2026-09-15T14:00:00ZFrom that point forward, most of the core system only needs to work with that instant.
For example:
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:00ZThe advantage of UTC is that the system operates on one consistent timeline.
It no longer matters where:
the server is running
the developer is located
the user is located
the worker process is deployed
An instant remains the same instant.
4. But “Convert Everything to UTC” Is Not Enough
A common rule is:
“Store everything in UTC and time zone problems disappear.”
That is not entirely true.
UTC is excellent for answering:
When did something happen?
But businesses frequently ask a different question:
Which business day does this belong to?
Suppose a transaction occurs at:
2026-09-01T03:30:00ZIn London:
September 1. 04:30In Los Angeles:
August 31. 20:30If a Los Angeles manager asks:
What was our revenue on August 31?
querying this UTC range:
August 31 00:00 UTC → September 1 00:00 UTCdoes not represent the Los Angeles business day correctly.
Instead, start with:
August 31 00:00 America/Los_Angeles -> UTCSeptember 1 00:00 America/Los_Angeles -> UTCThen query the database using the resulting UTC boundaries.
A useful mental model is:
Business date -> Business timezone -> Resolve UTC boundaries -> Query UTC data5. Server Time Zone Is Not Business Time Zone
Consider this setup:
Developer → Vietnam
Server → Singapore
Business → New YorkThis code is perfectly fine if you only need the current instant:
const now = new Date();But code like this may be dangerous:
const today = dayjs();
const day = today.format('dddd');if the actual business question is:
What day is it in New York?
For example:
Singapore → Tuesday 01:00
New York → Monday 13:00The server has already entered Tuesday.
The business is still operating on Monday.
If the application selects working hours based on the server's local date, it may accidentally use Tuesday's schedule.
The server's time zone should therefore not drive domain decisions.
Running infrastructure in UTC is still a good defensive practice:
Application runtime → UTC
Database → UTC
Container → UTC
Scheduler → UTCbut business logic still needs an explicit business time zone.
6. Browser Time Zone Is Not Always the Source of Truth Either
Frontend applications can easily detect the user's time zone:
Intl.DateTimeFormat().resolvedOptions().timeZoneFor example:
Asia/Ho_Chi_MinhThis is very useful for presentation.
But the browser's time zone does not necessarily represent the business context.
Imagine a manager traveling in Tokyo while managing a London office:
Browser timezone → Asia/Tokyo
Business timezone → Europe/LondonIf the dashboard uses the browser's time zone to calculate:
Today
Yesterday
This week
Current monththe reports may become incorrect.
Before implementing any time-sensitive UI, ask:
Should this screen use the viewer's time zone or the business time zone?
Different types of interfaces may require different answers.
A messaging application may reasonably prefer the viewer's local time.
An operational dashboard often needs the business entity's local time.
7. Store IANA Time Zones
Avoid modeling time zones using fixed offsets:
UTC+7
UTC-5
GMT+1Instead, use IANA time zone identifiers:
Asia/Ho_Chi_Minh
America/New_York
America/Los_Angeles
Europe/London
Asia/TokyoThe biggest reason is Daylight Saving Time.
For example:
America/New_YorkIs not permanently:
UTC-5At certain times of the year it becomes:
UTC-4If the system only stores:
offset = -5it does not have enough information to determine future time zone behavior correctly.
A useful way to think about it is:
Offset = the result at a specific momentTimezone= the rules used to determine the offsetThe time zone identifier should therefore be the source of truth.
8. How Should Absolute Time Be Stored in the Database?
With PostgreSQL, absolute timestamps are generally well represented using:
TIMESTAMPTZFor example:
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()
);A record may conceptually contain:
organizer_timezone: America/New_York
start_at: 2026-09-15T14:00:00ZAn important detail:
TIMESTAMPTZ does not mean PostgreSQL stores:
America/New_Yorkinside the timestamp.
It represents an absolute instant.
If the original business time zone has domain meaning, it should still be stored separately.
You can think of these fields as answering different questions:
start_at → WHEN
organizer_timezone → BUSINESS CONTEXT9. API Contracts Should Make Time Semantics Explicit
Absolute instants should generally be exchanged using ISO 8601 with an explicit UTC offset.
For example:
{
"startAt": "2026-09-15T14:00:00Z",
"endAt": "2026-09-15T15:30:00Z"
}The frontend may display that instant as:
New York → 10:00 AM
Tokyo → 11:00 PM
but the API value remains unchanged.
Avoid ambiguous payloads such as:
{
"startAt": "2026-09-15 10:00"
}The backend cannot know whether that means:
10:00 New York?
10:00 London?
10:00 Tokyo?
10:00 server time?If the input intentionally represents local time, the contract should say so explicitly:
{
"date": "2026-09-15",
"time": "10:00",
"timezone": "America/New_York"
}The backend can then resolve it into an absolute instant.
10. A Local Date Is Not a Timestamp
Suppose a user selects:
September 15on a calendar.
That is not yet an instant.
It may simply mean:
2026-09-15If the application immediately turns it into:
2026-09-15T00:00:00Zthe semantic meaning has changed.
A local date answers:
Which date on the business calendar?
A timestamp answers:
Which exact moment on the global timeline?
The API can therefore keep the value as:
{
"date": "2026-09-15"
}and only resolve it into an instant when the application actually has enough context.
For example:
2026-09-15 + Europe/London + 09:00 -> absolute instant -> UTC11. Recurring Schedules Should Keep Local Wall-Clock Time
Suppose a distributed company has a recurring standup:
Every Monday. 09:00. Europe/LondonDo not convert that once into:
09:00 London → 09:00 UTCAnd assume the UTC representation will always remain correct.
DST may change it later.
The recurring rule should retain:
day_of_week = MONDAY
time = 09:00
timezone = Europe/LondonEach concrete occurrence can then be resolved into UTC.
Conceptually:
Recurring rule -> Local schedule -> Timezone rules -> Concrete occurrence -> UTC instantThe same approach applies to:
Meetings
Office hours
Scheduled reports
Maintenance windows
Notification campaigns
Remote standups
Market opening times
12. DST Is Why Fixed Offsets Are Dangerous
Daylight Saving Time is where many apparently correct implementations begin to fail.
Consider:
09:00
America/New_YorkAt one point in the year:
= 14:00 UTCAt another:
= 13:00 UTCIf the application permanently assumes:
09:00 New York = 14:00 UTCthe system will become one hour wrong after the DST transition.
Applications should not manually maintain DST rules.
Time zone databases and mature date/time libraries already exist for that purpose.
The application should primarily provide the correct:
local datetime + IANA timezoneand let the time zone implementation resolve the appropriate offset.
13. Schedule Notifications Using Absolute Time
Suppose an event starts at:
14:00 UTCand a reminder must be sent 30 minutes earlier.
The scheduler can simply calculate:
13:30 UTCAt this point, the scheduler does not need to know whether the participant is in Tokyo or New York.
That is one of the major benefits of working with absolute time.
Core logic:
Event instant -> minus 30 minutes -> Notification instantOnly when rendering the message do we convert the event time for the recipient.
For a participant in Tokyo:
Event starts at 11:00 PMFor a participant in New York:
Event starts at 10:00 AMScheduling and presentation remain separate concerns.
14. Remote Work Is Also a Time Zone Domain
Time zone problems are not limited to customer-facing global SaaS products.
Distributed teams face the same challenge.
For example:
Product Owner → San Francisco
Tech Lead → London
Developer → Vietnam
QA → IndiaThe Product Owner says:
Deploy Monday at 9:00 AM.
That statement is incomplete.
The full meaning should be:
Monday. 09:00. America/Los_AngelesOnce the deployment schedule is confirmed, the system can resolve it into UTC.
Each team member can then see the same event in their own context:
San Francisco → 09:00
London → 17:00
Vietnam → 00:00 next day
India → 22:30One event.
One instant.
Multiple local representations.
15. Reporting Must Start With the Business Time Zone
Reporting is one of the easiest places for time zone bugs to survive because the resulting numbers often still look “close enough.”
Suppose we want:
Daily active users in Tokyo on July 20.
We should not automatically query:
2026-07-20T00:00:00Z → 2026-07-21T00:00:00ZInstead, resolve:
2026-07-20 00:00 Asia/Tokyo -> UTC2026-07-21 00:00 Asia/Tokyo -> UTCThen query:
WHERE created_at >= :utcStart
AND created_at < :utcEndThis pattern applies to:
Daily revenue
Daily active users
Monthly reports
Attendance
Conversion
Transactions
KPIs
Whenever a business question contains:
day
week
month
quarteryou should ask:
According to which time zone?
16. Prefer Half-Open Intervals for Date Ranges
Instead of modeling a day as:
00:00:00 → 23:59:59prefer:
[start, nextStart)For example:
WHERE created_at >= :startOfDayUtc
AND created_at < :startOfNextDayUtcThis avoids problems involving:
milliseconds
microseconds
database precisionIt is a simple convention, but a very useful one.
17. Centralize Time Zone Logic
A codebase becomes difficult to reason about when date handling is scattered everywhere:
new Date(...)
dayjs(...)
dayjs.utc(...)
.tz(...)
moment(...)
moment.utc(...)with every developer deciding independently how conversion should work.
A better approach is to introduce shared abstractions such as:
toUtcInstant(...)
toZonedTime(...)
resolveLocalDateTime(...)
getBusinessDayUtcRange(...)
getBusinessLocalDate(...)For example:
resolveLocalDateTime({
date: '2026-09-15',
time: '10:00',
timezone: 'America/New_York',
});returns:
2026-09-15T14:00:00ZThe biggest advantage is not saving a few lines of code.
It is giving the entire engineering team a consistent time zone convention.
18. Naming Helps Prevent Time Zone Bugs
Variables such as:
date
time
start
end
nowoften lose important semantic information.
More explicit names are safer:
eventStartUtc
businessTimezone
localBusinessDate
displayTime
utcRangeStart
utcRangeEndWhen a developer sees:
eventStartUtcit is immediately obvious that the value represents an instant.
When they see:
localBusinessDateit is clear that the value is not a UTC timestamp.
Many time zone bugs are caused not by complicated algorithms, but by losing semantic meaning as data moves through the system.
Good naming helps preserve that meaning.
19. A General Time Zone Architecture
The entire design can be summarized like this:

The core system has one timeline.
The presentation layer has multiple local views.
20. A Practical Time Zone Convention for Engineering Teams
A project can start with a set of rules like these:
Absolute datetime → canonical UTC.
Absolute database timestamps → TIMESTAMPTZ.
Absolute datetime in APIs → ISO 8601 with explicit UTC/offset.
Business entities with local time zones → store IANA timezone identifiers.
Recurring schedules → store local wall-clock time + timezone.
Calendar dates → keep YYYY-MM-DD when the semantic is only a date.
Duration → has no timezone.
Server timezone → UTC.
Browser timezone → presentation context, not automatically business context.
Business date ranges → resolve using business timezone first, query UTC second.
DST → let the timezone database/library handle the rules.
Timezone conversion → centralize it instead of scattering it across the codebase.
21. A Simple Mental Model Before Writing Code
Before handling any time-related field, ask two questions.
Question 1: Is This an Instant or Local Time?
If the event is something like:
payment happened
event started
message sent
record createdthen it represents an absolute instant.
It should usually be normalized to UTC.
If it is something like:
office opens at 9 AM
every Monday at 10 AM
September 15 on the calendarthen it represents local business time.
It should not be converted to UTC too early.
Question 2: Who Owns the Time Zone?
The time zone may belong to:
Office
Organization
User
Market
Event
ProjectIt should belong to the domain entity that gives the time its meaning.
Avoid allowing:
server timezone
browser timezone
developer timezoneto accidentally determine business behavior.
Conclusion
A good time zone architecture is not simply:
Convert everything to UTC.
A better rule is:
Understand what each time value represents, identify which domain object owns the time zone, and determine when that value should become an absolute instant.
The mental model can be summarized as:
Local business time + IANA timezone -> Absolute instant -> UTC -> Storage / API / Queue -> Convert for presentationIn this model:
Business timezone → defines the local meaning of time
UTC → provides a consistent timeline for the core system
Display timezone → lets users view the same instant in the appropriate contextOnce these layers are separated clearly, having customers in Tokyo, operations in London, developers in Vietnam, and infrastructure in Singapore is no longer a special case.
They are simply different local views of the same global timeline.
And that is the right way to think about time zones when designing applications for a global world.