summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJochen Sprickerhof <jspricke@debian.org>2023-06-12 18:03:42 +0200
committerJochen Sprickerhof <jspricke@debian.org>2023-06-12 18:03:42 +0200
commit5777b0ef7a80ea731b02b26477476acce19c497e (patch)
treec69bf99b06f2be2ff60d2907bb4955da122cd2e7
parentb2e11a4237dbcfc57f08c716d564faf5dd303992 (diff)
New upstream version 2.0.2
-rw-r--r--README.rst4
-rw-r--r--recurring_ical_events.py28
-rw-r--r--setup.py2
-rw-r--r--test/calendars/issue-107-omitting-last-event.ics24
-rw-r--r--test/test_issue_107_omitting_last_event.py19
5 files changed, 62 insertions, 15 deletions
diff --git a/README.rst b/README.rst
index ea31526..27509bf 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/setup.py b/setup.py
index 2ffb49e..e673f10 100644
--- a/setup.py
+++ b/setup.py
@@ -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"