Almost every date bug in production traces back to one of four mistakes, and all four are avoidable. Time zones for developers are not conceptually hard; they are simply unforgiving, because a design decision made in an afternoon surfaces as a defect eight months later when a government moves its clocks. The cost is asymmetric, so the conservative choice is nearly always the right one for developers to make.

This reference covers what to persist, why an offset is not a zone, how to handle the hour that does not exist and the hour that happens twice, and how to keep zone data current across the stack. Every identifier mentioned can be checked in the time zone browser.

Time Zones for Developers: What to Store

Store an instant in Coordinated Universal Time, and store the zone identifier separately when the local context matters. Never persist a raw UTC offset as though it identified a place, because an offset describes one moment while a zone describes a rule.

Most developers meet the distinction the moment a rule changes. A meeting saved as 2026-10-25T09:00:00+02:00 is pinned to an offset that Central European countries abandon that very morning; saved as 09:00 local in Europe/Berlin, it lands correctly whatever the rules turn out to be. This is the single most valuable habit in time zone handling for developers, and it costs one extra column in a table.

  • Past events: store the UTC instant. What happened, happened; the wall-clock rendering is a display concern.
  • Future events: store local wall time plus the zone identifier, because the offset that will apply is not yet knowable.
  • Recurring schedules: store the rule in local terms — every Tuesday at 09:00 in America/Chicago — and expand to instants at read time.
  • Audit logs: store UTC in ISO 8601 form with an explicit Z, and never rely on the server's local configuration.

Why Offsets and Abbreviations Are Not Identifiers

An offset is a number valid for one instant, and an abbreviation is display text with no unique meaning, which is why neither works as a primary key for developers. Neither can express a daylight saving rule, and neither survives a government decision, so neither belongs in a schema as a key.

The abbreviation problem is worse than it looks. IST resolves to three live offsets, CST to three more, and no registry exists to arbitrate — the subject of time zone abbreviations. Parsing user input such as "3pm EST" is guesswork; developers should render abbreviations if users expect them, but resolve them from a known zone rather than from a global lookup. The canonical identifiers to use instead come from the IANA time zone database and take the Area/Location form, as in Asia/Kolkata or Pacific/Auckland.

What Happens at a Daylight Saving Transition?

Two things, both of which need an explicit policy. In spring an hour of local time never occurs, so certain wall-clock values are invalid; in autumn an hour repeats, so certain values are ambiguous and match two distinct instants.

Date libraries differ in how they resolve these, and the defaults are rarely what a business wants, so developers who leave the choice implicit have made it by accident. Python's standard library exposes a fold flag to distinguish the first and second pass through a repeated hour; Java offers explicit resolver strategies for both gaps and overlaps; many other libraries silently pick the later instant and move on. Decide deliberately whether a 02:30 alarm on a spring-forward morning should fire at 01:30, at 03:30 or not at all, and write a test that pins the answer.

  • Gap: the local time does not exist. Shift forward, shift back, or reject — but choose, and document it.
  • Overlap: the local time occurs twice. Prefer the first occurrence for billing and the second for reminders, or record the UTC instant so the question never arises.
  • Non-hourly shifts: Lord Howe Island moves by thirty minutes, so a hard-coded one-hour adjustment is wrong there.
  • Southern hemisphere: transitions fall in October and April, and a summer rule spans the new year, breaking any assumption that a rule sits inside one calendar year.

Which regions still transition at all, and when, is covered in daylight saving time around the world.

How Do You Keep tzdata Current?

Update it wherever it is embedded, which is usually more places than developers expect. New releases ship several times a year, and every copy in the stack that misses one will quietly render the wrong local time for the affected region.

  • The operating system: a tzdata package that container images inherit and then freeze. A pinned base image is a pinned rule set.
  • The language runtime: the JVM carries its own copy; Python may use the system database or a separate data package; Node depends on how its internationalisation data was built.
  • The database: MySQL needs its zone tables loaded explicitly, and PostgreSQL ships its own copy that follows the server version.
  • The client: browsers and mobile devices resolve zones from their own data, so a stale phone disagrees with a current server.

Treat a tzdata release like a security patch: applied everywhere, on a schedule, not when a support ticket arrives. Releases come this often because governments keep amending their rules, sometimes with only days of notice.

Time Zones for Developers: A Testing Checklist

Test against the zones that break naive code rather than the ones near your office. Four or five well-chosen identifiers catch nearly every class of defect that time zones for developers can produce, and they catch it before production does.

  • Pacific/Chatham: a quarter-hour offset at UTC+12:45 that also observes daylight saving.
  • Asia/Kathmandu: UTC+05:45, which breaks integer-hour assumptions immediately.
  • Australia/Lord_Howe: the only zone with a thirty-minute seasonal shift.
  • America/Sao_Paulo: a zone that observed daylight saving until 2019 and then stopped, so old and new timestamps follow different rules.
  • Pacific/Apia: Samoa's 2011 jump across the date line, where 30 December simply does not exist.

Developers should also verify that their code reads the client zone from the environment properly. In browsers, the internationalisation API returns a proper identifier, while the older offset method reports minutes with an inverted sign — a mismatch that produces values wrong in both magnitude and direction. The POSIX TZ convention inverts signs in the same way, which is why Etc/GMT+5 means five hours behind Coordinated Universal Time.

Mistakes Developers Repeat

The same handful of errors appear in almost every codebase. Adding 86,400 seconds to get tomorrow is the most common: on a transition day the result is an hour out, because a calendar day is not always twenty-four hours long.

Others follow a pattern that any experienced developer recognises. Running application servers on local time rather than UTC makes logs unorderable across a transition. Comparing a date-only value against a timestamp without stating a zone shifts results by a day at the boundary. Attempting to model leap seconds in application code is wasted effort, since the platform below almost certainly smooths or ignores them. And converting a time by mental arithmetic instead of using the time zone converter is where most support tickets begin.

Conclusion

Time zones for developers reduce to four disciplines: store UTC instants with a zone identifier rather than an offset, refuse to treat abbreviations as data, define explicit behaviour for daylight saving gaps and overlaps, and keep tzdata updated in every layer that carries a copy. Do those and the category of bug largely disappears. Check any identifier and its live local time in the time zone browser, look up a code in the abbreviations table, or start from the timezones.now homepage.