A timezone-aware Python datetime carries offset information, while a naive datetime does not identify its relation to UTC.
Python datetime: require an offset before comparing timestamps
Operation contract
The ingestion boundary accepts a limited-length ISO timestamp that contains an offset and converts it to UTC. Two strings with different local clock readings represent the same instant. The fixture then rejects an otherwise valid timestamp that omits its offset, because the import has no rule for choosing a timezone on the sender’s behalf.
Failure and ownership boundary
This is offset-aware parsing, not a complete wire-format validator: fromisoformat accepts forms beyond a single strict API schema. It also does not resolve named-region daylight-saving ambiguity from an offset-free local time. Define that policy before adding ZoneInfo. Comparing naive and aware values for ordering can fail; replacing tzinfo labels a clock reading and is not the same operation as converting its instant. Java daylight-saving transitions: reject gaps and choose overlaps and Python input exercise: accept an explicit integer grammar address related choices.
Working program
from datetime import datetime, timezone
def receipt_instant(text):
if not 1 <= len(text) <= 40:
raise ValueError("timestamp length")
instant = datetime.fromisoformat(text)
if instant.tzinfo is None or instant.utcoffset() is None:
raise ValueError("offset required")
return instant.astimezone(timezone.utc)
local = receipt_instant("2026-09-29T10:00:00+05:30")
print(local.isoformat())
print(local == receipt_instant("2026-09-29T04:30:00+00:00"))
try:
receipt_instant("2026-09-29T10:00:00")
except ValueError:
print("missing offset rejected")Output
2026-09-29T04:30:00+00:00
True
missing offset rejectedCosts and limits
Input length is capped at forty characters, bounding parser work for this fixture. No timezone database lookup or external clock is used. Wider named-zone policies need versioned timezone data and transition tests.
Common Mistakes
- Do not silently choose a sender timezone for an offset-free value.
- Replacing tzinfo is not an instant-preserving conversion.
Connected lessons
Python input exercise: accept an explicit integer grammar, Java daylight-saving transitions: reject gaps and choose overlaps.
Apply this boundary
Python zoneinfo and fold: two instants can share one clock label.
Follow the ownership and update boundary
Python ZoneInfo: classify unique, repeated and missing local times.
