From a2f73c96371c27cb1106fdda5a99b637add7d482 Mon Sep 17 00:00:00 2001 From: sunny Date: Mon, 13 Sep 2021 17:00:52 -0400 Subject: [PATCH 1/3] fix recurrence id events --- icalevents/icalparser.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 63614db..41ae2f7 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -139,6 +139,7 @@ def copy_to(self, new_start=None, uid=None): ne.created = self.created ne.last_modified = self.last_modified ne.categories = self.categories + ne.sequence = self.sequence return ne @@ -407,12 +408,24 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): exdate = "%04d%02d%02d" % (e.start.year, e.start.month, e.start.day) if exdate not in exceptions: found.append(e) + # Filter out all events that are moved as indicated by the recurrence-id prop return [ event for event in found - if e.sequence is None or not (event.uid, event.start, e.sequence) in recurrence_ids + if event.sequence is None or not is_recurrence_event(recurrence_ids, event.uid, event.start, event.sequence) ] +def is_recurrence_event(recurrence_ids, uid, start, sequence): + for recurrence in recurrence_ids: + if recurrence[0] == uid and recurrence[1] == start: + # If the recurrence event's sequence is less than the original rrule event + # we ignore it + if recurrence[2] >= sequence: + return True + else: + return False + return False + def parse_rrule(component, tz=UTC): """ From e47fcd68f45d236b10805f37b617b7e8f6ac5753 Mon Sep 17 00:00:00 2001 From: Jack Cross Date: Thu, 16 Sep 2021 20:11:16 -0400 Subject: [PATCH 2/3] Replace faulty exception logic and handle more recurrence id cases --- icalevents/icalparser.py | 46 +++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 41ae2f7..023fe74 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -324,27 +324,13 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): found = [] recurrence_ids = [] - # Skip dates that are stored as exceptions. - exceptions = {} for component in calendar.walk(): if component.name == "VEVENT": e = create_event(component, cal_tz) - + if 'RECURRENCE-ID' in component: recurrence_ids.append((e.uid, component['RECURRENCE-ID'].dt, e.sequence)) - if 'EXDATE' in component: - # Deal with the fact that sometimes it's a list and - # sometimes it's a singleton - exlist = [] - if isinstance(component['EXDATE'], list): - exlist = component['EXDATE'] - else: - exlist.append(component['EXDATE']) - for ex in exlist: - exdate = ex.to_ical().decode("UTF-8") - exceptions[exdate[0:8]] = exdate - # Attempt to work out what timezone is used for the start # and end times. If the timezone is defined in the calendar, # use it; otherwise, attempt to load the rules from pytz. @@ -401,26 +387,32 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # timezone from the start time, we'll have lost that. ecopy.end = dtstart + duration - exdate = "%04d%02d%02d" % (ecopy.start.year, ecopy.start.month, ecopy.start.day) - if exdate not in exceptions: - found.append(ecopy) + found.append(ecopy) elif e.end >= start and e.start <= end: - exdate = "%04d%02d%02d" % (e.start.year, e.start.month, e.start.day) - if exdate not in exceptions: - found.append(e) + found.append(e) # Filter out all events that are moved as indicated by the recurrence-id prop return [ - event for event in found - if event.sequence is None or not is_recurrence_event(recurrence_ids, event.uid, event.start, event.sequence) + event for event in found + if not is_replaced_by_recurrence_id_instance(event, recurrence_ids) ] -def is_recurrence_event(recurrence_ids, uid, start, sequence): - for recurrence in recurrence_ids: - if recurrence[0] == uid and recurrence[1] == start: + +def is_replaced_by_recurrence_id_instance(event, recurrence_ids): + # calendars that use RECURRENCE-ID also use sequences + if event.sequence is None: + return False + + # only recurring events can be replaced by instances + if not event.recurring: + return False + + # check if the recurring event has a RECURRENCE-ID instance that replaces it + for uid, start, sequence in recurrence_ids: + if uid == event.uid and start == event.start: # If the recurrence event's sequence is less than the original rrule event # we ignore it - if recurrence[2] >= sequence: + if sequence >= event.sequence: return True else: return False From 73d6e7ace88fc5c8503a15da9659828137a10047 Mon Sep 17 00:00:00 2001 From: Jack Cross Date: Sat, 18 Sep 2021 20:16:11 -0400 Subject: [PATCH 3/3] Fix multi timezone dst issue --- icalevents/icalparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 023fe74..2d3a6cc 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -311,9 +311,9 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # value from the name as a fallback. timezones[name] = timezone(name) - # If there's exactly one timezone in the file, + # If there's at least one timezone in the file, # assume it applies globally, otherwise UTC - if len(timezones) == 1: + if len(timezones): cal_tz = get_timezone(list(timezones)[0]) else: cal_tz = UTC