Time Zone Math: UTC, DST, and Offsets Explained
What UTC actually is, how IANA timezone names differ from offsets, how DST transitions are scheduled, and the most common bugs in date and time code.
Most programmers first encounter timezone bugs the hard way: a user in Mumbai reports that their appointment shows up at the wrong time, or a cron job fires an hour late after a DST transition, or a database stores "2024-03-10 02:30:00 America/New_York" — a timestamp that literally does not exist. These bugs are hard to reproduce, hard to explain, and hard to fix, because the underlying model is more complicated than most people assume.
What UTC actually is
UTC (Coordinated Universal Time) is the reference point that all civil time is defined relative to. It's maintained by a network of about 450 atomic clocks distributed across national metrology labs, coordinated by the Bureau International des Poids et Mesures in Paris. UTC doesn't observe daylight saving time. It doesn't belong to any country. It's the closest thing we have to an absolute time reference for everyday purposes.
UTC replaced Greenwich Mean Time (GMT) as the international standard in 1960, though the two are often used interchangeably in casual contexts. The difference is technical: GMT is an astronomical time standard based on the Earth's rotation, while UTC is an atomic time standard that gets occasional leap seconds to stay within 0.9 seconds of the Earth's rotation. For software purposes, the distinction rarely matters — but the naming does. Using "GMT" in code or UI creates ambiguity about whether you mean the timezone (Europe/London without DST) or the time standard. Prefer "UTC" when you mean the reference point.
Timezone names are not offsets
This is the single most common source of timezone bugs. An offset like UTC-5 tells you a fixed relationship to UTC at a specific moment. A timezone name like "America/New_York" is a set of rules that says "use UTC-5 during standard time and UTC-4 during daylight saving time, switching on these specific dates." The offset changes; the name stays the same.
Storing an offset instead of a timezone name throws away information. If you record that a user is at UTC-5, you can't tell whether they're in New York (which will shift to UTC-4 in March), Bogotá (which stays at UTC-5 year-round), or Cancún (which used to observe DST but stopped in 2022). When DST transitions happen, the offset-only record becomes wrong with no way to correct it.
The fix is straightforward: always store the IANA timezone name ("America/New_York", "Asia/Kolkata", "Pacific/Auckland"). Derive the offset when you need it, using a library or Intl.DateTimeFormat. The offset is computed, not stored.
The IANA timezone database
Every major operating system, programming language, and browser uses the same timezone database: the IANA Time Zone Database, sometimes called "tzdata" or the "Olson database" after its original maintainer, Arthur David Olson. It's maintained by a small group of volunteers and published several times a year as governments announce changes to their timezone rules.
The database uses a region/city naming convention: "America/Chicago", "Europe/Berlin", "Asia/Tokyo". Each entry contains the complete history of UTC offset changes for that region — including historical DST rules, political changes (like Samoa skipping December 30, 2011 entirely when it moved across the International Date Line), and one-off adjustments. The database currently tracks over 590 zones.
When your code calls Intl.supportedValuesOf('timeZone') in a browser or pytz.all_timezonesin Python, you're querying this database. It gets updated through OS patches and runtime updates, which is why a timezone conversion that works on your machine might produce a different result on a server running an older tzdata version.
How DST transitions work
Daylight saving time is not a universal rule. Each country (and sometimes each region within a country) decides independently whether to observe it, when to start and stop, and how much to shift. The decisions are made by national legislatures, and they change more often than you'd expect.
The United States shifts clocks forward on the second Sunday of March and back on the first Sunday of November. The European Union shifts on the last Sundays of March and October. These dates are different, which means for a few weeks each year, the offset between New York and London is four hours instead of five. Australia shifts in the opposite calendar direction — their "spring forward" is in October and "fall back" is in April. Countries near the equator generally don't observe DST at all because day length barely varies.
The transition itself creates two kinds of edge cases. During the "spring forward" in the US, 2:00 AM jumps directly to 3:00 AM. The time 2:30 AM does not exist that day. Any code that tries to construct a datetime for 2:30 AM in that timezone on that date has an invalid input. During "fall back," 1:00 AM to 2:00 AM happens twice. A timestamp of 1:30 AM on that day is ambiguous — was it the first 1:30 AM (daylight time) or the second (standard time)?
Good datetime libraries handle these cases explicitly. Intl.DateTimeFormat in JavaScript will resolve ambiguous times to the later occurrence by default. Other libraries let you specify a disambiguation strategy. The important thing is knowing these cases exist — most timezone bugs happen because a developer assumed every day has 24 hours and every local time occurs exactly once.
Common bugs in date and time code
Adding 24 hours instead of 1 day.If you want "the same time tomorrow," adding 86,400 seconds (24 hours) to a UTC timestamp gives you the wrong local time on DST transition days. Adding one calendar day in the local timezone is correct — "tomorrow at 9 AM" might be 23 or 25 hours away depending on the transition.
Parsing dates without timezone context.The string "2024-06-15T14:00:00" without a timezone suffix is ambiguous. JavaScript's Dateconstructor parses it as local time in some contexts and UTC in others, depending on the exact format and runtime. Always include an explicit timezone or offset when parsing date strings: "2024-06-15T14:00:00Z" for UTC, or "2024-06-15T14:00:00-04:00" for a specific offset.
Storing local time in the database.If your database column is a "datetime without timezone" type and you insert "2024-06-15 14:00:00", you've stored a local time with no way to recover which timezone it referred to. Use UTC for storage and convert to the user's timezone on display. If you need to preserve the original timezone intent (like a recurring meeting at "2 PM New York time"), store the IANA timezone name alongside the time.
Comparing timestamps across timezones without normalizing.Two timestamps in different timezones can only be meaningfully compared after converting both to the same reference — UTC is the conventional choice. Comparing the string "2024-06-15 14:00 America/New_York" with "2024-06-15 14:00 America/Chicago" and concluding they're simultaneous is a bug — there's actually an hour between them.
Caching timezone offsets. If your code computes an offset once at startup and reuses it, any DST transition that occurs during the process lifetime will produce wrong results. Compute the offset at the point of use, not ahead of time.
A practical mental model
Think of time as having three layers. The first layer is the physical instant — a point on the timeline, best represented as a UTC timestamp or Unix epoch milliseconds. The second layer is the civil representation — "June 15, 2024 at 2:00 PM" — which is meaningless without the third layer: the timezone rules that map between the first and second layers.
Most bugs happen when code treats the civil representation as if it were the physical instant, or when it strips the timezone rules and tries to reconstruct them later. Keep all three layers explicit in your data model and conversions become mechanical rather than magical.
When you need to check what a specific time looks like in another timezone, the Timezone Converter handles the math for you — including DST offsets, UTC differences, and day boundary crossings. It runs entirely in your browser using the same IANA database your code depends on, so what you see matches what your users will experience.