diff options
author | Jochen Sprickerhof <jspricke@debian.org> | 2023-06-12 18:03:42 +0200 |
---|---|---|
committer | Jochen Sprickerhof <jspricke@debian.org> | 2023-06-12 18:03:42 +0200 |
commit | 5777b0ef7a80ea731b02b26477476acce19c497e (patch) | |
tree | c69bf99b06f2be2ff60d2907bb4955da122cd2e7 | |
parent | b2e11a4237dbcfc57f08c716d564faf5dd303992 (diff) |
New upstream version 2.0.2
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | recurring_ical_events.py | 28 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | test/calendars/issue-107-omitting-last-event.ics | 24 | ||||
-rw-r--r-- | test/test_issue_107_omitting_last_event.py | 19 |
5 files changed, 62 insertions, 15 deletions
@@ -276,6 +276,10 @@ how to go about it. Changelog --------- +- 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 <https://github.com/niccokunzmann/python-recurring-ical-events/issues/107>`_. + - v2.0.1 - Fixed crasher with duplicate RRULE. See `Pull Request 104 <https://github.com/niccokunzmann/python-recurring-ical-events/pull/104>`_ diff --git a/recurring_ical_events.py b/recurring_ical_events.py index d3bcf01..eb9cda3 100644 --- a/recurring_ical_events.py +++ b/recurring_ical_events.py @@ -237,19 +237,20 @@ class RepeatedComponent: def rrulestr(self, rule_string): """Return an rrulestr with a start. This might fail.""" - rule = rrulestr(rule_string, dtstart=self.start) + rule = rrulestr(rule_string, dtstart=self.start, cache=True) rule.string = 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 + # 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 return rule - _until = UNTIL_NOT_SET = "NOT_SET" - def get_rrule_until(self): - """Return the UNTIL datetime of the rrule or None is absent.""" - if self._until is not self.UNTIL_NOT_SET: - return self._until - self._until = None - if self.rrule is None: - return None - rule_list = self.rrule.string.split(";UNTIL=") + def _get_rrule_until(self, rrule): + """Return the UNTIL datetime of the rrule or None if absent.""" + rule_list = rrule.string.split(";UNTIL=") if len(rule_list) == 1: return None assert len(rule_list) == 2, "There should be only one UNTIL." @@ -257,8 +258,8 @@ class RepeatedComponent: if date_end_index == -1: date_end_index = len(rule_list[1]) until_string = rule_list[1][:date_end_index] - self._until = vDDDTypes.from_ical(until_string) - return self._until + until = vDDDTypes.from_ical(until_string) + return until def make_all_dates_comparable(self): """Make sure we can use all dates with eachother. @@ -306,8 +307,7 @@ class RepeatedComponent: start = start.tzinfo.localize(start.replace(tzinfo=None)) # We could now well be out of bounce of the end of the UNTIL # value. This is tested by test/test_issue_20_exdate_ignored.py. - until = self.get_rrule_until() - if until is not None and start > until and start not in self.rdates: + if self.rrule is not None and self.rrule.until is not None and start > self.rrule.until and start not in self.rdates: continue if self._unify_exdate(start) in self.exdates_utc: continue @@ -11,7 +11,7 @@ PACKAGE_NAME = "recurring_ical_events" HERE = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, HERE) # for package import -__version__ = "2.0.1" +__version__ = "2.0.2" __author__ = 'Nicco Kunzmann' diff --git a/test/calendars/issue-107-omitting-last-event.ics b/test/calendars/issue-107-omitting-last-event.ics new file mode 100644 index 0000000..a9859a0 --- /dev/null +++ b/test/calendars/issue-107-omitting-last-event.ics @@ -0,0 +1,24 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Pacific Standard Time: +BEGIN:STANDARD +DTSTART:16010101T020000 +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20230608T170000Z;INTERVAL=1;BYDAY=TH;WKST=SU +DTSTART;TZID=Pacific Standard Time:20230105T100000 +DTEND;TZID=Pacific Standard Time:20230105T110000 +END:VEVENT +END:VCALENDAR
\ No newline at end of file diff --git a/test/test_issue_107_omitting_last_event.py b/test/test_issue_107_omitting_last_event.py new file mode 100644 index 0000000..b3eb8a4 --- /dev/null +++ b/test/test_issue_107_omitting_last_event.py @@ -0,0 +1,19 @@ +"""bug: recurring event series that start in daylight savings time and end in standard time omit last event + +Using a calendar application, I created a weekly event series in Pacific Standard Time that begins on January 5th and ends on June 8th. I filtered out events using between from today's date (~January 2023) and 1 year in the future (~January 2024). However, it incorrectly omitted the last event in the series on June 8th. + +Upon further investigation, it seems to just be an issue for a recurring event series that begin in standard time but end in daylight savings time. + +see https://github.com/niccokunzmann/python-recurring-ical-events/issues/107 +see also test_issue_20_exdate_ignored.py - same problem with pytz +""" +import datetime + + +def test_last_event_is_present(calendars): + TODAY = datetime.date(2023, 1, 30) + FUTURE = TODAY + datetime.timedelta(days=365) + events = calendars.issue_107_omitting_last_event.between(TODAY, FUTURE) + dates = [event["DTSTART"].dt.date() for event in events] + assert datetime.date(2023, 6, 1) in dates, "event before last is present" + assert datetime.date(2023, 6, 8) in dates, "last event is present" |