Exercise 3 solution from datetime import datetime, timedelta from collections import Counter EPOCH = datetime(2000, 1, 1) def floor_to(dt, step): return EPOCH + ((dt - EPOCH) // step) * step def hms(td): s = int(td.total_seconds()) sign = "-" if s < 0 else "" h, r = divmod(abs(s), 3600) m, s = divmod(r, 60) return f"{sign}{h}:{m:02d}:{s:02d}" events = [datetime(2026, 11, 2, 9, 5), datetime(2026, 11, 2, 9, 14, 59), datetime(2026, 11, 2, 9, 15), datetime(2026, 11, 2, 9, 44), datetime(2026, 11, 2, 10, 0), datetime(2026, 11, 2, 10, 1), datetime(2026, 11, 2, 23, 59, 59)] counts = Counter(floor_to(e, timedelta(minutes=15)) for e in events) for k in sorted(counts): print(k.strftime("%H:%M"), counts[k]) span = max(events) - min(events) print(span, hms(span), hms(-span), hms(timedelta(seconds=-1))) print(floor_to(datetime(2026, 11, 2, 9, 14, 59), timedelta(days=1))) Output (run, and exactly as shown): 09:00 2 09:15 1 09:30 1 10:00 2 23:45 1 14:54:59 14:54:59 -14:54:59 -0:00:01 2026-11-02 00:00:00 Explanation: floor_to measures from a fixed anchor, so 09:14:59 stays in the 09:00 bucket and 09:15:00 starts the next. The bucket at 09:30 holds 09:44 and 10:00 starts a new hour, so 10:00 and 10:01 share a bucket. The span is the latest minus the earliest, 14:54:59, and hms keeps counting hours. Negating it puts the sign at the front, and minus one second prints as -0:00:01 (str() would have shown -1 day, 23:59:59). Flooring to a whole day gives midnight.