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()