add monthly recurrence options
Add a recurrence option for meetings that repeat monthly. Instantiate the ones for the first weekdays of the month for now. Change-Id: I0fa95653594dc5a28008630f57bee67b92537d29 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
parent
816a1e4815
commit
6a21064401
@ -163,6 +163,14 @@ yaml2ical supports a number of possible frequency options:
|
||||
* ``quadweekly-week-2``, ``quadweekly-alternate``: Occurs when ``ISOweek % 4 == 2``
|
||||
* ``quadweekly-week-3``: Occurs when ``ISOweek % 4 == 3``
|
||||
|
||||
* Event occurs in the first week of a month:
|
||||
|
||||
* ``first-monday``: On the first Monday of the month.
|
||||
* ``first-tuesday``: On the first Tuesday of the month.
|
||||
* ``first-wednesday``: On the first Wednesday of the month.
|
||||
* ``first-thursday``: On the first Thursday of the month.
|
||||
* ``first-friday``: On the first Friday of the month.
|
||||
|
||||
* Event doesn't happen on a defined schedule but is used as a placeholder for
|
||||
html generation:
|
||||
|
||||
|
@ -147,6 +147,11 @@ class Schedule(object):
|
||||
'quadweekly-week-2': set([2]),
|
||||
'quadweekly-week-3': set([3]),
|
||||
'quadweekly-alternate': set([2]),
|
||||
'first-monday': set([0, 1, 2, 3]),
|
||||
'first-tuesday': set([0, 1, 2, 3]),
|
||||
'first-wednesday': set([0, 1, 2, 3]),
|
||||
'first-thursday': set([0, 1, 2, 3]),
|
||||
'first-friday': set([0, 1, 2, 3]),
|
||||
}
|
||||
|
||||
return len(week[self.freq].intersection(week[other.freq])) > 0
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
|
||||
|
||||
@ -131,6 +132,66 @@ class AdhocRecurrence(object):
|
||||
def __str__(self):
|
||||
return "Occurs as needed, no fixed schedule."
|
||||
|
||||
|
||||
class MonthlyRecurrence(object):
|
||||
"""Meetings occuring every month."""
|
||||
def __init__(self, week, day):
|
||||
self._week = week
|
||||
self._day = day
|
||||
|
||||
def next_occurence(self, current_date_time, day):
|
||||
"""Return the date of the next meeting.
|
||||
|
||||
:param current_date_time: datetime object of meeting
|
||||
:param day: weekday the meeting is held on
|
||||
|
||||
:returns: datetime object of the next meeting time
|
||||
"""
|
||||
weekday = WEEKDAYS[day]
|
||||
|
||||
month = current_date_time.month + 1
|
||||
year = current_date_time.year
|
||||
if current_date_time.month == 12:
|
||||
month = 1
|
||||
year = year + 1
|
||||
next_month_dates = calendar.monthcalendar(year, month)
|
||||
|
||||
# We can't simply index into the dates for the next month
|
||||
# because we don't know that the first week is full of days
|
||||
# that actually appear in that month. Therefore we loop
|
||||
# through them counting down until we've skipped enough weeks.
|
||||
skip_weeks = self._week - 1
|
||||
for week in next_month_dates:
|
||||
day = week[weekday]
|
||||
# Dates in the week that fall in other months
|
||||
# are 0 so we want to skip counting those weeks.
|
||||
if not day:
|
||||
continue
|
||||
# If we have skipped all of the weeks we need to,
|
||||
# we have the day.
|
||||
if not skip_weeks:
|
||||
return datetime.datetime(
|
||||
year, month, day,
|
||||
current_date_time.hour, current_date_time.minute,
|
||||
current_date_time.second, current_date_time.microsecond,
|
||||
)
|
||||
skip_weeks -= 1
|
||||
|
||||
raise ValueError(
|
||||
'Could not compute week {} of next month for {}'.format(
|
||||
self._week, current_date_time)
|
||||
)
|
||||
|
||||
def rrule(self):
|
||||
return {
|
||||
'freq': 'monthly',
|
||||
'byday': '{}{}'.format(self._week, self._day[:2].upper()),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "Monthly"
|
||||
|
||||
|
||||
supported_recurrences = {
|
||||
'weekly': WeeklyRecurrence(),
|
||||
'biweekly-odd': BiWeeklyRecurrence(style='odd'),
|
||||
@ -141,4 +202,9 @@ supported_recurrences = {
|
||||
'quadweekly-week-3': QuadWeeklyRecurrence(week=3),
|
||||
'quadweekly-alternate': QuadWeeklyRecurrence(week=2),
|
||||
'adhoc': AdhocRecurrence(),
|
||||
'first-monday': MonthlyRecurrence(week=1, day='Monday'),
|
||||
'first-tuesday': MonthlyRecurrence(week=1, day='Tuesday'),
|
||||
'first-wednesday': MonthlyRecurrence(week=1, day='Wednesday'),
|
||||
'first-thursday': MonthlyRecurrence(week=1, day='Thursday'),
|
||||
'first-friday': MonthlyRecurrence(week=1, day='Friday'),
|
||||
}
|
||||
|
@ -362,3 +362,87 @@ chair: John Doe
|
||||
description: >
|
||||
Example Quadweekly Alternate meeting
|
||||
"""
|
||||
|
||||
FIRST_MONDAY_MEETING = """
|
||||
project: OpenStack Random Meeting
|
||||
agenda_url: http://agenda.com/
|
||||
project_url: http://project.com
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Monday
|
||||
irc: openstack-meeting
|
||||
frequency: first-monday
|
||||
chair: John Doe
|
||||
description: >
|
||||
Example Monthly meeting
|
||||
"""
|
||||
|
||||
FIRST_TUESDAY_MEETING = """
|
||||
project: OpenStack Random Meeting
|
||||
agenda_url: http://agenda.com/
|
||||
project_url: http://project.com
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Tuesday
|
||||
irc: openstack-meeting
|
||||
frequency: first-tuesday
|
||||
chair: John Doe
|
||||
description: >
|
||||
Example Monthly meeting
|
||||
"""
|
||||
|
||||
WEEKLY_MEETING_2200 = """
|
||||
project: OpenStack Subteam Meeting
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Wednesday
|
||||
irc: openstack-meeting
|
||||
frequency: weekly
|
||||
chair: Joe Developer
|
||||
description: >
|
||||
Weekly meeting for Subteam project.
|
||||
agenda: |
|
||||
* Top bugs this week
|
||||
"""
|
||||
|
||||
FIRST_WEDNESDAY_MEETING = """
|
||||
project: OpenStack Random Meeting
|
||||
agenda_url: http://agenda.com/
|
||||
project_url: http://project.com
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Wednesday
|
||||
irc: openstack-meeting
|
||||
frequency: first-wednesday
|
||||
chair: John Doe
|
||||
description: >
|
||||
Example Monthly meeting
|
||||
"""
|
||||
|
||||
FIRST_THURSDAY_MEETING = """
|
||||
project: OpenStack Random Meeting
|
||||
agenda_url: http://agenda.com/
|
||||
project_url: http://project.com
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Thursday
|
||||
irc: openstack-meeting
|
||||
frequency: first-thursday
|
||||
chair: John Doe
|
||||
description: >
|
||||
Example Monthly meeting
|
||||
"""
|
||||
|
||||
FIRST_FRIDAY_MEETING = """
|
||||
project: OpenStack Random Meeting
|
||||
agenda_url: http://agenda.com/
|
||||
project_url: http://project.com
|
||||
schedule:
|
||||
- time: '2200'
|
||||
day: Friday
|
||||
irc: openstack-meeting
|
||||
frequency: first-friday
|
||||
chair: John Doe
|
||||
description: >
|
||||
Example Monthly meeting
|
||||
"""
|
||||
|
@ -163,6 +163,20 @@ class MeetingTestCase(unittest.TestCase):
|
||||
sample_data.CONFLICTING_WEEKLY_MEETING,
|
||||
sample_data.MEETING_WITH_DURATION)
|
||||
|
||||
def test_monthly_conflicts(self):
|
||||
self.should_be_conflicting(
|
||||
sample_data.WEEKLY_MEETING_2200,
|
||||
sample_data.FIRST_WEDNESDAY_MEETING)
|
||||
self.should_be_conflicting(
|
||||
sample_data.BIWEEKLY_EVEN_MEETING,
|
||||
sample_data.FIRST_WEDNESDAY_MEETING)
|
||||
self.should_be_conflicting(
|
||||
sample_data.QUADWEEKLY_MEETING,
|
||||
sample_data.FIRST_WEDNESDAY_MEETING)
|
||||
self.should_be_conflicting(
|
||||
sample_data.ALTERNATING_MEETING,
|
||||
sample_data.FIRST_WEDNESDAY_MEETING)
|
||||
|
||||
def test_skip_meeting(self):
|
||||
meeting_yaml = sample_data.MEETING_WITH_SKIP_DATES
|
||||
# Copied from sample_data.MEETING_WITH_SKIP_DATES
|
||||
|
@ -83,3 +83,25 @@ class RecurrenceTestCase(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
'Every four weeks on week %d of the four week rotation' % i,
|
||||
str(recurrence.QuadWeeklyRecurrence(week=i)))
|
||||
|
||||
def test_monthly_first_week(self):
|
||||
rec = recurrence.MonthlyRecurrence(week=1, day='Wednesday')
|
||||
self.assertEqual(
|
||||
datetime.datetime(2014, 11, 5, 2, 47, 28, 832666),
|
||||
self.next_meeting(rec),
|
||||
)
|
||||
|
||||
def test_monthly_second_week(self):
|
||||
rec = recurrence.MonthlyRecurrence(week=2, day='Wednesday')
|
||||
self.assertEqual(
|
||||
datetime.datetime(2014, 11, 12, 2, 47, 28, 832666),
|
||||
self.next_meeting(rec),
|
||||
)
|
||||
|
||||
def test_monthly_invalid_week(self):
|
||||
rec = recurrence.MonthlyRecurrence(week=6, day='Wednesday')
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.next_meeting,
|
||||
rec,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user