Handling Daylight Saving Time Transitions

May 28, 20266 min read

Twice a year, many regions shift their clocks for daylight saving time (DST). These transitions create two recurring headaches for developers: a missing hour in spring and a repeated hour in fall. Both situations break assumptions that time moves forward at a constant rate, and both cause real bugs — missed scheduled jobs, duplicate log entries, incorrect billing intervals, and broken time-range queries.

The Two Transition Problems

Spring Forward: The Missing Hour

When clocks jump from 2:00 AM to 3:00 AM, the hour between 2:00 and 3:00 never exists. A scheduled task set for 2:30 AM simply does not run.

from datetime import datetime
import pytz

eastern = pytz.timezone('US/Eastern')
# March 12, 2026 — spring forward
t = eastern.localize(datetime(2026, 3, 12, 2, 30), is_dst=None)
# AmbiguousTimeError: 2:30 AM does not exist

Fall Back: The Repeated Hour

When clocks fall from 2:00 AM back to 1:00 AM, the hour between 1:00 and 2:00 occurs twice. Without disambiguation, a timestamp like 2026-11-01 01:30:00 is ambiguous — is it the first occurrence (EDT) or the second (EST)?

# November 1, 2026 — fall back
t1 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=True)   # EDT
t2 = eastern.localize(datetime(2026, 11, 1, 1, 30), is_dst=False)  # EST
# t1 and t2 are 1 hour apart in UTC, but display the same local time

Strategies for Handling Transitions

Store UTC, Display Local

The single most effective strategy: always store and compute timestamps in UTC. UTC has no DST transitions — it is a continuous, monotonically increasing timeline. Convert to local time only at the display layer.

// Store in UTC
const utcTimestamp = new Date().toISOString(); // "2026-05-28T14:30:00.000Z"

// Display in local timezone
const localString = new Date(utcTimestamp).toLocaleString('en-US', {
  timeZone: 'America/New_York'
});

This eliminates the ambiguous-timestamp problem entirely. If someone asks "what happened at 1:30 AM on November 1?" you answer by querying UTC ranges, not local strings.

Handle Scheduling with UTC or DST-Aware Libraries

For scheduled jobs, do not use local-time cron expressions if your system runs in a DST-affected timezone. Instead:

  • Schedule in UTC (0 7 * * * for 7:00 AM UTC, every day)
  • Or use a DST-aware scheduler (like java-time's ZonedDateTime in Java or Python's zoneinfo) that adjusts execution time automatically
from zoneinfo import ZoneInfo
from datetime import datetime

tz = ZoneInfo('US/Eastern')
# This correctly handles spring forward — job runs at 3:00 AM EDT
next_run = datetime(2026, 3, 12, 2, 30, tzinfo=tz)
print(next_run)  # 2026-03-12 03:30:00-04:00 (skipped to 3:30 AM EDT)

Use Interval Arithmetic, Not Wall-Clock Differences

When calculating the duration between two events, never subtract local timestamps. DST transitions make wall-clock differences incorrect.

# Wrong: subtracts local times (misses the spring-forward gap)
duration = local_end - local_start  # May be off by 1 hour

# Right: subtract UTC times or use aware datetimes
duration = utc_end - utc_start  # Always correct

Common Bugs and Fixes

BugCauseFix
Daily report skips a dayReport scheduled at 2:30 AM local — skipped during spring forwardSchedule in UTC or shift to 3:00 AM local
Duplicate entries in logsFall-back creates two 1:00 AM hours; both written with same local stringUse UTC timestamps in logs
Hourly rate overcharges3-hour window spans fall back; naive counting treats 2 AM as a new hourUse UTC intervals for billing
Time-range query returns duplicatesBETWEEN on local timestamps matches the repeated hour twiceQuery on UTC column instead
Countdown timer jumpsTimer computes target - Date.now() using local Date objectsUse Date.UTC() or epoch milliseconds

DST Rules Vary Globally

Not all regions observe DST, and those that do change their rules periodically:

  • United States: second Sunday in March to first Sunday in November
  • European Union: last Sunday in March to last Sunday in October
  • Australia (southern hemisphere): first Sunday in October to first Sunday in April (opposite season)
  • Japan, India, China: no DST

Never hardcode transition dates. Always use a timezone database (IANA tzdata) that is regularly updated. Most operating systems and language runtimes ship with tzdata and update it periodically.

Key Takeaways

  • Spring forward creates a missing hour; fall back creates a repeated hour — both break naive time arithmetic.
  • Store and compute in UTC; convert to local time only for display.
  • Schedule jobs in UTC or with DST-aware libraries to guarantee execution.
  • Use UTC intervals for billing, logging, and time-range queries to avoid duplicate or missing records.
  • Never hardcode DST transition dates — rely on the IANA timezone database.

Try It Yourself

Working with timestamps across timezones? The Timestamp Converter and Timezone Converter help you visualize transitions and convert between UTC and local times accurately.