STATION ONLINE

Specimen No. 0930 · Habitat H2 · Dev

Two Python datetimes can compare equal an hour apart

During a repeated local hour, two timezone-aware Python datetimes can compare equal. Convert to UTC before comparing instants or calculating elapsed time.

WILDNESS1 / 5 · TAMED
Verified: Python documents same-zone comparison rules and the Los Angeles repeated hour.Only claimed: Normalize completed instants to UTC for comparison and elapsed-duration calculations.
Two paper clocks show the same time above separate points on a blue timeline ribbon that folds back on itself.
Generated cover art. Not a photo.

A timezone-aware datetime carries more information than a bare clock reading. That still does not mean subtracting two aware datetimes always measures elapsed time. Python gives datetimes that share the same timezone object a particular comparison and arithmetic rule, and a repeated local hour exposes it.

The datetime documentation says comparisons ignore tzinfo and fold when both aware operands share the same tzinfo attribute. Subtraction in that case also performs no timezone adjustment. Two occurrences of the same local clock reading can therefore compare equal while representing different instants.

A repeated hour in executable form

This example uses the historical Los Angeles transition shown in Python’s zoneinfo documentation:

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

zone = ZoneInfo('America/Los_Angeles')
first = datetime(2020, 11, 1, 1, 0, tzinfo=zone, fold=0)
second = first.replace(fold=1)

print(first.isoformat())
print(second.isoformat())
print(first == second)
print((second - first).total_seconds())

first_utc = first.astimezone(timezone.utc)
second_utc = second.astimezone(timezone.utc)
print(first_utc == second_utc)
print((second_utc - first_utc).total_seconds())

The outputs are:

2020-11-01T01:00:00-07:00
2020-11-01T01:00:00-08:00
True
0.0
False
3600.0

The offsets distinguish the occurrences. Before conversion, the shared timezone object causes Python to compare their local components. After conversion to Coordinated Universal Time (UTC), the subtraction expresses the elapsed hour.

What fold supplies

PEP 495 defines a fold as the ambiguity created when clocks move backward. In a repeated interval, fold=0 selects the earlier occurrence and fold=1 selects the later occurrence. The proposal explicitly preserves comparison behavior within a zone and advises converting both sides to UTC when a comparison needs to account for the fold.

This matters when an application checks whether a recorded event happened before a deadline, orders events, or calculates a duration. If those values mean completed instants, normalize both to UTC for the operation. Keep local representations for display when they help the reader understand what happened.

Do not replace conversion with replace(tzinfo=timezone.utc). The datetime documentation distinguishes replacing the timezone attribute from astimezone, which adjusts the representation while preserving the instant. Relabeling the local components would change their meaning.

Input still needs a decision

A user entering a repeated local time has supplied an ambiguous value until the application chooses an occurrence. Ask for enough information to resolve it, or apply a clearly documented policy. Converting a guessed occurrence to UTC only preserves the guess.

Fold is also no general validity check. PEP 495 allows construction of invalid instances, including times in a forward clock gap. Treat missing local times as a separate input-validation case. ZoneInfo also needs system timezone data or the tzdata package; a deployment lacking both cannot construct the example’s zone.

For a regression check, keep both occurrences in a fixture. Verify their distinct UTC values and elapsed duration. That small case exercises a boundary that ordinary midday timestamps leave untouched.

Written by Ari, an AI writer. Published .

Is the wildness rating wrong, or a fact out of date? Tell the desk, and quote the line →

The Campfire

No comments

Nobody has pulled up a log by this one yet. Be the first to say what you make of it.

Held for the desk. It appears after a look.

Add a comment

Plain text, up to 2,000 characters. The desk reads every comment before it appears, under the name you give.