Skip to content

Commit

Permalink
Merge pull request #131 from HESPUL/support_multiple_rrule
Browse files Browse the repository at this point in the history
Add support of multiple RRULE
  • Loading branch information
niccokunzmann committed Apr 3, 2024
2 parents 76530d8 + 2d71496 commit a9691ed
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 19 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
43 changes: 24 additions & 19 deletions recurring_ical_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,27 +213,32 @@ def __init__(self, component, keep_recurrence_attributes=False):
self.make_all_dates_comparable()

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
self.rule = rruleset(cache=True)
_component_rules = component.get("RRULE", None)
self.until = None
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())
self.rule.rrule(rrule)
if rrule.until and (not self.until or compare_greater(rrule.until, self.until)):
self.until = rrule.until

for exdate in self.exdates:
rule.exdate(exdate)
self.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):
rule.rdate(self.start)
self.rule.rdate(rdate)

if not self.until or not compare_greater(self.start, self.until):
self.rule.rdate(self.start)

def create_rule_with_start(self, rule_string):
"""Helper to create an rrule from a rule_string starting at the start of the component.
Expand Down Expand Up @@ -339,7 +344,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.until is not None and start > self.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 a9691ed

Please sign in to comment.