The Developer's Guide to Time Zones and UTC: Stop Getting Burned by Dates
The Developer's Guide to Time Zones and UTC: Stop Getting Burned by Dates
Date and time handling is one of the most reliable sources of bugs in software. It's an area where things that seem straightforward — "just store the current time" — hide layers of complexity that only reveal themselves in production: meetings scheduled at the wrong hour, timestamps that shift after a server migration, or logs that appear to go backwards when daylight saving time ends.
This guide explains the core concepts every developer needs — UTC, Unix timestamps, ISO 8601, DST, and time zones — and gives you a practical framework for handling dates correctly in any application.
UTC: The Universal Reference
Coordinated Universal Time (UTC) is the primary time standard by which the world regulates clocks and time. It is the successor to Greenwich Mean Time (GMT) and serves as the zero reference point from which all other time zones are calculated as positive or negative offsets.
UTC has no daylight saving time (DST). It does not change. It is consistent, unambiguous, and universal.
The golden rule of time zone handling: Store all timestamps in UTC. Display them in the user's local time zone.
If you store a timestamp as "3:00 PM" without a time zone, you have no idea whether that means 3:00 PM in Paris, New York, or Tokyo. Store it as "2026-06-02T15:00:00Z" (UTC) and you have an absolute, unambiguous moment in time that can be converted to any time zone on display.
UTC Offsets and Time Zones
A UTC offset expresses how many hours and minutes a time zone is ahead of or behind UTC:
UTC+0— London (winter), Iceland, GhanaUTC+1— Paris, Berlin, Lagos (winter)UTC+2— Cairo, Helsinki, South AfricaUTC+5:30— India (note the non-integer offset)UTC-5— New York (winter, Eastern Standard Time)UTC-8— Los Angeles (winter, Pacific Standard Time)
"Time zone" and "UTC offset" are not the same thing. A time zone is a region with a specific set of rules, including whether it observes DST and when it transitions. New York uses UTC-5 in winter but UTC-4 in summer. Simply knowing the offset is not enough to determine the current local time — you need to know the IANA time zone name.
IANA Time Zone Database
The authoritative source of time zone data is the IANA Time Zone Database (also called the "Olson database"), maintained at iana.org/time-zones. It defines time zone names like America/New_York, Europe/London, Asia/Tokyo, Africa/Cairo.
These names are used in:
- JavaScript's
Intl.DateTimeFormatAPI - Python's
zoneinfomodule andpytzlibrary - Java's
java.time.ZoneId - PostgreSQL and MySQL
TIMESTAMP WITH TIME ZONEcolumns - Linux's
/usr/share/zoneinfodirectory
Always use IANA names, not abbreviations. The abbreviation "CST" is ambiguous — it could mean Central Standard Time (UTC-6, USA), China Standard Time (UTC+8), or Cuba Standard Time (UTC-5). IANA names are unambiguous.
Daylight Saving Time (DST)
Daylight Saving Time is the practice of advancing clocks by 1 hour in summer to extend evening daylight. Approximately 70 countries observe DST, but not all, and those that do transition on different dates.
DST creates two notorious problems:
The clock-forward gap: In spring (in the USA, at 2:00 AM the second Sunday of March), clocks jump to 3:00 AM. The hour from 2:00–3:00 AM does not exist. Scheduling a recurring job or event during this hour will either skip or behave unexpectedly.
The clock-backward fold: In autumn (first Sunday of November, clocks fall back from 2:00 AM to 1:00 AM), the hour from 1:00–2:00 AM occurs twice. An unqualified local time of "1:30 AM" is ambiguous — which occurrence?
Practical consequences:
- Cron jobs scheduled at "2:30 AM" may run twice or not at all during DST transitions
- User-facing "schedule a meeting at 3 PM" features must handle the fact that "3 PM" on the DST transition day is ambiguous
Solution: Always schedule recurring events in UTC or in an aware datetime that stores both the IANA time zone and the UTC equivalent.
Unix Timestamps
A Unix timestamp (also called a POSIX timestamp or epoch time) is the number of seconds elapsed since January 1, 1970, 00:00:00 UTC, not counting leap seconds.
June 2, 2026 at 12:00:00 UTC = Unix timestamp 1748865600
Unix timestamps are:
- Unambiguous — a single integer represents one exact moment in time
- Time-zone agnostic — no DST effects, no offset confusion
- Easy to compare — subtract two timestamps to get the duration in seconds
- Easy to store — just an integer column in any database
Current timestamp in various languages:
// JavaScript
Math.floor(Date.now() / 1000) // seconds
Date.now() // milliseconds
// Python
import time; int(time.time())
// Go
time.Now().Unix()
// SQL
EXTRACT(EPOCH FROM NOW()) -- PostgreSQL
UNIX_TIMESTAMP() -- MySQL
Convert any timestamp instantly with our Unix Timestamp Converter tool.
ISO 8601 Format
ISO 8601 is the international standard for representing dates and times as strings. It eliminates ambiguity caused by regional date conventions (is 06/04/26 June 4, April 6, or 2026-06-04?).
ISO 8601 examples:
2026-06-02 -- Date only
2026-06-02T15:30:00 -- Local date-time (no time zone info)
2026-06-02T15:30:00Z -- UTC time (Z = UTC)
2026-06-02T15:30:00+02:00 -- With UTC offset
Always use the Z suffix (UTC) or a full UTC offset when storing timestamps. A date-time string with no time zone indication is ambiguous.
ISO 8601 strings sort lexicographically — "2026-12-01" > "2026-01-01" — making them safe to sort as strings in databases and filesystems.
Time Zone Handling in JavaScript
JavaScript's Date object stores time internally as milliseconds since the Unix epoch (UTC). The confusion arises from display methods:
const now = new Date();
// UTC methods — always UTC, no DST surprises
now.getUTCFullYear()
now.getUTCMonth()
now.getUTCHours()
now.toISOString() // "2026-06-02T15:30:00.000Z"
// Local methods — use system time zone
now.getFullYear()
now.getMonth()
now.getHours()
now.toString() // "Mon Jun 02 2026 17:30:00 GMT+0200"
For robust internationalization, use the Intl.DateTimeFormat API:
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
});
formatter.format(new Date());
// "June 2, 2026 at 11:30 AM EDT"
For complex date arithmetic across time zones, use the Temporal API (currently a TC39 Stage 3 proposal with polyfills available) or established libraries like date-fns-tz or luxon.
Database Best Practices
PostgreSQL: Use TIMESTAMPTZ (timestamp with time zone). Despite the name, PostgreSQL stores the value in UTC and converts to the session time zone on display. Never use TIMESTAMP (without time zone) for user-facing time data.
MySQL: DATETIME stores without time zone; TIMESTAMP stores in UTC and converts on retrieval. Use TIMESTAMP for most cases.
MongoDB: All Date objects in MongoDB are stored as milliseconds since the Unix epoch in UTC.
SQLite: Store as an INTEGER (Unix timestamp) or TEXT (ISO 8601 UTC string) — SQLite has no native datetime type.
Using the Timezone Converter Tool
Our Timezone Converter tool lets you convert any date and time between any two IANA time zones instantly. It correctly accounts for DST transitions, making it useful for scheduling international meetings, understanding server log timestamps, or confirming what a given UTC time corresponds to in a specific city.
Summary
Correct time zone handling is a mark of a mature developer and a well-built application. The rules are simple once internalized:
Key takeaways:
- Store everything in UTC — display in the user's local time zone
- Use IANA time zone names (
America/New_York), not abbreviations (EST) - Unix timestamps are the safest, most portable way to store a moment in time
- ISO 8601 with Z suffix for human-readable UTC strings
- DST creates gaps and folds — schedule recurring tasks in UTC
- Convert between any time zones with the Timezone Converter tool