From e42b719cbaa5b13acc8efab666c706dba8aacae1 Mon Sep 17 00:00:00 2001 From: Matt Lewis Date: Mon, 29 May 2023 13:52:34 +0200 Subject: [PATCH] Fixed Issue 518 (RRULE BYDAY=xMO with x>=10 raises ValueError with to_ical()): updated WEEKDAY_RULE regex to accept 2 digits. Added tests for to_ical() covering various BYDAY values. --- CHANGES.rst | 1 + docs/credits.rst | 1 + src/icalendar/prop.py | 2 +- src/icalendar/tests/test_recurrence.py | 34 +++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6875a825..265d0bfd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,7 @@ New features: Bug fixes: +- to_ical() now accepts RRULE BYDAY values>=10 #518 - ... diff --git a/docs/credits.rst b/docs/credits.rst index 1cf1df80..9b91839f 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -68,6 +68,7 @@ icalendar contributors - Abe Hanoka - `Natasha Mattson `_ +- Matt Lewis Find out who contributed:: diff --git a/src/icalendar/prop.py b/src/icalendar/prop.py index 7ee13468..f322f65e 100644 --- a/src/icalendar/prop.py +++ b/src/icalendar/prop.py @@ -66,7 +66,7 @@ DURATION_REGEX = re.compile(r'([-+]?)P(?:(\d+)W)?(?:(\d+)D)?' r'(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$') -WEEKDAY_RULE = re.compile(r'(?P[+-]?)(?P[\d]?)' +WEEKDAY_RULE = re.compile(r'(?P[+-]?)(?P[\d]{0,2})' r'(?P[\w]{2})$') diff --git a/src/icalendar/tests/test_recurrence.py b/src/icalendar/tests/test_recurrence.py index 701b649e..03ea321b 100644 --- a/src/icalendar/tests/test_recurrence.py +++ b/src/icalendar/tests/test_recurrence.py @@ -1,4 +1,5 @@ -from datetime import datetime +from icalendar import Event +from datetime import date, datetime import pytest @@ -40,3 +41,34 @@ def test_list_exdate_to_ical_is_inverse_of_from_ical(events, i, exception_date, assert exdate[i].dts[0].dt == in_timezone(exception_date, 'Europe/Vienna') assert exdate[i].to_ical() == exception_date_ics +@pytest.mark.parametrize('freq, byday, dtstart, expected', [ + # Test some YEARLY BYDAY repeats + ('YEARLY', '1SU', date(2016,1,3), # 1st Sunday in year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n'), + ('YEARLY', '53MO', date(1984,12,31), # 53rd Monday in (leap) year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n'), + ('YEARLY', '-1TU', date(1999,12,28), # Last Tuesday in year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n'), + ('YEARLY', '-17WE', date(2000,9,6), # 17th-to-last Wednesday in year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n'), + # Test some MONTHLY BYDAY repeats + ('MONTHLY', '2TH', date(2003,4,10), # 2nd Thursday in month + b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n'), + ('MONTHLY', '-3FR', date(2017,5,12), # 3rd-to-last Friday in month + b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n'), + ('MONTHLY', '-5SA', date(2053,11,1), # 5th-to-last Saturday in month + b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n'), + # Specifically test examples from the report of Issue #518 + # https://github.com/collective/icalendar/issues/518 + ('YEARLY', '9MO', date(2023,2,27), # 9th Monday in year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n'), + ('YEARLY', '10MO', date(2023,3,6), # 10th Monday in year + b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n'), +]) +def test_byday_to_ical(freq, byday, dtstart, expected): + 'Test the BYDAY rule is correctly processed by to_ical().' + event = Event() + event.add('SUMMARY', ' '.join(['Event', freq, byday])) + event.add('DTSTART', dtstart) + event.add('RRULE', {'FREQ':[freq], 'BYDAY':byday}) + assert event.to_ical() == expected