Dates, Time Zones, and Daylight Saving
Time looks simple until software crosses a border or a season. A meeting booked for 09:00 in New York lands at 08:00 for half the year. A nightly job scheduled for 02:30 silently skips one night in March and runs twice in November. A birthday stored as midnight UTC shows up a day early for every user west of Greenwich. None of these are exotic edge cases; they happen every year, in every product that handles dates for real people.
The fix is mostly conceptual. There are several different kinds of time value — an exact instant, a wall-clock time in a place, a calendar date, a duration — and most bugs come from storing or calculating one as if it were another. Once each value has the right type, libraries handle the hard parts.
TL;DR
- Store things that happened as UTC instants; store future local events as local time plus an IANA time zone such as
America/New_York. - Store calendar dates (birthdays, due dates) as dates, without a time or zone.
- An offset (
-05:00) is not a time zone; zones have rules that change with daylight saving and with law. - Daylight saving creates local times that never happen (gaps) and times that happen twice (overlaps).
- "Add one day" and "add 24 hours" are different operations; choose deliberately.
- Format for display with the viewer's locale and zone (
Intl, Temporal), and test around DST transitions, month ends, and leap days.
Quick Example
The same 09:00 meeting in New York, two days before and one day after daylight saving starts on 8 March 2026:
The local time is constant; the UTC time moves by an hour. A recurring meeting stored as "14:00 UTC every weekday" would drift to 10:00 local after the change. The meeting's real definition is "09:00 in America/New_York", and that is what should be stored.
Core Concepts
Kinds of Time Value
Time Zones Versus Offsets
An offset is a fixed difference from UTC, such as -05:00. A time zone is a named region — America/New_York, Europe/Berlin, Asia/Kolkata — with a history of offsets and daylight-saving rules, maintained in the IANA Time Zone Database (tzdb). Governments change those rules regularly, so the database ships several updates a year. Store zone names, not offsets, and keep operating systems and runtimes updated so they carry current rules.
Avoid abbreviations such as EST or IST in data: they are ambiguous (IST is used for India, Ireland, and Israel) and do not say whether daylight saving applies.
Daylight Saving Gaps and Overlaps
When clocks spring forward, an hour of local time never happens; when they fall back, an hour happens twice.
Libraries need a policy for both cases. The common default moves times in a gap forward and picks the earlier of two overlapping times; whatever the policy, decide it explicitly for anything that matters, such as billing cut-offs or scheduled jobs.
Calendar Versus Elapsed Arithmetic
Across a DST change, "same time tomorrow" and "24 hours later" differ:
Python adds to the wall clock on zone-aware values, so the first result is the same local time — only 23 real hours later. Converting to UTC first adds true elapsed time. Subscriptions, reminders, and "due tomorrow" use calendar arithmetic; timeouts, rate limits, and token expiry use elapsed time.
Dates in JavaScript
Temporal
The legacy Date object is a single instant with confusing parsing and local-time methods. The Temporal API replaces it with distinct types — Temporal.Instant, Temporal.ZonedDateTime, Temporal.PlainDate, Temporal.PlainDateTime, Temporal.Duration — that make the kind of value explicit. It is arriving in browsers and runtimes; use a polyfill where it is not yet available.
Formatting With Intl
Format instants for the viewer with Intl.DateTimeFormat, passing both a locale and a time zone:
The browser reports the user's zone with Intl.DateTimeFormat().resolvedOptions().timeZone; save it as a default and let users change it.
Parsing Pitfalls
new Date("2026-03-09") treats a date-only string as UTC midnight, which displays as the evening of 8 March anywhere in the Americas. new Date("2026-03-09T00:00") — a date-time without an offset — is parsed as local midnight. For calendar dates, keep them as YYYY-MM-DD strings or Temporal.PlainDate and never convert them to instants.
Storing Time in Databases
Because timestamptz does not keep the zone, a future local event needs two columns:
Compute the instant when you need it — for sorting, reminders, or calendar feeds — so that a change in daylight-saving law moves the instant, not the meeting.
Scheduled Jobs and Daylight Saving
A job scheduled at 02:30 local time does not run on the spring-forward night and may run twice when clocks fall back. Options:
- Run schedulers in UTC, and translate user-facing schedules into UTC at each run.
- Avoid the 01:00–03:00 local window for jobs that must run exactly once a day.
- Make jobs idempotent with a per-day key, so a double run does no harm — see Background Jobs.
Best Practices
Keep Infrastructure in UTC
Servers, databases, and logs should run and record in UTC. Convert to a local zone only at the edges: when parsing a user's input and when formatting for display.
Use APIs That Carry the Offset
Exchange instants in APIs as RFC 3339 strings with Z or an explicit offset (2026-03-09T13:00:00Z). Send calendar dates as YYYY-MM-DD, and send zone names separately when the receiver needs local rules.
Rely on Maintained Libraries
Use Temporal or Intl in JavaScript, zoneinfo in Python, java.time in Java, and NodaTime in .NET. Hand-written offset arithmetic is wrong twice a year.
Show the Zone When It Matters
For events that involve people in different places, show the time in the viewer's zone and name the event's zone: "9:00 AM New York time (3:00 PM your time)".
Test Around the Edges
Freeze the clock in tests and include DST transitions, month ends, leap days (29 February 2028), year boundaries, and zones with unusual offsets such as Asia/Kathmandu (+05:45). Run the suite with a non-UTC TZ to catch hidden local-time assumptions. See Test Data and Environment Management.
Common Mistakes
Storing Local Times Without a Zone
Treating a Day as 86,400 Seconds
Calendar Dates Converted to Instants
Saving an Offset as the User's Time Zone
FAQ
Should I store all times in UTC?
Store instants — things that happened or exact moments — in UTC. Do not force everything into UTC: future events defined in local time need the local time and zone, and calendar dates should stay dates. Converting a future local event to UTC at save time bakes in today's daylight-saving rules.
What is the difference between a time zone and an offset?
An offset is a fixed difference from UTC at one moment, such as +01:00. A time zone such as Europe/Paris is a set of rules that determines the offset at any moment, including daylight saving and historical changes. One zone has different offsets during the year.
How do I get a user's time zone?
In the browser, Intl.DateTimeFormat().resolvedOptions().timeZone returns an IANA name such as Europe/London. Save it as the default, let users override it in settings, and use it for server-side tasks such as emails and reminders.
What happens to a job scheduled at 2:30 AM on a daylight-saving day?
In zones that spring forward at 02:00, 2:30 AM does not exist that night, so a local-time scheduler may skip the job; on the fall-back night it may run twice. Schedule in UTC, avoid the transition window, or make the job idempotent.
Why does new Date("2026-03-09") show 8 March?
JavaScript parses date-only ISO strings as UTC midnight. Displayed in any zone west of UTC, that instant is still the previous evening. Keep calendar dates as plain dates instead of Date objects.
Do I still need a date library?
Less than before. Intl handles formatting, and Temporal covers zones, calendar arithmetic, and durations. Libraries such as date-fns or Luxon remain useful where Temporal is not yet available, or for helpers such as relative-time formatting and parsing user input.
Related Topics
- Internationalization (i18n) — Locale-aware formatting beyond dates
- JavaScript — Date, Intl, and Temporal in the language
- Python — zoneinfo and datetime arithmetic
- PostgreSQL — timestamptz, date, and AT TIME ZONE
- Background Jobs — Scheduling work safely across time zones
- Test Data and Environment Management — Freezing and controlling time in tests