Schedulers and billing code break twice a year if they ignore DST transitions.
When clocks spring forward, a range of local times doesn't exist (a gap): in New York on 2025-03-09, 2:00 to 2:59 never happens. When clocks fall back, an hour happens twice (a fold): 1:30 on 2025-11-02 occurs once in EDT and again in EST.
Python represents the ambiguity with the fold attribute (PEP 495): fold=0 means the first occurrence, fold=1 the second. ZoneInfo uses it to pick the offset. For a time in a gap, ZoneInfo applies the offset from before the transition, and converting to UTC and back moves it to a valid time (2:30 becomes 3:30 EDT).
Arithmetic semantics matter:
- Adding a timedelta to an aware datetime is wall-clock arithmetic:
9:00 + 1 dayis 9:00 the next day, even if that day was only 23 hours long. That's usually what you want for "same time tomorrow". - For elapsed time, convert to UTC first, add, and convert back. Subtracting two datetimes in the same zone also ignores DST; subtract their UTC versions to get real elapsed time.
For recurring events, store the local time and zone name ("9:00 Europe/Berlin"), not a UTC instant, so the event stays at 9:00 after DST changes. For log records and durations, store UTC.
Example
from datetime import UTC, datetime, timedelta
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
before = datetime(2025, 3, 8, 12, 0, tzinfo=ny) # day before spring forward
same_time = before + timedelta(days=1) # wall-clock arithmetic
print(same_time) # 2025-03-09 12:00:00-04:00
print(same_time - before) # 1 day, 0:00:00
print(same_time.astimezone(UTC) - before.astimezone(UTC)) # 23:00:00
gap = datetime(2025, 3, 9, 2, 30, tzinfo=ny) # doesn't exist
print(gap.astimezone(UTC).astimezone(ny)) # 2025-03-09 03:30:00-04:00
first = datetime(2025, 11, 2, 1, 30, tzinfo=ny) # happens twice
second = first.replace(fold=1)
print(first.tzname(), second.tzname()) # EDT EST
print(second.astimezone(UTC) - first.astimezone(UTC)) # 1:00:00Remember
- Spring-forward creates nonexistent times; fall-back creates ambiguous ones
fold=0/1selects the first or second occurrence of an ambiguous time- Same-zone arithmetic is wall-clock; convert to UTC for elapsed time
Watch out for
- Computing durations by subtracting two local datetimes across a DST change and being an hour off.