feat: add support for MONTHLY events with BYDAY clause

fixes #16 and #24
pull/25/head
Pablo Castellano 2018-09-16 14:34:36 +02:00
rodzic d2f5e62061
commit 1f05d58c72
3 zmienionych plików z 57 dodań i 9 usunięć

Wyświetl plik

@ -4,6 +4,7 @@ Parse iCal data to Events.
# for UID generation # for UID generation
from random import randint from random import randint
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
from dateutil import relativedelta
from icalendar import Calendar from icalendar import Calendar
from pytz import utc from pytz import utc
@ -297,20 +298,29 @@ def create_recurring_events(start, end, component):
unfolded.append(current) unfolded.append(current)
else: else:
break break
if freq == 'MONTHLY': elif freq == 'MONTHLY':
by_day = rule.get('BYDAY')
while True: while True:
current = current.copy_to(next_month_at(current.start)) if by_day:
next_date = next_month_byday_delta(current.start, by_day[0])
current = current.copy_to(next_date)
else:
current = current.copy_to(next_month_at(current.start))
if current.start < limit: if current.start < limit:
unfolded.append(current) unfolded.append(current)
else: else:
break break
else: elif freq == 'DAILY':
if freq == 'DAILY': delta = timedelta(days=1)
delta = timedelta(days=1) while True:
elif freq == 'WEEKLY': current = current.copy_to(current.start + delta)
delta = timedelta(days=7) if current.start < limit:
else: unfolded.append(current)
return else:
break
elif freq == 'WEEKLY':
delta = timedelta(days=7)
by_day = rule.get('BYDAY') by_day = rule.get('BYDAY')
if by_day: if by_day:
@ -326,6 +336,8 @@ def create_recurring_events(start, end, component):
unfolded.append(current) unfolded.append(current)
else: else:
break break
else:
return
return in_range(unfolded, start, end) return in_range(unfolded, start, end)
@ -361,3 +373,26 @@ def generate_day_deltas_by_weekday(by_day):
adjusted_deltas = day_deltas[1:] + [first_hop_count] adjusted_deltas = day_deltas[1:] + [first_hop_count]
return dict(zip(selected_weekday_numbers, adjusted_deltas)) return dict(zip(selected_weekday_numbers, adjusted_deltas))
def next_month_byday_delta(start_date, by_day):
"""
Get the next event date when a MONTHLY rule contains a BYDAY clause,
e.g. 3SA = "Next third Saturday"
"""
weekdays = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
number = int(by_day[0])
weekday = by_day[1:]
if weekday not in weekdays:
raise ValueError('Invalid weekday: {}'.format(weekday))
weekday = weekdays.index(weekday)
weekday_func = relativedelta.weekday(weekday)
delta = relativedelta.relativedelta(day=1, months=+1,
weekday=weekday_func(number))
return start_date + delta

1
test.py 100644 → 100755
Wyświetl plik

@ -1,3 +1,4 @@
#!/usr/bin/env python3
import unittest import unittest
from test.test_icaldownload import * from test.test_icaldownload import *

Wyświetl plik

@ -114,3 +114,15 @@ class ICalParserTests(unittest.TestCase):
result = icalevents.icalparser.generate_day_deltas_by_weekday(by_day) result = icalevents.icalparser.generate_day_deltas_by_weekday(by_day)
self.assertEqual(7, result[0], 'Mon to Mon') self.assertEqual(7, result[0], 'Mon to Mon')
def test_next_month_byday_delta(self):
dt = date(2018, 9, 15)
result = icalevents.icalparser.next_month_byday_delta(dt, "3SA")
self.assertEqual(date(2018, 10, 20), result, '3rd Saturday next month')
result = icalevents.icalparser.next_month_byday_delta(dt, "2TU")
self.assertEqual(date(2018, 10, 9), result, '2nd Tuesday next month')
with self.assertRaises(ValueError, msg='Invalid weekday'):
icalevents.icalparser.next_month_byday_delta(dt, "1ZZ")