diff --git a/README.rst b/README.rst index 0b32bfe..98d78a6 100644 --- a/README.rst +++ b/README.rst @@ -266,7 +266,7 @@ To release new versions, 1. edit the Changelog Section 2. edit setup.py, the ``__version__`` variable 3. create a commit and push it -4. Wait for `GitHub Actions `_ to finish the build. +4. wait for `GitHub Actions `_ to finish the build 5. run .. code-block:: shell @@ -278,6 +278,12 @@ To release new versions, Changelog --------- +- v2.1.0 + + - Added support for PERIOD values in RDATE. See `Issue 113 `_. + - Fixed ``icalendar>=5.0.9`` to support ``RDATE`` of type ``PERIOD`` with a time zone. + - Fixed ``pytz>=2023.3`` to assure compatibility. + - v2.0.2 - Fixed omitting last event of ``RRULE`` with ``UNTIL`` when using ``pytz``, the event starting in winter time and ending in summer time. See `Issue 107 `_. diff --git a/recurring_ical_events.py b/recurring_ical_events.py index eb9cda3..4f47b49 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -169,6 +169,7 @@ def __init__(self, component, keep_recurrence_attributes=False): self.keep_recurrence_attributes = keep_recurrence_attributes self.exdates = [] self.exdates_utc = set() + self.replace_ends = {} # DTSTART -> DTEND # for periods exdates = component.get("EXDATE", []) for exdates in ((exdates,) if not isinstance(exdates, list) else exdates): for exdate in exdates.dts: @@ -178,7 +179,13 @@ def __init__(self, component, keep_recurrence_attributes=False): rdates = component.get("RDATE", []) for rdates in ((rdates,) if not isinstance(rdates, list) else rdates): for rdate in rdates.dts: - self.rdates.append(rdate.dt) + if isinstance(rdate.dt, tuple): + # we have a period as rdate + self.rdates.append(rdate.dt[0]) + self.replace_ends[timestamp(rdate.dt[0])] = rdate.dt[1] + else: + # we have a date/datetime + self.rdates.append(rdate.dt) self.make_all_dates_comparable() @@ -242,7 +249,7 @@ def rrulestr(self, rule_string): rule.until = until = self._get_rrule_until(rule) if is_pytz(self.start.tzinfo) and rule.until: # when starting in a time zone that is one hour off to the end, - # we might miss the last occurence + # we might miss the last occurrence # see issue 107 and test/test_issue_107_omitting_last_event.py rule = rule.replace(until=rule.until + datetime.timedelta(hours=1)) rule.until = until @@ -266,7 +273,7 @@ def make_all_dates_comparable(self): Dates may be mixed and we have many of them. - date - - datetime without timezome + - datetime without timezone - datetime with timezone These three are not comparable but can be converted. """ @@ -311,7 +318,7 @@ def within_days(self, span_start, span_stop): continue if self._unify_exdate(start) in self.exdates_utc: continue - stop = start + self.duration + stop = self.replace_ends.get(timestamp(start), start + self.duration) yield Repetition( self.component, self.convert_to_original_type(start), diff --git a/requirements.txt b/requirements.txt index f8dc157..25fa70a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -icalendar -pytz +icalendar>=5.0.9 +icalendar<6.0.0 +pytz>=2023.3 python-dateutil>=2.8.1 x-wr-timezone < 1.0.0 x-wr-timezone >= 0.0.5 diff --git a/setup.py b/setup.py index e673f10..6a1dcc8 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ HERE = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, HERE) # for package import -__version__ = "2.0.2" +__version__ = "2.0.3" __author__ = 'Nicco Kunzmann' @@ -124,7 +124,6 @@ def run(self): 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities', - 'Intended Audience :: Developers', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', diff --git a/test/calendars/test_issue_113_period_in_rdate.ics b/test/calendars/issue_113_period_in_rdate.ics similarity index 100% rename from test/calendars/test_issue_113_period_in_rdate.ics rename to test/calendars/issue_113_period_in_rdate.ics diff --git a/test/test_issue_113_period_in_rdate.py b/test/test_issue_113_period_in_rdate.py index 5ae6254..db3c861 100644 --- a/test/test_issue_113_period_in_rdate.py +++ b/test/test_issue_113_period_in_rdate.py @@ -1,11 +1,28 @@ """This tests that RDATE can be a PERIOD. See https://github.com/niccokunzmann/python-recurring-ical-events/issues/113 + + Value Type: The default value type for this property is DATE-TIME. + The value type can be set to DATE or PERIOD. + + If the "RDATE" property is + specified as a PERIOD value the duration of the recurrence + instance will be the one specified by the "RDATE" property, and + not the duration of the recurrence instance defined by the + "DTSTART" property. + """ from datetime import datetime import pytz -def test_rdate_is_period(calendars): - """The recurring event has a period rdate.""" - event = calendars.test_issue_113_period_in_rdate.at("20231213") - assert event["DTSTART"] == pytz.timezone("America/Vancouver").localize(datetime.datetime(2023, 12, 13, 12, 0)) \ No newline at end of file +def test_start_of_rdate(calendars): + """The event starts on that time.""" + event = calendars.issue_113_period_in_rdate.at("20231213")[0] + expected_start = pytz.timezone("America/Vancouver").localize(datetime(2023, 12, 13, 12, 0)) + start = event["DTSTART"].dt + assert start == expected_start + +def test_end_of_rdate(calendars): + """The event starts on that time.""" + event = calendars.issue_113_period_in_rdate.at("20231213")[0] + assert event["DTEND"].dt == pytz.timezone("America/Vancouver").localize(datetime(2023, 12, 13, 15, 0)) \ No newline at end of file diff --git a/test/test_rdate.py b/test/test_rdate.py index 93ad902..6558dbc 100644 --- a/test/test_rdate.py +++ b/test/test_rdate.py @@ -70,19 +70,6 @@ def test_rdate_and_rrule_can_be_excluded_by_exdate(calendars): events = calendars.rdate.at("20150705") assert len(events) == 0 -def test_period_as_rdate(todo): - """Test the PERIOD type. - - Value Type: The default value type for this property is DATE-TIME. - The value type can be set to DATE or PERIOD. - - If the "RDATE" property is - specified as a PERIOD value the duration of the recurrence - instance will be the one specified by the "RDATE" property, and - not the duration of the recurrence instance defined by the - "DTSTART" property. - """ - def test_rdate_occurs_multiple_times(calendars): """An event can not only have an RDATE once but also many of them.""" events = calendars.rdate_hackerpublicradio.all()