From d380d87e1437358060bcab90d503efe0d8d240fe Mon Sep 17 00:00:00 2001
From: Matthew Hague <matthew.hague@rhul.ac.uk>
Date: Fri, 21 Jun 2024 10:42:51 +0100
Subject: [PATCH] add(timetable): use recurring not individual events

---
 timetabling/timetable-ics.py | 64 +++++++++++++++++++++++++++++++++---
 1 file changed, 60 insertions(+), 4 deletions(-)

diff --git a/timetabling/timetable-ics.py b/timetabling/timetable-ics.py
index a3de9d3..51083c3 100644
--- a/timetabling/timetable-ics.py
+++ b/timetabling/timetable-ics.py
@@ -6,6 +6,7 @@ import re
 import sys
 
 from ics import Calendar, Event
+from ics.grammar.parse import ContentLine
 from datetime import datetime, timedelta
 
 from typing import Dict, Generator
@@ -13,11 +14,21 @@ from typing import Dict, Generator
 BASE_DATE = datetime(2024, 9, 23, 0, 0, 0, 0, pytz.timezone("Europe/London"))
 BEDFORD = 100 # not a year
 
+CAL_DAYS = [ "MO", "TU", "WE", "TH", "FR", "SA", "SU" ]
+
 if len(sys.argv) < 2:
-    print("Usage: timetable-ics.py timetable1.csv [timetable2.csv..]")
+    print("Usage: timetable-ics.py [-expand] timetable1.csv [timetable2.csv..]")
     print("May also pass excel files instead of csv")
+    print(
+        "Add -expand as first arg to make individual events for each",
+        "lecture, instead of using recurring events."
+    )
 
 timetable_files = sys.argv[1:]
+expand_events = False
+if sys.argv[1].lower() == "-expand":
+    expand_events = True
+    del timetable_files[0]
 
 cals = { year: Calendar() for year in range(0, 6) }
 cals.update({ BEDFORD : Calendar() })
@@ -93,7 +104,7 @@ for ttfile in timetable_files:
         elif "Start Time" in row:
             start = row["Start Time"]
         duration_hours = row["Duration"]
-        weeks = get_weeks(row["Teaching Week Pattern"])
+        weeks = list(get_weeks(row["Teaching Week Pattern"]))
 
         location = ""
         if "PROVISIONAL Location Name" in row:
@@ -112,20 +123,64 @@ for ttfile in timetable_files:
         if "TUT" in name:
             continue
 
-        for week in weeks:
+        if expand_events:
+            for week in weeks:
+                offset = get_day_offset(day)
+                if offset < 0:
+                    print(f"Warning: ignoring day {day} of {row}")
+                else:
+                    start_time = get_time(week, offset, start)
+                    end_time = get_end_time(start_time, duration_hours)
+                    event = Event()
+                    event.name = name
+                    event.location = location
+                    event.begin = start_time
+                    event.end = end_time
+                    for year in get_years(name):
+                        cals[year].events.add(event)
+                    if "bedford" in location.lower():
+                        event = event.clone()
+                        if "04-06" in location:
+                            event.name = f"ALL {name}"
+                        elif "04" in location:
+                            event.name = f"4 {name}"
+                        elif "05" in location:
+                            event.name = f"5 {name}"
+                        elif "06" in location:
+                            event.name = f"6 {name}"
+                        else:
+                            event.name = f"? {name}"
+
+                        cals[BEDFORD].events.add(event)
+        elif len(weeks) > 0:
             offset = get_day_offset(day)
             if offset < 0:
                 print(f"Warning: ignoring day {day} of {row}")
             else:
-                start_time = get_time(week, offset, start)
+                start_time = get_time(weeks[0], offset, start)
                 end_time = get_end_time(start_time, duration_hours)
+
+                year_weeks = ",".join(map(
+                    lambda w: str(get_time(w, offset, start).isocalendar()[1]),
+                    weeks
+                ))
+
                 event = Event()
                 event.name = name
                 event.location = location
                 event.begin = start_time
                 event.end = end_time
+                event.extra.append(ContentLine(
+                    name="RRULE",
+                    value="FREQ=YEARLY;"
+                        + f"COUNT={len(weeks)};"
+                        + f"BYWEEKNO={year_weeks};"
+                        + f"BYDAY={CAL_DAYS[offset]}"
+                ))
+
                 for year in get_years(name):
                     cals[year].events.add(event)
+
                 if "bedford" in location.lower():
                     event = event.clone()
                     if "04-06" in location:
@@ -141,6 +196,7 @@ for ttfile in timetable_files:
 
                     cals[BEDFORD].events.add(event)
 
+
 for year in cals:
     if year != BEDFORD:
         with open(f"Year{year}.ics", 'w') as icsfile:
-- 
GitLab