diff --git a/timetabling/find-empty-hour.py b/timetabling/find-empty-hour.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e485e70137366cf9d6280a656df617af415059e
--- /dev/null
+++ b/timetabling/find-empty-hour.py
@@ -0,0 +1,78 @@
+"""
+Script to find ICS files that don't have any events during a specified hour.
+Usage: python find_empty_hour.py <hour>
+Example: python find_empty_hour.py 14  # Checks for events at 2 PM
+"""
+
+import pytz
+import sys
+
+from icalendar import Calendar
+from datetime import datetime, timedelta
+from dateutil import rrule
+from pathlib import Path
+
+def parse_ics_file(filename : Path, target_date : datetime):
+    """
+    Parse an ICS file and check if it has any events during the target hour on the target date.
+    Returns True if the file has at least one event during that hour on that date, False otherwise.
+    """
+    with open(filename, "r") as f:
+        cal = Calendar.from_ical(f.read())
+
+        for component in cal.walk():
+            if component.name == "VEVENT":
+                # Get start time
+                start = component.get('dtstart').dt
+                end = component.get('dtend').dt
+                # Handle both datetime and date objects
+                if start <= target_date < end:
+                    return True
+
+                # Handle recurring events
+                rrule_val = component.get('rrule')
+                if rrule_val and isinstance(start, datetime):
+                    # Convert rrule to dateutil rrule
+                    try:
+                        # Create recurrence rule
+                        recur_rule = rrule.rrulestr(
+                            rrule_val.to_ical().decode('utf-8'),
+                            dtstart=start
+                        )
+
+                        event_duration = end - start
+
+                        # Get occurrences around the target date
+                        # Look for occurrences from 1 week before to account for multi-day events
+                        for occurrence_start in recur_rule.between(
+                            target_date - timedelta(days=7),
+                            target_date + timedelta(days=1),
+                            inc=True
+                        ):
+                            occurrence_end = occurrence_start + event_duration
+                            if occurrence_start <= target_date < occurrence_end:
+                                return True
+
+                    except Exception as e:
+                        print(f"Error processing recurring event in {filename}: {e}", file=sys.stderr)
+
+def main():
+    if len(sys.argv) != 3:
+        print("Usage: python find_empty_hour.py <date> <hour>")
+        print("Example: python find_empty_hour.py 2025-03-20 14  # Checks for events at 2 PM on March 20, 2025")
+        sys.exit(1)
+
+    # Parse date (YYYY-MM-DD)
+    date_str = sys.argv[1]
+    hour = int(sys.argv[2])
+    target_date = pytz.timezone("Europe/London").localize(
+        datetime.strptime(date_str, "%Y-%m-%d") \
+        .replace(hour=hour)
+    )
+    print(target_date.astimezone())
+
+    for filename in Path(".").glob("*.ics"):
+        if not parse_ics_file(filename, target_date):
+            print("No overlap", filename)
+
+main()