Last active
September 21, 2017 12:31
-
-
Save soudy/b99b61d4a9c4f5caf1d1ab9a0599f816 to your computer and use it in GitHub Desktop.
HvA schedule checker for overlapping free time
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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