Guide

A time value looks simple, but it hides three questions: which moment, which place and which calendar. Most time bugs come from answering one of them by accident. The fix is to keep each part separate: store the moment, keep the place as a name, and convert only when you show the time to someone.

The big idea

A Unix timestamp counts seconds since 1970-01-01T00:00:00Z, the Unix epoch. It names one exact moment and has no time zone. Every reader sees the same number, so store moments this way.

A time zone is a place's rule for local time. Its offset from UTC can change over the year, because of daylight saving time (DST). Store a zone by its IANA name, such as America/New_York, not a fixed offset like -05:00. The offset in winter is not the offset in summer.

Concept

UTC: the standard reference time with a zero offset. Offset: how far local time is ahead of or behind UTC, such as -05:00. IANA zone name: a name like Europe/Berlin that maps to the full set of offset rules for that place.

Walking through one conversion

Take one moment, 14:30 UTC on 15 January 2026, and show it in New York. New York is on standard time in January, which is UTC minus 5 hours.

Python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

moment = datetime(2026, 1, 15, 14, 30, tzinfo=timezone.utc)
print(moment.astimezone(ZoneInfo("America/New_York")))

Output:

2026-01-15 09:30:00-05:00

The stored value stays 14:30 UTC. Only the display changes. In July, the same zone would show a different offset, and the code would not change.

Rules that prevent most bugs

  • Store instants in UTC. Keep them as a 64-bit integer or a TIMESTAMPTZ-style column.
  • Send times as RFC 3339 / ISO 8601 with an offset. For example, 2026-03-08T14:30:00Z. Never send 03/08/26 2:30, which can be read two ways.
  • Convert to local time only for display. Use the user's IANA zone.
  • Store future local events as local time plus a zone. A meeting at 09:00 every Monday in Berlin should stay at 09:00 local time when the clocks change. As a UTC instant it would move by an hour.

Daylight saving creates impossible and repeated times

Spring forward skips an hour. A job scheduled for 02:30 local time on that night never runs. Fall back repeats an hour. The same 01:30 happens twice, and a job can run twice. A daily report that assumes every day has 24 hours is wrong twice a year.

Schedule infrastructure jobs in UTC. Use local time only when a human's wall clock is the point, such as "remind me at 9:00 every day".

Seconds vs milliseconds

Unix time in seconds is about 10 digits today, around 1.8 billion. JavaScript's Date.now() returns 13 digits, milliseconds. Mixing them gives dates in January 1970 when you read seconds as milliseconds, and dates tens of thousands of years ahead when you read milliseconds as seconds. Check the digit count when you read a timestamp from a new source.

Leap seconds and monotonic clocks

UTC sometimes adds a leap second, so one minute has 61 seconds. Leap seconds have caused real outages. The June 2012 leap second triggered a Linux kernel bug that pinned CPUs at many large sites. Google and AWS now "smear" leap seconds over hours instead of inserting them at once.

Wall clocks can also jump backward when NTP corrects them. So measure durations with a monotonic clock, which only moves forward. Use time.monotonic() in Python or System.nanoTime() in Java. Go's time.Now() includes a monotonic reading for elapsed-time math.

Common mistakes

  • Storing local time without a zone. The same string can mean two different moments.
  • Fixed offsets instead of zone names. The offset changes with DST, and the zone rules can change too.
  • Using wall-clock time to measure how long something took. A clock correction can make the duration negative.
  • Scheduling daily jobs in local time. They skip or repeat on DST days.

Check yourself

  1. A meeting is stored as 09:00 UTC each week. Why does it move an hour in the local time after DST starts? Because UTC does not change with DST, but the local offset does. The local time shifts while the UTC instant stays put.
  2. Your code measures a request as negative 2 seconds. Which clock did it use? A wall clock, which the system corrected in the middle of the request. Use a monotonic clock for durations.

Key takeaways

  • Store moments as UTC. Store places as IANA zone names. Convert only for display.
  • Send RFC 3339 timestamps with an explicit offset.
  • DST makes some local times impossible and others repeated. Schedule jobs in UTC.
  • Check whether a number is seconds or milliseconds, and measure durations with a monotonic clock.

Further learning

Tom Scott's famous talk on time zones is funny and accurate, and a good reminder never to write your own time zone code.

The Problem with Time & Timezones - ComputerphileComputerphile
Falsehoods programmers believe about timeOver the past couple of years [I have spent a lot of time][checklist] debugging other engineers' test code. This was interesting work, occasionally frustrating but always informative. One might not...Tumblr

For how clocks are ordered across machines, see clocks and ordering.