Skip to content

Instantly share code, notes, and snippets.

@soudy
Last active September 21, 2017 12:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soudy/b99b61d4a9c4f5caf1d1ab9a0599f816 to your computer and use it in GitHub Desktop.
Save soudy/b99b61d4a9c4f5caf1d1ab9a0599f816 to your computer and use it in GitHub Desktop.
HvA schedule checker for overlapping free time
#! /usr/bin/env python3
from icalendar import Calendar
from datetime import datetime
from collections import namedtuple
from calendar import day_name
import itertools
Range = namedtuple('Range', ['start', 'end'])
class ScheduleHoles:
def __init__(self, groups, calendar, period):
self.groups = groups
self.classes = {group: [] for group in groups}
self.free_time = {group: {} for group in groups}
self.calendar = Calendar.from_ical(calendar)
self.period = period
self.parse_ical()
def check_overlapping_free_hours(self):
self.calculate_free_hours()
self.compare_free_time()
def parse_ical(self):
# Parse ical and get today's schedule
for component in self.calendar.walk():
summary = component.get('summary')
description = component.get('description')
location = component.get('location')
dtstart = component.get('dtstart')
dtend = component.get('dtend')
# For some reason, sometimes empty events appear. This eliminates them
if not summary:
continue
start = dtstart.dt
end = dtend.dt
# Only save classes from given period
if not self.date_in_period(end):
continue
# Add class to group if group has that class
for group in self.groups:
if group in description:
self.classes[group].append({
'summary': summary,
'start': start,
'end': end,
'location': location
})
def calculate_free_hours(self):
for group in self.groups:
for i, _ in enumerate(self.classes[group]):
# Don't check free time before classes start
if i == 0:
continue
class_ = self.classes[group][i]
previous_class = self.classes[group][i-1]
# Don't check free time outside of school hours
if class_['start'].date() != previous_class['start'].date():
continue
# Only count as free hour if it's longer than 20 minutes
long_enough = ((class_['start'] - previous_class['end']).seconds / 60) >= 20
if long_enough:
# Save free hours by date, not datetime
day = class_['start'].date()
free_hour = Range(start=previous_class['end'], end=class_['start'])
if not self.free_time[group].get(day):
self.free_time[group][day] = []
self.free_time[group][day].append(free_hour)
def compare_free_time(self):
# Compare all free time of groups against each other to see if any groups have
# shared free time.
combinations = itertools.combinations(self.groups, 2)
for group_a, group_b in combinations:
print('Checking groups {} and {}'.format(group_a, group_b))
corresponding_days = set(self.free_time[group_a]) & set(self.free_time[group_b])
for day in corresponding_days:
print(' Checking {} {}'.format(day_name[day.weekday()], day))
for free_time_a in self.free_time[group_a][day]:
for free_time_b in self.free_time[group_b][day]:
if self.has_overlapping_free_time(free_time_a, free_time_b):
print('\tGroup {} has free hours between {} and {}'.format(
group_a, free_time_a.start.strftime('%H:%M'), free_time_a.end.strftime('%H:%M')))
print('\tGroup {} has free hours between {} and {}'.format(
group_b, free_time_b.start.strftime('%H:%M'), free_time_b.end.strftime('%H:%M')))
def date_in_period(self, date):
today = datetime.today()
if self.period == 'today':
return datetime.strftime(today, "%Y %m %d") == datetime.strftime(date, "%Y %m %d")
elif self.period == 'week':
return today.isocalendar()[1] == date.isocalendar()[1]
def has_overlapping_free_time(self, range_a, range_b):
return (range_a.start <= range_b.end) and \
(range_a.end >= range_b.start)
def main():
with open('./calendar.ical', 'r') as f:
calendar = f.read()
schedule_holes = ScheduleHoles(['IS102', 'IS106', 'ZEE'], calendar, 'week')
schedule_holes.check_overlapping_free_hours()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment