From bb8dc977fe41934c8c464a7756f0dd5f73d978d3 Mon Sep 17 00:00:00 2001 From: Jabiertxof Date: Wed, 18 Sep 2024 13:03:57 +0200 Subject: [PATCH 1/7] Add RECURRENCE-ID with range parameter --- recurring_ical_events.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/recurring_ical_events.py b/recurring_ical_events.py index 2c916db..8f3bda0 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -519,7 +519,7 @@ def rrule_between(self, span_start: Time, span_stop: Time) -> Generator[Time]: span_stop_dt + datetime.timedelta(hours=1) ) for rule in self.rrules: - for start in rule.between(span_start_dt, span_stop_dt, inc=True): + for start in rule.between(rule._dtstart, span_stop_dt, inc=True): if is_pytz_dt(start): # update the time zone in case of summer/winter time change start = start.tzinfo.localize(start.replace(tzinfo=None)) # noqa: PLW2901 @@ -595,6 +595,7 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: returned_modifications: set[ComponentAdapter] = set() # NOTE: If in the following line, we get an error, datetime and date # may still be mixed because RDATE, EXDATE, start and rule. + prev_adapter = None for start in self.recurrence.rrule_between(span_start, span_stop): recurrence_ids = to_recurrence_ids(start) if ( @@ -606,7 +607,18 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: adapter: ComponentAdapter = get_any( self.recurrence_id_to_modification, recurrence_ids, self.recurrence.core ) - if adapter is self.recurrence.core: + if prev_adapter and adapter is self.recurrence.core: + start_dt = datetime.datetime.combine(start.date(), prev_adapter.start.time()) + stop = get_any( + self.recurrence.replace_ends, + recurrence_ids, + normalize_pytz(start_dt + prev_adapter.duration), + ) + core = self.recurrence.core + self.recurrence.core = prev_adapter + occurrence = self.recurrence.as_occurrence(start_dt, stop, self.occurrence) + self.recurrence.core = core + elif adapter is self.recurrence.core: stop = get_any( self.recurrence.replace_ends, recurrence_ids, @@ -620,6 +632,8 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: continue returned_modifications.add(adapter) occurrence = self.occurrence(adapter) + if adapter.thisandfuture: + prev_adapter = adapter if occurrence.is_in_span(span_start, span_stop): yield occurrence for modification in self.modifications: @@ -650,7 +664,7 @@ def __repr__(self): class ComponentAdapter(ABC): """A unified interface to work with icalendar components.""" - ATTRIBUTES_TO_DELETE_ON_COPY = ["RRULE", "RDATE", "EXDATE"] + ATTRIBUTES_TO_DELETE_ON_COPY = ["RRULE", "RDATE", "EXDATE", "RECURRENCE-ID"] @staticmethod @abstractmethod @@ -720,6 +734,18 @@ def recurrence_ids(self) -> RecurrenceIDs: return () return to_recurrence_ids(recurrence_id.dt) + @cached_property + def thisandfuture(self) -> bool: + + """The recurrence ids has a thisand future range property""" + recurrence_id = self._component.get("RECURRENCE-ID") + if recurrence_id is None: + return False + if "RANGE" in recurrence_id.params: + return recurrence_id.params["RANGE"] == "THISANDFUTURE" + return False + + def is_modification(self) -> bool: """Whether the adapter is a modification.""" return bool(self.recurrence_ids) @@ -912,7 +938,7 @@ def end(self) -> Time: class Occurrence: """A repetition of an event.""" - ATTRIBUTES_TO_DELETE_ON_COPY = ["RRULE", "RDATE", "EXDATE"] + ATTRIBUTES_TO_DELETE_ON_COPY = ["RRULE", "RDATE", "EXDATE", "RECURRENCE-ID"] def __init__( self, From ca9cc55f23b4689658431947b54bf73b1cd9ea34 Mon Sep 17 00:00:00 2001 From: Jabiertxof Date: Wed, 18 Sep 2024 13:14:46 +0200 Subject: [PATCH 2/7] Add test from https://github.com/niccokunzmann/python-recurring-ical-events/pull/170 --- test/calendars/issue_75_range_parameter.ics | 29 +++++++++++++++ test/test_issue_75_range_parameter.py | 41 +++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/calendars/issue_75_range_parameter.ics create mode 100644 test/test_issue_75_range_parameter.py diff --git a/test/calendars/issue_75_range_parameter.ics b/test/calendars/issue_75_range_parameter.ics new file mode 100644 index 0000000..85f2987 --- /dev/null +++ b/test/calendars/issue_75_range_parameter.ics @@ -0,0 +1,29 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:RESERVAS 1.0//EN +BEGIN:VEVENT +UID:210 +DTSTART:20240901T120000Z +DTEND:20240901T140000Z +RRULE:FREQ=DAILY;INTERVAL=2;UNTIL=20250920 +RDATE:20240924T090000Z +SEQUENCE:0 +SUMMARY:ORIGINAL EVENT +END:VEVENT +BEGIN:VEVENT +UID:210 +RECURRENCE-ID;RANGE=THISANDFUTURE:20240913T120000Z +DTSTART:20240913T090000Z +DTEND:20240913T160000Z +SEQUENCE:1 +SUMMARY:MODIFIED EVENT +END:VEVENT +BEGIN:VEVENT +UID:210 +RECURRENCE-ID;RANGE=THISANDFUTURE:20240923T120000Z +DTSTART:20240923T090000Z +DTEND:20240923T160000Z +SEQUENCE:1 +SUMMARY:EDITED EVENT +END:VEVENT +END:VCALENDAR diff --git a/test/test_issue_75_range_parameter.py b/test/test_issue_75_range_parameter.py new file mode 100644 index 0000000..7a0b327 --- /dev/null +++ b/test/test_issue_75_range_parameter.py @@ -0,0 +1,41 @@ + +"""This tests the range parameter for ics file. +see https://github.com/niccokunzmann/python-recurring-ical-events/issues/75 +Description: This parameter can be specified on a property that + specifies a recurrence identifier. The parameter specifies the + effective range of recurrence instances that is specified by the + property. The effective range is from the recurrence identifier + specified by the property. If this parameter is not specified on + an allowed property, then the default range is the single instance + specified by the recurrence identifier value of the property. The + parameter value can only be "THISANDFUTURE" to indicate a range + defined by the recurrence identifier and all subsequent instances. + The value "THISANDPRIOR" is deprecated by this revision of + iCalendar and MUST NOT be generated by applications. +""" + +import pytest + + +@pytest.mark.parametrize( + ("date", "summary"), + [ + ("20240901", "ORGINAL EVENT"), + ("20240911", "ORGINAL EVENT"), + ("20240913", "MODIFIED EVENT"), + ("20240915", "MODIFIED EVENT"), + ("20240917", "MODIFIED EVENT"), + ("20240919", "MODIFIED EVENT"), + ("20240921", "MODIFIED EVENT"), + ("20240923", "EDITED EVENT"), + ("20240924", "EDITED EVENT"), # RDATE + ("20240925", "EDITED EVENT"), + ], +) +def test_issue_75_RANGE_parameter(calendars, date, summary): + events = calendars.issue_75_range_parameter.at(date) + assert len(events) == 1, f"Expecting one event at {date}" + event = events[0] + assert event["SUMMARY"] == summary + +# TODO: Test DTSTART and DTEND From 477dded62d156e4dd69e70c369c5e0617b0efc8e Mon Sep 17 00:00:00 2001 From: Jabiertxof Date: Wed, 18 Sep 2024 14:52:14 +0200 Subject: [PATCH 3/7] fix tests --- test/conftest.py | 9 +++++++-- test/py.py | 15 +++++++++++++++ test/test_issue_75_range_parameter.py | 13 +++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 test/py.py diff --git a/test/conftest.py b/test/conftest.py index 01d8874..2fe86fd 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,8 +1,13 @@ -import sys +import sys,os import time from datetime import timezone from pathlib import Path - +try: + DESKTOPPATH = Path(__file__).resolve().parent +except: + print("error encontrando ruta de la aplicacione") +sys.path.append(os.path.join(Path(__file__).resolve().parent, 'inc')) +print(os.path.join(Path(__file__).resolve().parent, 'inc')) import dateutil import icalendar import pytest diff --git a/test/py.py b/test/py.py new file mode 100644 index 0000000..5c661e6 --- /dev/null +++ b/test/py.py @@ -0,0 +1,15 @@ +# shim for pylib going away +# if pylib is installed this file will get skipped +# (`py/__init__.py` has higher precedence) +from __future__ import annotations + +import sys + +import _pytest._py.error as error +import _pytest._py.path as path + + +sys.modules["py.error"] = error +sys.modules["py.path"] = path + +__all__ = ["error", "path"] diff --git a/test/test_issue_75_range_parameter.py b/test/test_issue_75_range_parameter.py index 7a0b327..6e752ab 100644 --- a/test/test_issue_75_range_parameter.py +++ b/test/test_issue_75_range_parameter.py @@ -20,15 +20,15 @@ @pytest.mark.parametrize( ("date", "summary"), [ - ("20240901", "ORGINAL EVENT"), - ("20240911", "ORGINAL EVENT"), - ("20240913", "MODIFIED EVENT"), + ("20240901", "ORIGINAL EVENT"), + ("20240911", "ORIGINAL EVENT"), + ("20240913", "MODIFIED EVENT"), # it span to start of day so no modified ("20240915", "MODIFIED EVENT"), ("20240917", "MODIFIED EVENT"), ("20240919", "MODIFIED EVENT"), ("20240921", "MODIFIED EVENT"), - ("20240923", "EDITED EVENT"), - ("20240924", "EDITED EVENT"), # RDATE + ("20240923", "EDITED EVENT"), # it span to start of day so no edited + #("20240924", "EDITED EVENT"), # RDATE:need to work on ("20240925", "EDITED EVENT"), ], ) @@ -36,6 +36,7 @@ def test_issue_75_RANGE_parameter(calendars, date, summary): events = calendars.issue_75_range_parameter.at(date) assert len(events) == 1, f"Expecting one event at {date}" event = events[0] - assert event["SUMMARY"] == summary + print( str(event["SUMMARY"]),date,summary) + assert str(event["SUMMARY"]) == summary # TODO: Test DTSTART and DTEND From e7192061fffd0693ffa28e1eb7ab2206962bc53f Mon Sep 17 00:00:00 2001 From: Jabiertxof Date: Wed, 18 Sep 2024 15:22:24 +0200 Subject: [PATCH 4/7] fix tests rdate --- recurring_ical_events.py | 3 ++- test/test_issue_75_range_parameter.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/recurring_ical_events.py b/recurring_ical_events.py index 8f3bda0..aa5c750 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -499,6 +499,7 @@ def make_all_dates_comparable(self): def rrule_between(self, span_start: Time, span_stop: Time) -> Generator[Time]: """Recalculate the rrules so that minor mistakes are corrected.""" # TODO: optimize and only return what is in the span + yield from self.rule_set # make dates comparable, rrule converts them to datetimes span_start_dt = convert_to_datetime(span_start, self.tzinfo) @@ -596,7 +597,7 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: # NOTE: If in the following line, we get an error, datetime and date # may still be mixed because RDATE, EXDATE, start and rule. prev_adapter = None - for start in self.recurrence.rrule_between(span_start, span_stop): + for start in sorted(self.recurrence.rrule_between(span_start, span_stop)): recurrence_ids = to_recurrence_ids(start) if ( start in returned_starts diff --git a/test/test_issue_75_range_parameter.py b/test/test_issue_75_range_parameter.py index 6e752ab..db46d6c 100644 --- a/test/test_issue_75_range_parameter.py +++ b/test/test_issue_75_range_parameter.py @@ -28,7 +28,7 @@ ("20240919", "MODIFIED EVENT"), ("20240921", "MODIFIED EVENT"), ("20240923", "EDITED EVENT"), # it span to start of day so no edited - #("20240924", "EDITED EVENT"), # RDATE:need to work on + ("20240924", "EDITED EVENT"), # RDATE ("20240925", "EDITED EVENT"), ], ) From 2c6eabdcbfdfed4dca7d2271770a76bab9734708 Mon Sep 17 00:00:00 2001 From: Jabiertxof Date: Wed, 18 Sep 2024 15:25:52 +0200 Subject: [PATCH 5/7] remove legacy comment to test --- test/test_issue_75_range_parameter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_issue_75_range_parameter.py b/test/test_issue_75_range_parameter.py index db46d6c..328a6e0 100644 --- a/test/test_issue_75_range_parameter.py +++ b/test/test_issue_75_range_parameter.py @@ -22,12 +22,12 @@ [ ("20240901", "ORIGINAL EVENT"), ("20240911", "ORIGINAL EVENT"), - ("20240913", "MODIFIED EVENT"), # it span to start of day so no modified + ("20240913", "MODIFIED EVENT"), ("20240915", "MODIFIED EVENT"), ("20240917", "MODIFIED EVENT"), ("20240919", "MODIFIED EVENT"), ("20240921", "MODIFIED EVENT"), - ("20240923", "EDITED EVENT"), # it span to start of day so no edited + ("20240923", "EDITED EVENT"), ("20240924", "EDITED EVENT"), # RDATE ("20240925", "EDITED EVENT"), ], From 3ea9216d2e6207aaf80e1a1cbcf420f76dbe39b0 Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Thu, 19 Sep 2024 23:58:31 +0200 Subject: [PATCH 6/7] Fix review --- recurring_ical_events.py | 33 ++++++++++++-------- test/calendars/issue_75_range_parameter.ics | 14 +++++++-- test/conftest.py | 8 +---- test/test_issue_75_range_parameter.py | 34 ++++++++++++++++++--- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/recurring_ical_events.py b/recurring_ical_events.py index aa5c750..1dc0fa8 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -320,7 +320,7 @@ class NoRecurrence: replace_ends: dict[RecurrenceID, Time] = {} def as_occurrence( - self, start: Time, stop: Time, occurrence: type[Occurrence] + self, start: Time, stop: Time, occurrence: type[Occurrence], core: ComponentAdapter ) -> Occurrence: raise NotImplementedError("This code should never be reached.") @@ -520,7 +520,7 @@ def rrule_between(self, span_start: Time, span_stop: Time) -> Generator[Time]: span_stop_dt + datetime.timedelta(hours=1) ) for rule in self.rrules: - for start in rule.between(rule._dtstart, span_stop_dt, inc=True): + for start in rule.between(span_start_dt, span_stop_dt, inc=True): if is_pytz_dt(start): # update the time zone in case of summer/winter time change start = start.tzinfo.localize(start.replace(tzinfo=None)) # noqa: PLW2901 @@ -547,11 +547,11 @@ def convert_to_original_type(self, date): return date def as_occurrence( - self, start: Time, stop: Time, occurrence: type[Occurrence] + self, start: Time, stop: Time, occurrence: type[Occurrence], core: ComponentAdapter ) -> Occurrence: """Return this as an occurrence at a specific time.""" return occurrence( - self.core, + core, self.convert_to_original_type(start), self.convert_to_original_type(stop), ) @@ -597,7 +597,19 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: # NOTE: If in the following line, we get an error, datetime and date # may still be mixed because RDATE, EXDATE, start and rule. prev_adapter = None - for start in sorted(self.recurrence.rrule_between(span_start, span_stop)): + starts = sorted(self.recurrence.rrule_between(span_start, span_stop)) + rangestarts = [] + rangemax = convert_to_datetime(DATE_MIN_DT, self.recurrence.tzinfo) + for modification in self.modifications: + if modification.thisandfuture and convert_to_datetime(span_start, self.recurrence.tzinfo) > modification.start: + if modification.start > rangemax: + prev_adapter = modification + rangemax = modification.start + rangestarts.append(modification.start) + if rangestarts: + rangestarts.sort() + starts = [starts[0], rangestarts[-1]-self.recurrence.core.duration, * starts[1:]] + for start in starts: recurrence_ids = to_recurrence_ids(start) if ( start in returned_starts @@ -608,24 +620,21 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: adapter: ComponentAdapter = get_any( self.recurrence_id_to_modification, recurrence_ids, self.recurrence.core ) - if prev_adapter and adapter is self.recurrence.core: + if starts[0] != start and prev_adapter and adapter is self.recurrence.core: start_dt = datetime.datetime.combine(start.date(), prev_adapter.start.time()) stop = get_any( self.recurrence.replace_ends, recurrence_ids, normalize_pytz(start_dt + prev_adapter.duration), ) - core = self.recurrence.core - self.recurrence.core = prev_adapter - occurrence = self.recurrence.as_occurrence(start_dt, stop, self.occurrence) - self.recurrence.core = core + occurrence = self.recurrence.as_occurrence(start_dt, stop, self.occurrence, prev_adapter) elif adapter is self.recurrence.core: stop = get_any( self.recurrence.replace_ends, recurrence_ids, normalize_pytz(start + self.recurrence.core.duration), ) - occurrence = self.recurrence.as_occurrence(start, stop, self.occurrence) + occurrence = self.recurrence.as_occurrence(start, stop, self.occurrence, self.recurrence.core) returned_starts.add(start) else: # We found a modification @@ -939,8 +948,6 @@ def end(self) -> Time: class Occurrence: """A repetition of an event.""" - ATTRIBUTES_TO_DELETE_ON_COPY = ["RRULE", "RDATE", "EXDATE", "RECURRENCE-ID"] - def __init__( self, adapter: ComponentAdapter, diff --git a/test/calendars/issue_75_range_parameter.ics b/test/calendars/issue_75_range_parameter.ics index 85f2987..b3c5bd5 100644 --- a/test/calendars/issue_75_range_parameter.ics +++ b/test/calendars/issue_75_range_parameter.ics @@ -20,9 +20,17 @@ SUMMARY:MODIFIED EVENT END:VEVENT BEGIN:VEVENT UID:210 -RECURRENCE-ID;RANGE=THISANDFUTURE:20240923T120000Z -DTSTART:20240923T090000Z -DTEND:20240923T160000Z +RECURRENCE-ID:20240915T120000Z +DTSTART:20240915T170000Z +DTEND:20240915T190000Z +SEQUENCE:1 +SUMMARY:MODIFIED EVENT +END:VEVENT +BEGIN:VEVENT +UID:210 +RECURRENCE-ID;RANGE=THISANDFUTURE:20240922T120000Z +DTSTART:20240922T142200Z +DTEND:20240922T161300Z SEQUENCE:1 SUMMARY:EDITED EVENT END:VEVENT diff --git a/test/conftest.py b/test/conftest.py index 2fe86fd..f0f6f00 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,13 +1,7 @@ -import sys,os +import sys import time from datetime import timezone from pathlib import Path -try: - DESKTOPPATH = Path(__file__).resolve().parent -except: - print("error encontrando ruta de la aplicacione") -sys.path.append(os.path.join(Path(__file__).resolve().parent, 'inc')) -print(os.path.join(Path(__file__).resolve().parent, 'inc')) import dateutil import icalendar import pytest diff --git a/test/test_issue_75_range_parameter.py b/test/test_issue_75_range_parameter.py index 328a6e0..a3a0380 100644 --- a/test/test_issue_75_range_parameter.py +++ b/test/test_issue_75_range_parameter.py @@ -23,20 +23,46 @@ ("20240901", "ORIGINAL EVENT"), ("20240911", "ORIGINAL EVENT"), ("20240913", "MODIFIED EVENT"), - ("20240915", "MODIFIED EVENT"), + ("20240915", "MODIFIED EVENT"), # Normal recurrence-id ("20240917", "MODIFIED EVENT"), ("20240919", "MODIFIED EVENT"), ("20240921", "MODIFIED EVENT"), - ("20240923", "EDITED EVENT"), + ("20240922", "EDITED EVENT"), ("20240924", "EDITED EVENT"), # RDATE ("20240925", "EDITED EVENT"), ], ) -def test_issue_75_RANGE_parameter(calendars, date, summary): +def test_issue_75_RANGE_AT_parameter(calendars, date, summary): events = calendars.issue_75_range_parameter.at(date) assert len(events) == 1, f"Expecting one event at {date}" event = events[0] - print( str(event["SUMMARY"]),date,summary) + assert str(event["SUMMARY"]) == summary + +@pytest.mark.parametrize( + ("start", "end", "summary","total"), + [ + ("20240901T000000Z", "20240911T235959Z", "ORIGINAL EVENT",6), + ("20240901T000000Z", "20240913T000000Z", "ORIGINAL EVENT",6), + ("20240901T000000Z", "20240913T235959Z", "MODIFIED EVENT",7), + ("20240901T000000Z", "20240915T235959Z", "MODIFIED EVENT",8), # Normal recurrence-id + ("20240901T000000Z", "20240917T235959Z", "MODIFIED EVENT",9), + ("20240901T000000Z", "20240919T235959Z", "MODIFIED EVENT",10), + ("20240901T000000Z", "20240921T235959Z", "MODIFIED EVENT",11), + ("20240901T000000Z", "20240922T000000Z", "MODIFIED EVENT",11), + ("20240901T000000Z", "20240922T235959Z", "EDITED EVENT",12), + ("20240901T000000Z", "20240923T000000Z", "EDITED EVENT",12), + ("20240901T000000Z", "20240923T235959Z", "EDITED EVENT",13), + ("20240901T000000Z", "20240924T235959Z", "EDITED EVENT",14), # RDATE + ("20240901T000000Z", "20240925T235959Z", "EDITED EVENT",15), + ("20240913T000000Z", "20240922T000000Z", "MODIFIED EVENT",5), # out of query bounds + ("20240913T000000Z", "20240922T235959Z", "EDITED EVENT",6), # out of query bounds + ("20240924T000000Z", "20240925T235959Z", "EDITED EVENT",2), # out of query bounds + ], +) +def test_issue_75_RANGE_BETWEEN_parameter(calendars, start, end, summary, total): + events = calendars.issue_75_range_parameter.between(start,end) + assert len(events) == total, f"Expecting {total} events at range {start}, {end}, get {len(events)}" + event = events[-1] assert str(event["SUMMARY"]) == summary # TODO: Test DTSTART and DTEND From 2ab8833a9d773830ed5d50fc65cde0f0b5dd7f8e Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Fri, 20 Sep 2024 00:42:45 +0200 Subject: [PATCH 7/7] fix tests --- recurring_ical_events.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/recurring_ical_events.py b/recurring_ical_events.py index 1dc0fa8..7dabb30 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -598,17 +598,16 @@ def between(self, span_start: Time, span_stop: Time) -> Generator[Occurrence]: # may still be mixed because RDATE, EXDATE, start and rule. prev_adapter = None starts = sorted(self.recurrence.rrule_between(span_start, span_stop)) - rangestarts = [] - rangemax = convert_to_datetime(DATE_MIN_DT, self.recurrence.tzinfo) - for modification in self.modifications: - if modification.thisandfuture and convert_to_datetime(span_start, self.recurrence.tzinfo) > modification.start: - if modification.start > rangemax: - prev_adapter = modification - rangemax = modification.start - rangestarts.append(modification.start) - if rangestarts: - rangestarts.sort() - starts = [starts[0], rangestarts[-1]-self.recurrence.core.duration, * starts[1:]] + if type(self.recurrence) != Series.NoRecurrence: + mindatetime = convert_to_datetime(DATE_MIN_DT, self.recurrence.tzinfo) + rangestarts = mindatetime + for modification in self.modifications: + if modification.thisandfuture and convert_to_datetime(span_start, self.recurrence.tzinfo) > modification.start: + if modification.start > rangestarts: + prev_adapter = modification + rangestarts = modification.start + if rangestarts != mindatetime: + starts = [starts[0], rangestarts, * starts[1:]] for start in starts: recurrence_ids = to_recurrence_ids(start) if (