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

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:

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

References