Handling Daylight Saving Time Transitions
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'sZonedDateTimein Java or Python'szoneinfo) 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
| Bug | Cause | Fix |
|---|---|---|
| Daily report skips a day | Report scheduled at 2:30 AM local — skipped during spring forward | Schedule in UTC or shift to 3:00 AM local |
| Duplicate entries in logs | Fall-back creates two 1:00 AM hours; both written with same local string | Use UTC timestamps in logs |
| Hourly rate overcharges | 3-hour window spans fall back; naive counting treats 2 AM as a new hour | Use UTC intervals for billing |
| Time-range query returns duplicates | BETWEEN on local timestamps matches the repeated hour twice | Query on UTC column instead |
| Countdown timer jumps | Timer computes target - Date.now() using local Date objects | Use 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.