Exercise 2 solution from datetime import datetime, timedelta def at(h, m=0): return datetime(2026, 11, 2, h, m) def merge(intervals): out = [] for s, e in sorted(intervals): if out and s <= out[-1][1]: out[-1] = (out[-1][0], max(out[-1][1], e)) else: out.append((s, e)) return out def free_gaps(busy, day_start, day_end): gaps, cur = [], day_start for s, e in merge(busy): if s > cur: gaps.append((cur, s)) cur = max(cur, e) if cur < day_end: gaps.append((cur, day_end)) return gaps def slots(gaps, length): out = [] for s, e in gaps: start = s while start + length <= e: out.append(start) start += length return out busy = [(at(9), at(10, 30)), (at(10), at(11)), (at(12, 30), at(13)), (at(15), at(17))] gaps = free_gaps(busy, at(9), at(17)) for s, e in gaps: print("free", s.strftime("%H:%M"), "-", e.strftime("%H:%M"), e - s) print([s.strftime("%H:%M") for s in slots(gaps, timedelta(minutes=45))]) Output (run, and exactly as shown): free 11:00 - 12:30 1:30:00 free 13:00 - 15:00 2:00:00 ['11:00', '11:45', '13:00', '13:45'] Explanation: merged busy time is 09:00-11:00, 12:30-13:00 and 15:00-17:00, so the gaps are 11:00-12:30 (1:30) and 13:00-15:00 (2:00). In the first gap two 45-minute meetings fit back to back (11:00 and 11:45, ending exactly 12:30). In the second, 13:00 and 13:45 fit, and a third would end at 15:15, past the gap, so it is rejected. Rule: test start + length <= gap end, with the half-open end allowed to touch.