Skip to content

← Everyday Standard Library Tools step 55 of 55

Hard Primitives

Find the Next Daily Run in a Time Zone

A job runs every day at a wall-clock time in a named zone, such as 09:00 in Tokyo. Write a timestamp parser, a check for wall-clock times that do not exist, and a function that finds the next run as a UTC instant.

after = parse_timestamp("2026-07-28T10:00:00Z")  # 19:00 in Tokyo
next_occurrence(after, time(9, 0), "Asia/Tokyo")
# 2026-07-29 00:00 UTC, which is 09:00 on 29 July in Tokyo

What to write

parse_timestamp(text) -> datetime:

  • Parse with datetime.fromisoformat, which accepts Z and offsets such as +02:00.
  • Raise ValueError for a malformed string, and for a naive result, where tzinfo is None or utcoffset() returns None.
  • Convert a valid result to UTC with astimezone(timezone.utc). Do not use replace(tzinfo=...), which changes the instant. The starter does that.

exists_in_zone(candidate) -> bool is missing from the starter. Add it:

  • Raise ValueError if candidate is naive.
  • Convert candidate to UTC and back into its own zone. Return True if the date and clock fields stay the same. Compare copies with tzinfo removed.

next_occurrence(after, local_time, tz) -> datetime:

  • Raise ValueError if after is naive or if local_time has a tzinfo.
  • Load the zone with ZoneInfo(tz), and let its errors propagate.
  • Start from the date of after in that zone. Try up to 400 dates, one calendar day at a time.
  • On each date, build the wall-clock time with fold=0. Skip it if it does not exist. Because of fold=0, only the first of a repeated time counts.
  • Return the first candidate, in UTC, that is strictly later than after. If the first 01:30 of a day has passed, move on to the next date. Do not use that day's second 01:30.
  • Raise ValueError if none of the 400 dates works.

solve is already in the starter. It parses each stamp and records its UTC isoformat() string, or "ValueError". It keeps the order and any duplicates. Then it parses after, reads local_time as HH:MM, and returns {"parsed": ..., "next": ...}. The tests always give solve a valid after, local_time and tz.

Examples

  • 02:30 does not exist in New York on 8 March 2026. A search after noon on 7 March skips 8 March and returns 9 March at 02:30 local time, which is 06:30 UTC.
  • On 1 November 2026, the first 01:30 in New York is 05:30 UTC. If after is 05:45 UTC that day, the answer is 2 November at 06:30 UTC, not the second 01:30 on 1 November.
  • "2026-07-28T10:30:00" has no offset, so solve records "ValueError" for it.