skip to content

questions

4

What does the fold attribute on a datetime.datetime mean during a DST transition?

level: middleimportance: must knowfreq 45%

answer

  1. One local hour happens twice a year
  2. A flag picks which pass you mean
  3. Zero is the earlier occurrence
  4. Read by utcoffset, ignored by equality
  5. PEP 495, Python 3.6, defaults to 0

basics

~20 s

fold is a 0-or-1 flag that says which pass through a repeated local wall time you mean: 0 the first occurrence, 1 the second. It defaults to 0 and only changes anything across an offset change.

solid answer

~40 s

When a zone leaves daylight saving time the clock goes back, so one local hour happens twice — 01:30 on 2 November 2025 in `America/New_York` names two instants an hour apart. `fold`, added in Python 3.6 by PEP 495, disambiguates them: `fold=0` is the first (earlier) occurrence, `fold=1` the second. The flag is inert until a `tzinfo` reads it, and `zoneinfo.ZoneInfo` does so inside `utcoffset()`, `dst()` and `tzname()`, which is why `astimezone()` and `timestamp()` give different results for the two values. Crucially, two datetimes in the same zone differing only in fold still compare equal and hash equal — PEP 495 kept intra-zone comparison wall-clock based — so equality will not tell them apart even though they are different moments.

code

python · 10 lines
python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

ny = ZoneInfo("America/New_York")
first = datetime(2025, 11, 2, 1, 30, tzinfo=ny)
second = datetime(2025, 11, 2, 1, 30, fold=1, tzinfo=ny)

print(first.tzname(), first.astimezone(timezone.utc))
print(second.tzname(), second.astimezone(timezone.utc))
print(first == second, first.timestamp() == second.timestamp())

go deeper

for a junior

Be ready to say that one local hour repeats each autumn, so a bare local timestamp can mean two different moments, and that storing UTC is what avoids the problem.

for a middle

Explain the mechanics: fold is 0 or 1, defaults to 0, means first-versus-second occurrence, and is read by the tzinfo inside utcoffset, dst and tzname — so astimezone and timestamp change with it.

for a senior

Demonstrate the production consequence: equality and hashing ignore fold inside a zone, so de-duplication and 'have we seen this already' checks silently merge two distinct instants around the transition.

for a principal

Own the policy. Decide once, at the system edge, how an ambiguous local input is resolved and recorded, and make UTC the storage form so the ambiguity is settled before data spreads across services.

## One wall time, two instants A zone that observes daylight saving time changes its UTC offset twice a year. In `America/New_York` the autumn transition falls at 02:00 local on the first Sunday of November: the clock jumps back to 01:00, and every wall time from 01:00:00 to 01:59:59 is lived through twice — once on daylight time (offset `-04:00`) and again an hour later on standard time (offset `-05:00`). Nothing in the six calendar fields of a `datetime` object (year, month, day, hour, minute, second) tells the two passes apart, so `datetime(2025, 11, 2, 1, 30, tzinfo=ZoneInfo("America/New_York"))` is genuinely ambiguous: it names two distinct instants an hour apart. `fold` is the extra field that resolves it. Added in Python 3.6 by PEP 495, it is an integer that is 0 or 1, keyword-only in the constructor, and 0 by default. The rule is chronological, not political: **`fold=0` selects the first (earlier) occurrence of the repeated wall time and `fold=1` selects the second (later) one.** In a northern-hemisphere autumn that makes `fold=0` the daylight reading and `fold=1` the standard reading, but the daylight/standard labels swap in a southern-hemisphere zone while the fold rule does not. Never memorise it as "fold=1 means standard time"; memorise "second time round". ### Who actually reads it The `datetime` class does almost nothing with the flag itself; it passes the object to its `tzinfo`. The three methods that route through the zone — `utcoffset()`, `dst()` and `tzname()` — inspect the fold of the datetime they are asked about, and `zoneinfo.ZoneInfo` implements exactly the PEP 495 rule. Everything layered on those methods therefore becomes fold-sensitive: `astimezone()` to any other zone, `timestamp()`, the offset that `isoformat()` renders, and comparison or subtraction against a datetime carrying a *different* `tzinfo`. Everything else ignores it. Two aware datetimes in the **same** zone that differ only in fold compare equal and hash equal, because PEP 495 deliberately kept intra-zone `==` and `hash()` wall-clock based so that adding the attribute could not silently reshuffle existing dicts and sorted lists. That is the sharpest trap in the feature: a de-duplication pass keyed on equality collapses two events an hour apart, while `timestamp()` on the same pair returns two different numbers. ### How the flag gets set for you You rarely set it by hand — only when interpreting a local wall time that a human or a config file supplied. The library sets it whenever it *derives* a local time from an absolute one: `datetime.fromtimestamp(ts, tz)` and `astimezone(tz)` both mark the result `fold=1` when the instant lands in the second pass. That is what makes a UTC-origin round trip lossless, and it is one more argument for keeping UTC as the storage form and treating local time as a rendering. Arithmetic runs the other way. `timedelta` arithmetic on an aware datetime is wall-clock arithmetic inside the zone: it adds to the naive fields, keeps the same `tzinfo`, and always produces a result with `fold=0`. So noon on the day before the transition plus one day is noon the next day even though 25 real hours elapsed, and adding an interval that lands inside the repeated hour silently picks the first pass. When you need elapsed *real* time, convert both ends to UTC, do the arithmetic there, and convert back. ### A working policy Three rules cover most systems. First, decide the ambiguity policy once, at the edge where a local wall time enters: either reject the input and ask which pass was meant, or default to `fold=0` and **record the choice** alongside the value so the decision is auditable. Second, never let equality do double duty as identity for aware datetimes; compare `timestamp()` values, or convert to UTC first, whenever two candidates could straddle a transition. Third, remember that `fold` only means anything on an aware datetime — a naive datetime carries the attribute but no zone consults it, so setting it on naive data is a no-op that reads like a fix. Detecting the ambiguity is a two-line test: flip the flag and see whether the offset moves. `dt.replace(fold=not dt.fold).utcoffset() != dt.utcoffset()` is true exactly when the wall time sits in a repeated hour or in a spring-forward gap, and a round-trip check separates those two cases. Logging that predicate around the two transition dates is usually enough to find every place a system quietly assumed local times are unique.

  • Why do two datetimes in the same zone that differ only in fold compare equal?
    PEP 495 deliberately kept intra-zone comparison and hashing wall-clock based, so adding the attribute could not silently change existing sorts, sets and dict keys. Fold only enters comparison when the two datetimes carry different zones, or when you convert them. The practical consequence is that de-duplicating aware datetimes by equality merges two instants an hour apart; compare `timestamp()` values, or convert to UTC first, whenever a transition is in range.
  • Who sets fold if you never pass it to the constructor?
    Anything that derives a local time from an absolute one. `datetime.fromtimestamp(ts, tz)` and `astimezone(tz)` both mark the result `fold=1` when the instant falls in the second pass, which makes a UTC-origin round trip lossless. Direct construction and `strptime` default to 0, and timedelta arithmetic on an aware datetime always produces a result with fold reset to 0.
  • Does fold mean anything on a naive datetime?
    No. The attribute exists on every datetime and you can set it, but nothing reads it without a `tzinfo` — `utcoffset()`, `dst()` and `tzname()` all return `None` on a naive value. Setting fold on naive data looks like a fix and does nothing; the disambiguation only exists once the object carries a zone that implements the PEP 495 rules.

A loop line stops at the same platform twice a night. The platform sign is the wall time; fold is the note on your ticket saying whether you got on during the first pass or the second.

saying these in an interview costs you the question

  • Says Python raises an error for an ambiguous local time
  • Believes fold=1 always means standard time
  • Thinks equality distinguishes two datetimes differing only in fold
  • Assumes the constructor sets fold from the zone automatically
  • Claims a naive datetime carries daylight-saving information

context

open as a page

Why does a datetime for 02:30 on a spring-forward date not raise in Python?

level: middleimportance: should knowfreq 32%

basics

~10 s

Nothing validates a local time against the zone's transitions. The constructor only range-checks the fields and stores the tzinfo, so an hour the clock skipped builds fine and misbehaves later, when something converts it.

open as a page

How do you schedule a nightly Python job across DST so it neither repeats nor skips?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Decide whether the job is anchored to an absolute cadence or to a local wall clock. Absolute means a stored UTC instant plus a fixed interval; wall-clock means recomputing the local time each night and resolving gaps and repeats explicitly.

open as a page

When should you store a future event as UTC instead of local time plus an IANA zone key?

level: principalimportance: should knowfreq 30%

basics

~20 s

Store UTC when the record answers 'what moment was that?' — anything already past. Store local wall time plus the IANA key when it answers 'what wall clock did we promise?', because zone rules change under it.

open as a page