diff --git a/meetings/example3.yaml b/meetings/example3.yaml new file mode 100644 index 0000000..add4483 --- /dev/null +++ b/meetings/example3.yaml @@ -0,0 +1,19 @@ +project: Example alternating quadweekly meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '1700' + duration: 45 + start_date: 20150801 + day: Thursday + irc: openstack-meeting + frequency: quadweekly + - time: '600' + duration: 45 + start_date: 20150801 + day: Thursday + irc: openstack-meeting + frequency: quadweekly-alternate +chair: John Doe +description: > + quadweekly diff --git a/yaml2ical/meeting.py b/yaml2ical/meeting.py index eac2f9b..a2484f9 100644 --- a/yaml2ical/meeting.py +++ b/yaml2ical/meeting.py @@ -136,13 +136,27 @@ class Schedule(object): def conflicts(self, other): """Checks for conflicting schedules.""" - alternating = set(['biweekly-odd', 'biweekly-even']) + + def _non_weekly_conflict_detection(self, other): + week = { + 'weekly': set([0, 1, 2, 3]), + 'biweekly-even': set([0, 2]), + 'biweekly-odd': set([1, 3]), + 'quadweekly': set([0]), + 'quadweekly-week-1': set([1]), + 'quadweekly-week-2': set([2]), + 'quadweekly-week-3': set([3]), + 'quadweekly-alternate': set([2]), + } + + return len(week[self.freq].intersection(week[other.freq])) > 0 + # NOTE(tonyb): .meeting_start also includes the day of the week. So no # need to check .day explictly return ((self.irc == other.irc) and ((self.meeting_start < other.meeting_end) and (other.meeting_start < self.meeting_end)) and - (set([self.freq, other.freq]) != alternating)) + _non_weekly_conflict_detection(self, other)) class Meeting(object): diff --git a/yaml2ical/recurrence.py b/yaml2ical/recurrence.py index a3cbc01..65258be 100644 --- a/yaml2ical/recurrence.py +++ b/yaml2ical/recurrence.py @@ -78,6 +78,34 @@ class BiWeeklyRecurrence(object): return "Every two weeks (on %s weeks)" % self.style +class QuadWeeklyRecurrence(object): + """Meetings occuring every 4 weeks. + + A week number can be supplied to offset meetings + """ + def __init__(self, week=0): + self.week = week + + def next_occurence(self, current_date, day): + """Calculate the next biweekly meeting. + + :param current_date: the current date + :param day: scheduled day of the meeting + :returns: datetime object of next meeting + """ + nextweek_day = WeeklyRecurrence().next_occurence(current_date, day) + if nextweek_day.isocalendar()[1] % 4 == self.week: + return nextweek_day + # If week doesn't match rule, skip one week + return self.next_occurence(nextweek_day + datetime.timedelta(7), day) + + def rrule(self): + return {'freq': 'weekly', 'interval': 4} + + def __str__(self): + return "Every four weeks" % self.style + + class AdhocRecurrence(object): """Meetings occuring as needed. @@ -105,5 +133,10 @@ supported_recurrences = { 'weekly': WeeklyRecurrence(), 'biweekly-odd': BiWeeklyRecurrence(style='odd'), 'biweekly-even': BiWeeklyRecurrence(), + 'quadweekly': QuadWeeklyRecurrence(week=0), + 'quadweekly-week-1': QuadWeeklyRecurrence(week=1), + 'quadweekly-week-2': QuadWeeklyRecurrence(week=2), + 'quadweekly-week-3': QuadWeeklyRecurrence(week=3), + 'quadweekly-alternate': QuadWeeklyRecurrence(week=2), 'adhoc': AdhocRecurrence(), } diff --git a/yaml2ical/tests/sample_data.py b/yaml2ical/tests/sample_data.py index 9218046..bf221df 100644 --- a/yaml2ical/tests/sample_data.py +++ b/yaml2ical/tests/sample_data.py @@ -272,3 +272,93 @@ chair: Shannon Stacker description: > Adhoc random meeting for Subteam project. """ + +QUADWEEKLY_MEETING_ALTERNATING = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly + - time: '600' + duration: 45 + start_date: 20150801 + day: Thursday + irc: openstack-meeting + frequency: quadweekly-alternate +chair: John Doe +description: > + Example alternating quadweekly meeting +""" + +QUADWEEKLY_MEETING = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly +chair: John Doe +description: > + Example Quadweekly meeting +""" + +QUADWEEKLY_MEETING_WEEK_1 = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly-week-1 +chair: John Doe +description: > + Example Quadweekly meeting on week 1 +""" + +QUADWEEKLY_MEETING_WEEK_2 = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly-week-2 +chair: John Doe +description: > + Example Quadweekly meeting on week 2 +""" + +QUADWEEKLY_MEETING_WEEK_3 = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly-week-3 +chair: John Doe +description: > + Example Quadweekly meeting on week 3 +""" + +QUADWEEKLY_MEETING_ALTERNATE = """ +project: OpenStack Random Meeting +agenda_url: http://agenda.com/ +project_url: http://project.com +schedule: + - time: '2200' + day: Wednesday + irc: openstack-meeting + frequency: quadweekly-alternate +chair: John Doe +description: > + Example Quadweekly Alternate meeting +""" diff --git a/yaml2ical/tests/test_meeting.py b/yaml2ical/tests/test_meeting.py index 2651341..03701e4 100644 --- a/yaml2ical/tests/test_meeting.py +++ b/yaml2ical/tests/test_meeting.py @@ -78,6 +78,77 @@ class MeetingTestCase(unittest.TestCase): sample_data.MEETING_MONDAY_LATE, sample_data.MEETING_TUESDAY_EARLY) + def test_quadweekly_conflicts(self): + self.should_be_conflicting( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING_ALTERNATING) + self.should_be_conflicting( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING) + self.should_be_conflicting( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING_ALTERNATE) + self.should_be_conflicting( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_2) + self.should_not_conflict( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_1) + self.should_not_conflict( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_be_conflicting( + sample_data.BIWEEKLY_EVEN_MEETING, + sample_data.QUADWEEKLY_MEETING) + self.should_be_conflicting( + sample_data.BIWEEKLY_ODD_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_1) + self.should_be_conflicting( + sample_data.BIWEEKLY_ODD_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_not_conflict( + sample_data.BIWEEKLY_ODD_MEETING, + sample_data.QUADWEEKLY_MEETING) + self.should_not_conflict( + sample_data.BIWEEKLY_ODD_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_2) + self.should_not_conflict( + sample_data.BIWEEKLY_ODD_MEETING, + sample_data.QUADWEEKLY_MEETING_ALTERNATING) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_1) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_2) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING_WEEK_1, + sample_data.QUADWEEKLY_MEETING_WEEK_2) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING_WEEK_1, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_not_conflict( + sample_data.QUADWEEKLY_MEETING_WEEK_2, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_be_conflicting( + sample_data.QUADWEEKLY_MEETING, + sample_data.QUADWEEKLY_MEETING) + self.should_be_conflicting( + sample_data.QUADWEEKLY_MEETING_WEEK_1, + sample_data.QUADWEEKLY_MEETING_WEEK_1) + self.should_be_conflicting( + sample_data.QUADWEEKLY_MEETING_WEEK_2, + sample_data.QUADWEEKLY_MEETING_WEEK_2) + self.should_be_conflicting( + sample_data.QUADWEEKLY_MEETING_WEEK_3, + sample_data.QUADWEEKLY_MEETING_WEEK_3) + self.should_be_conflicting( + sample_data.QUADWEEKLY_MEETING_WEEK_2, + sample_data.QUADWEEKLY_MEETING_ALTERNATE) + def test_meeting_duration(self): m = meeting.load_meetings(sample_data.MEETING_WITH_DURATION)[0] self.assertEqual(30, m.schedules[0].duration) diff --git a/yaml2ical/tests/test_recurrence.py b/yaml2ical/tests/test_recurrence.py index afbc74f..d90e020 100644 --- a/yaml2ical/tests/test_recurrence.py +++ b/yaml2ical/tests/test_recurrence.py @@ -38,6 +38,26 @@ class RecurrenceTestCase(unittest.TestCase): datetime.datetime(2014, 10, 15, 2, 47, 28, 832666), self.next_meeting(recurrence.BiWeeklyRecurrence(style='even'))) + def test_next_quadweekly_week_0(self): + self.assertEqual( + datetime.datetime(2014, 10, 29, 2, 47, 28, 832666), + self.next_meeting(recurrence.QuadWeeklyRecurrence(week=0))) + + def test_next_quadweekly_week_1(self): + self.assertEqual( + datetime.datetime(2014, 10, 8, 2, 47, 28, 832666), + self.next_meeting(recurrence.QuadWeeklyRecurrence(week=1))) + + def test_next_quadweekly_week_2(self): + self.assertEqual( + datetime.datetime(2014, 10, 15, 2, 47, 28, 832666), + self.next_meeting(recurrence.QuadWeeklyRecurrence(week=2))) + + def test_next_quadweekly_week_3(self): + self.assertEqual( + datetime.datetime(2014, 10, 22, 2, 47, 28, 832666), + self.next_meeting(recurrence.QuadWeeklyRecurrence(week=3))) + def test_next_adhoc(self): self.assertEqual( None,