Skip to content

Commit

Permalink
Add support of multiple RRULE
Browse files Browse the repository at this point in the history
  • Loading branch information
fabien-michel committed Apr 2, 2024
1 parent 76530d8 commit af05d9b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 15 deletions.
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Let's put our expertise together and build a tool that can solve this!
* events with start date and no end date (DONE)
* events with start as date and start as datetime (DONE)
* `RRULE <https://www.kanzaki.com/docs/ical/rrule.html>`_ (DONE)
* events with multiple RRULE (DONE)
* `RDATE <https://www.kanzaki.com/docs/ical/rdate.html>`_ (DONE)
* `DURATION <https://www.kanzaki.com/docs/ical/duration.html>`_ (DONE)
* `EXDATE <https://www.kanzaki.com/docs/ical/exdate.html>`_ (DONE)
Expand Down Expand Up @@ -291,6 +292,10 @@ To release new versions,
Changelog
---------

- Incoming

- Add support for multiple RRULE in events.

- v2.2.0

- Add ``after()`` method to iterate over upcoming events.
Expand Down
36 changes: 21 additions & 15 deletions recurring_ical_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,25 +214,31 @@ def __init__(self, component, keep_recurrence_attributes=False):

self.duration = self.end - self.start
self.rule = rule = rruleset(cache=True)
_rule = component.get("RRULE", None)
if _rule:
# We don't support multiple RRULE yet, but we can support cases
# where the same RRULE is erroneously repeated
if isinstance(_rule, list):
if len(_rule) > 0 and all(part == _rule[0] for part in _rule):
_rule = _rule[0]
else:
raise ValueError("Don't yet support multiple distinct RRULE properties")
self.rrule = self.create_rule_with_start(_rule.to_ical().decode())
rule.rrule(self.rrule)
else:
self.rrule = None
_component_rules = component.get("RRULE", None)
rrules = []
if _component_rules:
if not isinstance(_component_rules, list):
_component_rules = [_component_rules]
else:
_dedup_rules=[]
for _rule in _component_rules:
if _rule not in _dedup_rules:
_dedup_rules.append(_rule)
_component_rules = _dedup_rules

for _rule in _component_rules:
rrule = self.create_rule_with_start(_rule.to_ical().decode())
rrules.append(rrule)
rule.rrule(rrule)

for exdate in self.exdates:
rule.exdate(exdate)
for rdate in self.rdates:
rule.rdate(rdate)
if not self.rrule or not self.rrule.until or not compare_greater(self.start, self.rrule.until):

untils = [rrule.until for rrule in rrules if rrule and rrule.until]
self.max_until = max(untils) if untils else None
if not rrules or not self.max_until or not compare_greater(self.start, self.max_until):
rule.rdate(self.start)

def create_rule_with_start(self, rule_string):
Expand Down Expand Up @@ -339,7 +345,7 @@ def within_days(self, span_start, span_stop):
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.
if self.rrule is not None and self.rrule.until is not None and start > self.rrule.until and start not in self.rdates:
if self.max_until is not None and start > self.max_until and start not in self.rdates:
continue
if self._unify_exdate(start) in self.exdates_utc or start.date() in self.exdates_utc:
continue
Expand Down
16 changes: 16 additions & 0 deletions test/calendars/multiple_rrule.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CyrusIMAP.org/Cyrus
3.9.0-alpha0-85-gd6d859e0cf-fm-20230116.001-gd6d859e0//EN
BEGIN:VEVENT
CREATED:20230109T084023Z
LAST-MODIFIED:20230119T110732Z
DTSTAMP:20230119T110732Z
UID:56cdc4dc-11b7-407c-86c6-9faedfc28afb
SUMMARY:My repeating event
RRULE:FREQ=WEEKLY;BYDAY=TH;COUNT=20
RRULE:FREQ=MONTHLY;BYDAY=2MO;COUNT=2
DTSTART;TZID=Europe/London:20230112T100000
DTEND;TZID=Europe/London:20230112T120000
END:VEVENT
END:VCALENDAR
12 changes: 12 additions & 0 deletions test/test_multiple_rrule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

from datetime import date


def test_multiple_rrule(calendars):
events = calendars.multiple_rrule.at(2023)
assert len(events) == 20 + 2
event_dstarts = [event['DTSTART'].dt.date() for event in events]
assert date(2023,2,9) in event_dstarts
assert date(2023,2,13) in event_dstarts
assert date(2023,2,16) in event_dstarts
assert date(2023,3,13) in event_dstarts

0 comments on commit af05d9b

Please sign in to comment.