Time Zones vs UTC Offsets

· timezonesfundamentals

An offset like +05:30 tells you what a clock read. Asia/Kolkata tells you which clock, and what it will read next year. That difference matters for future dates.

These two things are constantly confused, and the confusion produces a specific bug: future dates that are correct when saved and wrong when they arrive.

The distinction

A UTC offset is a fixed difference from UTC: +05:30, -08:00, +00:00. It is a number. It tells you what a clock read relative to UTC at one moment.

A time zone is a geographic region together with the complete history and current rules of the offsets it has observed: Asia/Kolkata, America/New_York, Europe/London. It is identified by an IANA name and it changes over time.

America/New_York is -05:00 in January and -04:00 in July. Both are the same zone.

Why the difference matters

Consider storing a meeting for 9 a.m. on 3 November 2024 in New York.

Stored as an offset — you compute 2024-11-03T09:00:00-04:00 today and save the resulting instant. But daylight saving ends that morning; at 9 a.m. New York is actually on -05:00. Your meeting fires at 8 a.m. local.

Stored as local time plus zone — you save ("2024-11-03T09:00", "America/New_York") and resolve it to an instant at render time. It fires at 9 a.m., because the resolution uses whatever the rules actually are.

The second form survives rule changes. The first does not.

Rules change more often than you think

Zone rules are political decisions, and the IANA database is updated several times a year. Recent examples:

  • Egypt reintroduced daylight saving in 2023 after abandoning it in 2014.
  • Chile has changed its DST dates repeatedly, sometimes with a few weeks’ notice.
  • Morocco switched to permanent UTC+1 in 2018, with a Ramadan exception each year.
  • Samoa skipped 30 December 2011 entirely, crossing the date line. That date does not exist there.
  • Russia abolished DST in 2011, then changed zone boundaries again in 2014.

Any instant computed in advance from an offset becomes wrong the moment a rule changes. An instant resolved from a zone name at display time does not.

When each is correct

Store the offset when recording something that already happened and you want to preserve what the local clock read. A log entry of 2024-03-10T11:30:00+05:30 is a complete, permanent historical record. The offset is a fact about the past and cannot become wrong.

Store the zone when the local wall-clock time is the thing that matters and the event is in the future. Meetings, alarms, recurring jobs, business hours, delivery windows.

Often you want both: the UTC instant for ordering and querying, plus the zone for reconstructing what the user meant.

CREATE TABLE appointments (
  starts_at    TIMESTAMPTZ  NOT NULL,   -- the instant, for ordering
  local_time   TIME         NOT NULL,   -- what the user typed
  time_zone    TEXT         NOT NULL    -- 'America/New_York'
);

The abbreviation trap

Zone abbreviations are not identifiers and must never be stored or parsed:

  • CST is US Central (−06:00), China Standard (+08:00) and Cuba Standard (−05:00).
  • IST is India (+05:30), Ireland (+01:00) and Israel (+02:00).
  • BST is British Summer (+01:00) and, historically, British Standard.
  • AMT has meant both Amazon and Armenia time.

They are ambiguous by construction. Use them for display only.

The POSIX sign reversal

POSIX TZ strings invert the sign relative to every other convention:

TZ="GMT+5"   # five hours BEHIND UTC — i.e. UTC-05:00

This is genuinely backwards and catches everyone once. IANA names avoid it entirely; prefer TZ="America/New_York".

Fixed-offset zones that are not places

The IANA database includes Etc/GMT+5 and similar. These are fixed offsets with no DST, and they carry the POSIX sign reversal — Etc/GMT+5 is UTC−05:00. Use them only when you genuinely mean a fixed offset with no location, and be aware of the sign.

UTC itself is the safe fixed-offset choice.

Offsets that are not whole hours

Assuming offsets are whole hours is a common and wrong simplification:

  • Asia/Kolkata — UTC+05:30
  • Asia/Kathmandu — UTC+05:45
  • Australia/Adelaide — UTC+09:30 (and +10:30 in summer)
  • Pacific/Chatham — UTC+12:45
  • Pacific/Kiritimati — UTC+14:00, the furthest ahead

Anything that stores offsets as an integer number of hours breaks for a substantial fraction of the world’s population. Store minutes.

Practical checklist

  1. Store instants as UTC or an epoch integer.
  2. Store the IANA zone name alongside any user-entered local time.
  3. Never store or parse abbreviations.
  4. Never hard-code an offset for a named place.
  5. Keep tzdata updated across OS, runtime, database and containers.
  6. Test with Asia/Kathmandu (+05:45) and Australia/Sydney (southern hemisphere DST).

Current offsets and this year’s DST dates for all zones are on the time zone reference.

Related guides