Exercise 3 solution from datetime import datetime, timezone from zoneinfo import ZoneInfo # Part A def parse_instant(text): try: dt = datetime.fromisoformat(text) except ValueError: raise ValueError("not an ISO 8601 date-time") from None if dt.utcoffset() is None: raise ValueError("missing offset") return dt.astimezone(timezone.utc) for text in ("2026-11-02T12:00:00Z", "2026-11-02T13:00:00+01:00", "2026-11-02T12:00:00", "2026-11-02", "02/11/2026 12:00", "2026-13-02T12:00:00Z"): try: print(f"{text:27} -> {parse_instant(text).isoformat()}") except ValueError as e: print(f"{text:27} -> rejected: {e}") # Part B record = {"local": "2027-03-28 09:00", "zone": "Europe/London"} def instant_now(rec): local = datetime.fromisoformat(rec["local"]).replace(tzinfo=ZoneInfo(rec["zone"])) return local.astimezone(timezone.utc) frozen = instant_now(record) print("Instant frozen at booking time:", frozen.isoformat()) print("Recomputed from local + zone :", instant_now(record).isoformat()) # Pretend the zone's rules changed, so that it stays on UTC+0 all year. changed = datetime.fromisoformat(record["local"]).replace(tzinfo=timezone.utc) print("Recomputed after a rule change:", changed.isoformat()) print("Frozen instant shown locally :", frozen.astimezone(timezone.utc).strftime("%H:%M")) print("Meeting should still be at :", record["local"][-5:]) Output (run, and exactly as shown): 2026-11-02T12:00:00Z -> 2026-11-02T12:00:00+00:00 2026-11-02T13:00:00+01:00 -> 2026-11-02T12:00:00+00:00 2026-11-02T12:00:00 -> rejected: missing offset 2026-11-02 -> rejected: missing offset 02/11/2026 12:00 -> rejected: not an ISO 8601 date-time 2026-13-02T12:00:00Z -> rejected: not an ISO 8601 date-time Instant frozen at booking time: 2027-03-28T08:00:00+00:00 Recomputed from local + zone : 2027-03-28T08:00:00+00:00 Recomputed after a rule change: 2027-03-28T09:00:00+00:00 Frozen instant shown locally : 08:00 Meeting should still be at : 09:00 Explanation: Part A accepts only strings that carry an offset (Z or +hh:mm), converts them to UTC, and gives a distinct reason for each rejection. A bare date parses successfully but has no offset, so it is rejected for that reason; the day/month/year string and the month 13 fail to parse at all. Part B: on 28 March 2027 London has just moved to summer time (UTC+1), so 09:00 local is 08:00 UTC. If the zone's rules changed to stay on UTC+0, the meeting should still be at 09:00 local, which is now 09:00 UTC. The instant frozen at booking time (08:00 UTC) would show as 08:00 local and be an hour wrong, while the stored local time plus zone name gives the right answer simply by being converted again with the updated rules.