#!/usr/bin/env python3 import calendar import datetime class IsoWeek(datetime.date): """Datetime representing the Monday of an ISO-8601 week. """ pass class TeachingWeek(datetime.date): """Datetime representing the Monday of a teaching week. """ pass class BreakWeek(datetime.date): """Datetime representing the Monday of a break (e.g., mid-semester) week. """ pass class TeachingCalendar(): """This class generates teaching-related dates for a specific paper offered in a specific teaching period of a specific year. If you don't provide teaching period details, it generates dates for the current ISO-8601 year. """ weeks = [] lecture_offsets = [] skipped_lectures = [] mondays = [] period = "iso" year = datetime.date.today().year def __init__(self, year, period, paper, period_config=None, paper_config=None, *args, **kwargs): super().__init__(*args, **kwargs) self.year = year self.period = period self.paper = paper # This is likely to end up with one week too many at the end, # but that doesn't matter as there will never be any teaching # during the last week of December! cal = calendar.Calendar().yeardatescalendar(self.year, width=12) self.mondays = [week[0] for month in cal[0] for week in month] self.mondays = list(dict.fromkeys(self.mondays)) if period_config: self.update_weeks(period_config["weeks"]) else: last_week = datetime.date(self.year, 12, 28).isocalendar()[1] self.update_weeks([{"iso": [1, last_week]}]) if paper_config: self.update_lectures(paper_config["lectures"]) def week(self, week_type, start, end=None): end = start if end is None else end return [ week_type(self.mondays[w].year, self.mondays[w].month, self.mondays[w].day) for w in range(start - 1, end)] def teaching_date(self, period, week, offset=0): pass def update_weeks(self, week_list): for w in week_list: for t, r in w.items(): if t == "teach": self.weeks += self.week(TeachingWeek, r[0], r[1]) elif t == "break": self.weeks += self.week(BreakWeek, r[0], r[1]) elif t == "iso": self.weeks += self.week(IsoWeek, r[0], r[1]) else: print("WARNING: ignored unknown week type {t}.".format(t=t)) def update_lectures(self, lecture_list): for l in lecture_list: for t, v in l.items(): if t == "offsets": self.lecture_offsets = v elif t == "skip": self.skipped_lectures = v else: print("WARNING: ignored unknown lecture key {t}.".format(t=t)) def calendar(self): period = {} week_num = break_num = 0 for w in self.weeks: if isinstance(w, (TeachingWeek, IsoWeek)): # Increment first so that break weeks have the # same week number as the preceding teaching week. # This ensures the keys are always chronologically # sorted. Reset the break number for each new # teaching week. week_num += 1 break_num = 0 period[week_num] = w elif isinstance(w, BreakWeek): # Allow for up to 99 consecutive break weeks, # which should be sufficient :). Should probably # throw an exception if we exceed that. break_num += 0.01 period[week_num + break_num] = w return period def lecture_dates(self): dates = {} lecture_num = 1 teaching_weeks = [t for t in self.weeks if isinstance(t, TeachingWeek)] for week_index, monday in enumerate(teaching_weeks): for offset_index, offset in enumerate(self.lecture_offsets): lec = week_index * 2 + offset_index + 1 if lec in self.skipped_lectures: continue dates[lecture_num] = monday + datetime.timedelta(offset) lecture_num += 1 return dates