We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 53 of 55
datetime: reject naive input, and find the next local wall clock
Two boundary functions that together cover the whole of the naive/aware and DST minefield.
def parse_timestamp(text: str) -> datetime: ...
def exists_in_zone(candidate: datetime) -> bool: ...
def next_occurrence(after: datetime, local_time: time, tz: str) -> datetime: ...
def solve(stamps: list[str], after: str, local_time: str, tz: str) -> dict[str, object]: ...
-
parse_timestampparses an ISO-8601 string and returns an aware UTC datetime. Naive input — no offset — raisesValueError.Zand explicit offsets both work (datetime.fromisoformatacceptsZfrom 3.11). -
next_occurrencereturns the next UTC instant strictly afterafterat which the local wall clock intzreadslocal_time. Skip days on which that wall clock does not exist. When it occurs twice, take the first. -
solvemaps each stamp toparse_timestamp(...).isoformat()or"ValueError", parseslocal_timeas"HH:MM", and returns{"parsed": [...], "next": next_occurrence(...).isoformat()}.
Silent corruption source one: naive versus aware. datetime.utcnow()
returns a naive datetime whose fields happen to hold UTC. Comparing it to
an aware datetime raises — the good outcome, because it is loud. Calling
.timestamp() on it interprets the wall clock as local time: measured
under TZ=America/Los_Angeles, 28,800 seconds of skew, silently, forever, in
your database. And replace(tzinfo=...) attaches a zone without converting
(moving the instant), while astimezone() converts it (keeping the instant).
Silent corruption source two: DST. Arithmetic on a zoned datetime is
wall-clock, not absolute:
datetime(2026, 3, 7, 12, 0, tzinfo=ZoneInfo("America/New_York")) + timedelta(days=1)
gives the same wall clock, but only 82,800 real seconds elapse. On
8 March 2026, 02:30 in New York does not exist — and Python silently
accepts the construction, giving you an object that reads 03:30 when
converted back. On 1 November 2026, 01:30 occurs twice; two datetimes
differing only in fold compare equal intra-zone and convert to UTC
instants an hour apart.
exists_in_zone is the detection idiom: normalise the candidate through UTC
and back, and compare the wall clocks. If normalising changed the reading, the
reading never happened.
The type system cannot help here, at all. Aware and naive are both
datetime; there is no annotation that distinguishes them. This is the
clearest case in the course for an invariant the checker cannot express, which
is why it has to be enforced by a boundary function — and why the emptiness of
-> datetime as a contract is worth feeling.
Note the documented aware-check is dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None:
a tzinfo may return None from utcoffset, making the datetime aware by
attribute and naive in behaviour.
zoneinfo (3.9) reads the system tz database and its objects go straight into
tzinfo=. It replaces pytz, whose localize()/normalize() dance existed
because attaching a bare pytz zone with replace gives an obsolete LMT
offset — the famous “seven minutes off” bug.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.