Exercise 3 solution import unittest from datetime import datetime, date, time, timedelta, timezone from zoneinfo import ZoneInfo UTC = timezone.utc def resolve(d, t, zone): z = ZoneInfo(zone) return datetime.combine(d, t, tzinfo=z).astimezone(UTC).astimezone(z) def next_after(now, t, zone, weekdays): """First occurrence strictly after the aware moment `now`, returned in UTC.""" if now.tzinfo is None: raise ValueError("now must be aware") d = now.astimezone(ZoneInfo(zone)).date() for _ in range(8): if d.weekday() in weekdays: cand = resolve(d, t, zone).astimezone(UTC) if cand > now: return cand d += timedelta(days=1) raise ValueError("no weekdays given") class Tests(unittest.TestCase): def test_same_day_later(self): now = datetime(2026, 11, 2, 8, 0, tzinfo=UTC) # Monday got = next_after(now, time(9), "Europe/London", {0}) self.assertEqual(got, datetime(2026, 11, 2, 9, 0, tzinfo=UTC)) def test_exactly_now_is_not_after(self): now = datetime(2026, 11, 2, 9, 0, tzinfo=UTC) got = next_after(now, time(9), "Europe/London", {0}) self.assertEqual(got, datetime(2026, 11, 9, 9, 0, tzinfo=UTC)) def test_crosses_dst_change(self): now = datetime(2027, 3, 26, 12, 0, tzinfo=UTC) # Friday got = next_after(now, time(9), "Europe/London", {0}) self.assertEqual(got, datetime(2027, 3, 29, 8, 0, tzinfo=UTC)) def test_zone_date_differs_from_utc_date(self): now = datetime(2026, 11, 2, 23, 30, tzinfo=UTC) # already Tuesday in Tokyo got = next_after(now, time(9), "Asia/Tokyo", {1}) self.assertEqual(got, datetime(2026, 11, 3, 0, 0, tzinfo=UTC)) def test_naive_rejected(self): with self.assertRaises(ValueError): next_after(datetime(2026, 11, 2, 8, 0), time(9), "Europe/London", {0}) unittest.main(argv=["x"], verbosity=2) Output (run, and exactly as shown; the timing line varies): test_crosses_dst_change (__main__.Tests.test_crosses_dst_change) ... ok test_exactly_now_is_not_after (__main__.Tests.test_exactly_now_is_not_after) ... ok test_naive_rejected (__main__.Tests.test_naive_rejected) ... ok test_same_day_later (__main__.Tests.test_same_day_later) ... ok test_zone_date_differs_from_utc_date (__main__.Tests.test_zone_date_differs_from_utc_date) ... ok ---------------------------------------------------------------------- Ran 5 tests in 0.061s OK Explanation: each test pins one boundary from the course. The search starts from the date in the rule's own zone, not the UTC date (the Tokyo test), compares with > so a moment exactly on the start is not "after" it (the second test), generates candidates from local date plus wall time so the UTC hour changes across the clock change (the third test), and rejects a naive "now" instead of guessing (the last test). The loop looks at 8 days so every weekday is seen at least once, and an empty weekday set fails loudly instead of looping forever.