The short answer
Quick answer: Time zones are hard because local time is defined by politics, not physics. Governments choose their offsets from UTC and their daylight saving rules, and they change them, sometimes with only weeks of notice. As a result a local time can not exist (when clocks jump forward), can happen twice (when they fall back), and a day is not always 24 hours long. On top of that, an offset such as +02:00 is not the same thing as a time zone such as Europe/Paris. The working rules: store moments in UTC, convert to local time only for display, keep the named zone for future events, and use a proper date library.
The model people start with
"Each country has a fixed offset from Greenwich, and some add an hour in summer."
Almost every part of that is wrong.
What is actually true
Offsets are not whole hours
India is UTC+5:30. Nepal is UTC+5:45. Parts of Australia use +9:30 and +8:45. The full range runs from UTC-12 to UTC+14, so two places can show the same clock time on different calendar dates.
Daylight saving is inconsistent
- Many countries do not use it at all.
- Those that do switch on different dates. Europe and North America are out of step for a few weeks each spring and autumn.
- The southern hemisphere shifts the opposite way.
- Not every shift is one hour. Lord Howe Island moves by 30 minutes.
Rules change
Governments abolish daylight saving, adopt it, move the dates, or change their base offset. Some changes have been announced days in advance. Samoa moved across the International Date Line at the end of 2011 and skipped 30 December entirely.
Local times can be missing or repeated
On the spring change in a zone that moves from 02:00 to 03:00:
| Wall clock | Exists? |
|---|---|
| 01:59 | Yes |
| 02:30 | No |
| 03:00 | Yes |
On the autumn change, 01:30 happens twice, an hour apart. A timestamp of "01:30" without an offset is ambiguous.
So a day can be 23, 24 or 25 hours long. Code that adds 24 hours to mean "tomorrow" is wrong twice a year.
Abbreviations are ambiguous
CST is Central Standard Time, China Standard Time and Cuba Standard Time. IST is India, Ireland and Israel. Never parse or store them.
Offset versus time zone
This is the distinction that matters most.
| Example | What it tells you | |
|---|---|---|
| Offset | +02:00 | The difference from UTC at one moment |
| Time zone | Europe/Paris | A region and its full history of rules, past and future |
Paris is +01:00 in winter and +02:00 in summer. Knowing that a timestamp was +02:00 does not tell you what the offset will be six months later.
The tz database
The rules live in the tz database, also called the IANA or Olson database. It names zones as Area/Location, such as America/New_York or Asia/Kolkata, and records every known change for each.
It is maintained by volunteers and updated several times a year. Your operating system, language runtime and database each ship a copy. If those copies are out of date, your software computes wrong local times for the affected regions. Keeping them updated is a real maintenance task.
Representing time
A Unix timestamp counts seconds since 1 January 1970, 00:00 UTC. It identifies a moment unambiguously, with no zone involved. See Unix time.
For text, use the ISO 8601 style defined for internet use in RFC 3339:
2026-10-04T14:30:00Z the Z means UTC
2026-10-04T16:30:00+02:00 the same instant, written with an offset
Avoid 10/04/2026. It is 4 October in the United States and 10 April in much of the rest of the world.
Three kinds of value are easy to confuse:
| Kind | Example | Use |
|---|---|---|
| Instant | 2026-10-04T14:30:00Z | When something happened |
| Local date-time | 2026-10-04 09:00 | A wall-clock reading with no zone |
| Date only | 1990-05-17 | Birthdays, due dates |
Most bugs come from treating one kind as another.
Rules that work
- Store past events as UTC instants. Log entries, creation times, payments.
- Convert to local time at the edge, when displaying to a person, using their zone.
- For future events, store the local time and the named zone. A meeting at 09:00 in
Europe/Berlinnext year should stay at 09:00 Berlin time even if Germany changes its rules. If you had converted to UTC in advance, it would silently move. - Store the user's zone as an IANA name, not an offset.
- Keep dates as dates. A birthday stored as midnight UTC shows up as the previous day for users west of Greenwich.
- Use a real library:
java.time, Python'szoneinfo, JavaScript'sTemporalorIntl, Noda Time. Do not do offset arithmetic by hand. - Run servers in UTC.
- Name things clearly.
created_at_utc,starts_at_local,timezone. See why naming things is so hard. - Keep time zone data updated.
from datetime import datetime
from zoneinfo import ZoneInfo
utc = datetime(2026, 10, 4, 14, 30, tzinfo=ZoneInfo("UTC"))
print(utc.astimezone(ZoneInfo("Asia/Kolkata"))) # 2026-10-04 20:00:00+05:30
A database note: in PostgreSQL, timestamp with time zone does not store a zone. It converts the input to UTC and stores the instant. timestamp without time zone stores the wall-clock digits with no meaning attached. Know which one you are using.
Classic bugs
- A daily job at 02:30 that is skipped in spring and runs twice in autumn. Schedule in UTC, or avoid the changeover hours.
- "Add one day" as 86,400 seconds. Use calendar arithmetic.
- Date ranges that miss the last day, because "to 31 March" was read as midnight at the start of the day.
- Daily reports cut at UTC midnight that split a local business day in two.
- JavaScript's
Date, which parses date-only strings as UTC and date-time strings as local, and counts months from zero. - Tests that pass in the office and fail on a build server in another zone, or only on changeover days.
- Charts and counts "per day" that depend on whose day it is.
Beyond time zones
- Leap seconds. UTC occasionally gains an extra second to track the Earth's rotation. Unix time pretends they do not exist, so large operators "smear" the second across many hours. It has been agreed to phase out the practice by 2035. See leap second.
- Clocks drift and jump. A machine's clock can be wrong, and can move backwards when corrected. Use a monotonic clock to measure durations.
- Machines disagree. Ordering events across servers by timestamp is unreliable. See clocks in distributed systems.
- The year 2038. Signed 32-bit Unix timestamps overflow on 19 January 2038.
- Other calendars and historical dates bring problems of their own.
Time belongs with Unicode and floating-point numbers on the list of things that seem simple until real data arrives.
Frequently asked questions
Should I store dates in UTC?
For moments that have already happened, yes. For future events tied to a place, store the local time and the named time zone.
What is the difference between UTC and GMT?
UTC is the modern time standard based on atomic clocks. GMT is a time zone that currently has the same clock time. For most software they can be treated as equal, but UTC is the correct term.
What is the difference between an offset and a time zone?
An offset is a fixed difference from UTC. A time zone is a set of rules for a region that determines the offset at any given date.
Why did my scheduled job run twice?
It was probably scheduled in local time during the hour that repeats when clocks go back.
Conclusion
Time zones are difficult because they encode human decisions that are irregular and keep changing. You cannot simplify them away, but you can contain them: keep instants in UTC, keep the named zone when the local time is what matters, convert only at the edges, and let a maintained library and the tz database carry the rules.
Related articles
- Why Clocks Can't Be Trusted in Distributed Systems
- Why Unicode Exists (and Why Emojis Break Things)
- Why Floating Point Math Gives You 0.1 + 0.2 = 0.30000000000000004
- Why Is Naming Things So Hard in Programming?
