From 7720a49dac3e63cf6bf50421275fcf06784801dc Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 28 Mar 2021 11:38:42 +0200 Subject: [PATCH 01/69] feat() uses ical tz translation for windows --- icalevents/icalparser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 46bcd79..1a084f5 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -14,6 +14,8 @@ from icalendar.prop import vDDDLists, vText from pytz import timezone +from icalendar.windows_to_olson import WINDOWS_TO_OLSON + def now(): """ @@ -267,6 +269,9 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Keep track of the timezones defined in the calendar timezones = {} + if 'X-WR-TIMEZONE' in calendar: + timezones[str(calendar['X-WR-TIMEZONE'])] = str(calendar['X-WR-TIMEZONE']) + for c in calendar.walk('VTIMEZONE'): name = str(c['TZID']) try: @@ -282,6 +287,8 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # assume it applies globally, otherwise UTC if len(timezones) == 1: cal_tz = gettz(list(timezones)[0]) + if not cal_tz and timezone in WINDOWS_TO_OLSON: + cal_tz = gettz(WINDOWS_TO_OLSON[str(c['TZID'])]) else: cal_tz = UTC From d06beb8c66cf7ac477b20a001f25259ddfcd3f8a Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 28 Mar 2021 12:47:01 +0200 Subject: [PATCH 02/69] feat() adds tests for default tz on all day events --- icalevents/icalparser.py | 9 ++++---- test/test_data/google_tz.ics | 17 +++++++++++++++ test/test_data/ms_tz.ics | 42 ++++++++++++++++++++++++++++++++++++ test/test_icalevents.py | 24 ++++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 test/test_data/google_tz.ics create mode 100644 test/test_data/ms_tz.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 1a084f5..14262cb 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -270,7 +270,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Keep track of the timezones defined in the calendar timezones = {} if 'X-WR-TIMEZONE' in calendar: - timezones[str(calendar['X-WR-TIMEZONE'])] = str(calendar['X-WR-TIMEZONE']) + timezones[str(calendar['X-WR-TIMEZONE'])] = gettz(str(calendar['X-WR-TIMEZONE'])) for c in calendar.walk('VTIMEZONE'): name = str(c['TZID']) @@ -287,7 +287,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # assume it applies globally, otherwise UTC if len(timezones) == 1: cal_tz = gettz(list(timezones)[0]) - if not cal_tz and timezone in WINDOWS_TO_OLSON: + if not cal_tz and str(c['TZID']) in WINDOWS_TO_OLSON: cal_tz = gettz(WINDOWS_TO_OLSON[str(c['TZID'])]) else: cal_tz = UTC @@ -320,8 +320,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # use it; otherwise, attempt to load the rules from pytz. start_tz = None end_tz = None - - if e.all_day: + if e.all_day and e.recurring: # Start and end times for all day events must not have # a timezone because the specification forbids the # RRULE UNTIL from having a timezone. On the other @@ -355,7 +354,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end_tz = timezone(str(e.end.tzinfo)) except: pass - + # If we've been passed or constructed start/end values # that are timezone naive, but the actual appointment # start and end times are in a timezone, convert start diff --git a/test/test_data/google_tz.ics b/test/test_data/google_tz.ics new file mode 100644 index 0000000..9cbb9e1 --- /dev/null +++ b/test/test_data/google_tz.ics @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:test +X-WR-TIMEZONE:Europe/Zurich +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210324 +DTEND;VALUE=DATE:20210327 +DTSTAMP:20210328T094322Z +UID:1gn1mvk5325euprb9fcf7ndpdn@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +SUMMARY:Busy +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_data/ms_tz.ics b/test/test_data/ms_tz.ics new file mode 100644 index 0000000..84f6fc9 --- /dev/null +++ b/test/test_data/ms_tz.ics @@ -0,0 +1,42 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:test +BEGIN:VTIMEZONE +TZID:W. Europe Standard Time +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000007E56CFC7E1DD701000000000000000 + 01000000068B41E0ADF04374687B1104CB913D622 +SUMMARY:Busy +DTSTART;VALUE=DATE:20210324 +DTEND;VALUE=DATE:20210327 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210328T104140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 0561271..38626c4 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -3,7 +3,7 @@ from datetime import date, timedelta, datetime from time import sleep from dateutil.relativedelta import relativedelta -from dateutil.tz import UTC +from dateutil.tz import UTC, gettz from re import search @@ -260,3 +260,25 @@ def test_event_categories(self): events = icalevents.events(url=None, file=ical, start=start, end=end) self.assertEqual(events[0].categories, ["In19-S04-IT2403"], "event 1 is not equal") self.assertEqual(events[1].categories, ["In19-S04-IT2406", "In19-S04-IT2405"], "event 2 is not equal") + + def test_google_timezone(self): + ical = "test/test_data/google_tz.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + evs = icalevents.events(file=ical, start=start, end=end) + + e1 = evs[0] + self.assertEqual(e1.start.hour, 0, "check start of the day") + self.assertEqual(e1.start.tzinfo, gettz('Europe/Zurich'), "check tz as specified in calendar") + + def test_ms_timezone(self): + ical = "test/test_data/ms_tz.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + evs = icalevents.events(file=ical, start=start, end=end) + + e1 = evs[0] + self.assertEqual(e1.start.hour, 0, "check start of the day") + self.assertEqual(e1.start.tzinfo, gettz('Europe/Berlin'), "check tz as specified in calendar") \ No newline at end of file From 98b6f3cccdfb34b41d7cc13b05c6e84ef7c7468d Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 28 Mar 2021 20:54:33 +0200 Subject: [PATCH 03/69] feat() accepts reccuring all day events --- icalevents/icalparser.py | 9 ++++- test/test_data/rrule_until_all_day_google.ics | 38 +++++++++++++++++++ test/test_data/rrule_until_all_day_ms.ics | 36 ++++++++++++++++++ test/test_icalevents.py | 26 +++++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 test/test_data/rrule_until_all_day_google.ics create mode 100644 test/test_data/rrule_until_all_day_ms.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 14262cb..6ef7290 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -424,6 +424,11 @@ def parse_rrule(component, tz=UTC): if type(rdtstart) is datetime: rdtstart = normalize(rdtstart, tz=tz) + if type(rdtstart) is date: + for index, rru in enumerate(rrules): + if 'UNTIL' in rru: + rrules[index]['UNTIL'] = [dt.date() if type(dt) is datetime else dt for dt in rru['UNTIL']] + # Parse the rrules, might return a rruleset instance, instead of rrule rule = rrulestr('\n'.join(x.to_ical().decode() for x in rrules), dtstart=rdtstart) @@ -437,8 +442,8 @@ def parse_rrule(component, tz=UTC): # Add exdates to the rruleset for exd in extract_exdates(component): - rule.exdate(exd) - + rule.exdate(exd.replace(tzinfo=None) if type(rdtstart) is date else exd) + # TODO: What about rdates and exrules? # You really want an rrule for a component without rrule? Here you are. diff --git a/test/test_data/rrule_until_all_day_google.ics b/test/test_data/rrule_until_all_day_google.ics new file mode 100644 index 0000000..7e7b91e --- /dev/null +++ b/test/test_data/rrule_until_all_day_google.ics @@ -0,0 +1,38 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:test +X-WR-TIMEZONE:Europe/Zurich +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210407 +DTEND;VALUE=DATE:20210410 +DTSTAMP:20210328T184235Z +UID:1gn1mvk5325euprb9fcf7ndpdn@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID;VALUE=DATE:20210407 +SUMMARY:Busy +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210331 +DTEND;VALUE=DATE:20210403 +DTSTAMP:20210328T184235Z +UID:1gn1mvk5325euprb9fcf7ndpdn@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID;VALUE=DATE:20210331 +SUMMARY:Busy +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210324 +DTEND;VALUE=DATE:20210327 +DTSTAMP:20210328T184235Z +UID:1gn1mvk5325euprb9fcf7ndpdn@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID;VALUE=DATE:20210324 +SUMMARY:Busy +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_data/rrule_until_all_day_ms.ics b/test/test_data/rrule_until_all_day_ms.ics new file mode 100644 index 0000000..e59d120 --- /dev/null +++ b/test/test_data/rrule_until_all_day_ms.ics @@ -0,0 +1,36 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Kalender +BEGIN:VTIMEZONE +TZID:W. Europe Standard Time +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210502T220000Z;INTERVAL=1;BYDAY=FR;WKST=MO +EXDATE;TZID=W. Europe Standard Time:20210430T000000 +UID:040000008200E00074C5B7101A82E00800000000D0A3F2C7C4E8D601000000000000000 + 0100000006C7ACDDE7E4A5E47ACD97CBB31A1438C +SUMMARY:Away +DTSTART;VALUE=DATE:20210319 +DTEND;VALUE=DATE:20210321 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210326T082505Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 38626c4..202c8b6 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -107,6 +107,32 @@ def test_events_all_day_recurring(self): self.assertEqual(ev_2.description, "All-day event recurring on tuesday each week") self.assertTrue(ev_2.all_day, "Recurring All-day Event's second instance is an all-day event") + def test_events_rrule_until_all_day_ms(self): + ical = "test/test_data/rrule_until_all_day_ms.ics" + start = date(2021, 1, 1) + end = date(2022, 1, 1) + + evs = icalevents.events(file=ical, start=start, end=end) + ev_0 = evs[0] + + self.assertEqual(len(evs), 6, "Seven events and one is excluded") + self.assertEqual(ev_0.start, datetime(2021, 3, 19, 00, 0, 0)) + self.assertEqual(ev_0.recurring, True, "Recurring all day event") + self.assertEqual(ev_0.summary, "Away") + + def test_events_rrule_until_all_day_google(self): + ical = "test/test_data/rrule_until_all_day_google.ics" + start = date(2021, 1, 1) + end = date(2022, 1, 1) + + evs = icalevents.events(file=ical, start=start, end=end) + ev_2 = evs[2] + + self.assertEqual(len(evs), 3) + self.assertEqual(ev_2.start, datetime(2021, 3, 24, 00, 0, 0, tzinfo=gettz('Europe/Zurich'))) + self.assertEqual(ev_2.all_day, True, "All day event") + self.assertEqual(ev_2.summary, "Busy") + def test_events_rrule_until(self): ical = "test/test_data/rrule_until.ics" start = date(2019, 4, 2) From 0d92c07ef7598aa879c8025bd2bea7389a2d7445 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 28 Mar 2021 21:10:24 +0200 Subject: [PATCH 04/69] style() cleanup --- icalevents/icalparser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 6ef7290..b3495c1 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -11,11 +11,10 @@ from dateutil.tz import UTC, gettz from icalendar import Calendar +from icalendar.windows_to_olson import WINDOWS_TO_OLSON from icalendar.prop import vDDDLists, vText from pytz import timezone -from icalendar.windows_to_olson import WINDOWS_TO_OLSON - def now(): """ @@ -354,7 +353,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end_tz = timezone(str(e.end.tzinfo)) except: pass - + # If we've been passed or constructed start/end values # that are timezone naive, but the actual appointment # start and end times are in a timezone, convert start @@ -443,7 +442,7 @@ def parse_rrule(component, tz=UTC): # Add exdates to the rruleset for exd in extract_exdates(component): rule.exdate(exd.replace(tzinfo=None) if type(rdtstart) is date else exd) - + # TODO: What about rdates and exrules? # You really want an rrule for a component without rrule? Here you are. From 6aa12a280bab9417deff3aa9fc9a127b41c3a156 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 29 Mar 2021 23:30:55 +0200 Subject: [PATCH 05/69] feat() respects reccurence-id --- icalevents/icalparser.py | 14 +++++- test/test_data/recurrenceid_google.ics | 48 +++++++++++++++++++ test/test_data/recurrenceid_ms.ics | 65 ++++++++++++++++++++++++++ test/test_icalevents.py | 22 ++++++++- 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 test/test_data/recurrenceid_google.ics create mode 100644 test/test_data/recurrenceid_ms.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index b3495c1..2e629ee 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -209,7 +209,8 @@ def create_event(component, tz=UTC): elif event.created: event.last_modified = event.created - if component.get('sequence'): + # sequence can be 0 - test for None instead + if not component.get('sequence') is None: event.sequence = component.get('sequence') if component.get("categories"): @@ -295,12 +296,16 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end = normalize(end, cal_tz) 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 @@ -320,6 +325,10 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): start_tz = None end_tz = None if e.all_day and e.recurring: + # Keep the timezone around if to apply later + start_tz = timezone(e.start.tzname()) + end_tz = timezone(e.end.tzname()) + # Start and end times for all day events must not have # a timezone because the specification forbids the # RRULE UNTIL from having a timezone. On the other @@ -399,7 +408,8 @@ 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) - return found + # 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] def parse_rrule(component, tz=UTC): diff --git a/test/test_data/recurrenceid_google.ics b/test/test_data/recurrenceid_google.ics new file mode 100644 index 0000000..043825c --- /dev/null +++ b/test/test_data/recurrenceid_google.ics @@ -0,0 +1,48 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:test +X-WR-TIMEZONE:Europe/Zurich +BEGIN:VEVENT +DTSTART:20210327T110000Z +DTEND:20210327T120000Z +DTSTAMP:20210320T113243Z +UID:5kml9evtgr3lffqh2lc2lu86kp@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID:20210327T110000Z +SUMMARY:Busy +END:VEVENT +BEGIN:VEVENT +DTSTART:20210326T110000Z +DTEND:20210326T120000Z +DTSTAMP:20210320T113243Z +UID:5kml9evtgr3lffqh2lc2lu86kp@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID:20210326T110000Z +SUMMARY:Busy +END:VEVENT +BEGIN:VEVENT +DTSTART:20210325T110000Z +DTEND:20210325T120000Z +DTSTAMP:20210320T113243Z +UID:5kml9evtgr3lffqh2lc2lu86kp@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID:20210325T110000Z +SUMMARY:Busy +END:VEVENT +BEGIN:VEVENT +DTSTART:20210324T110000Z +DTEND:20210324T120000Z +DTSTAMP:20210320T113243Z +UID:5kml9evtgr3lffqh2lc2lu86kp@google.com +ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go + ogle.com +RECURRENCE-ID:20210324T110000Z +SUMMARY:Busy +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_data/recurrenceid_ms.ics b/test/test_data/recurrenceid_ms.ics new file mode 100644 index 0000000..6ae0577 --- /dev/null +++ b/test/test_data/recurrenceid_ms.ics @@ -0,0 +1,65 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Calendar +BEGIN:VTIMEZONE +TZID:W. Europe Standard Time +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210701T220000Z;INTERVAL=1;BYDAY=WE,FR;WKST=SU +UID:040000008200E00074C5B7101A82E0080000000062FD14411601D701000000000000000 + 01000000052EAF66D5C3BC6458198F47BAE50996F +SUMMARY:Away +DTSTART;VALUE=DATE:20210210 +DTEND;VALUE=DATE:20210211 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210329T195704Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000062FD14411601D701000000000000000 + 01000000052EAF66D5C3BC6458198F47BAE50996F +RECURRENCE-ID;TZID=W. Europe Standard Time:20210409T000000 +SUMMARY:Away +DTSTART;VALUE=DATE:20210410 +DTEND;VALUE=DATE:20210411 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210329T195704Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 202c8b6..d059dda 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -116,7 +116,7 @@ def test_events_rrule_until_all_day_ms(self): ev_0 = evs[0] self.assertEqual(len(evs), 6, "Seven events and one is excluded") - self.assertEqual(ev_0.start, datetime(2021, 3, 19, 00, 0, 0)) + self.assertEqual(ev_0.start, datetime(2021, 3, 19, 00, 0, 0, tzinfo=gettz('Europe/Berlin'))) self.assertEqual(ev_0.recurring, True, "Recurring all day event") self.assertEqual(ev_0.summary, "Away") @@ -307,4 +307,22 @@ def test_ms_timezone(self): e1 = evs[0] self.assertEqual(e1.start.hour, 0, "check start of the day") - self.assertEqual(e1.start.tzinfo, gettz('Europe/Berlin'), "check tz as specified in calendar") \ No newline at end of file + self.assertEqual(e1.start.tzinfo, gettz('Europe/Berlin'), "check tz as specified in calendar") + + def test_recurence_id_ms(self): + ical = "test/test_data/recurrenceid_ms.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + evs = icalevents.events(file=ical, start=start, end=end) + + self.assertEqual(len(evs), 41, "41 events in total - one was moved") + + def test_recurence_id_google(self): + ical = "test/test_data/recurrenceid_google.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + evs = icalevents.events(file=ical, start=start, end=end) + + self.assertEqual(len(evs), 4, "4 events in total") \ No newline at end of file From c3c3b02b7dd849ced6dc539e54f56a4939db6814 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sat, 8 May 2021 16:50:16 +0200 Subject: [PATCH 06/69] fix: keyerror cest --- icalevents/icalparser.py | 13 +- test/test_data/cest.ics | 2262 ++++++++++++++++++++++++++++++++++++++ test/test_icalevents.py | 11 +- 3 files changed, 2278 insertions(+), 8 deletions(-) create mode 100644 test/test_data/cest.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 2e629ee..1bd65f2 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -326,8 +326,8 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end_tz = None if e.all_day and e.recurring: # Keep the timezone around if to apply later - start_tz = timezone(e.start.tzname()) - end_tz = timezone(e.end.tzname()) + start_tz = e.start.tzinfo + end_tz = e.end.tzinfo # Start and end times for all day events must not have # a timezone because the specification forbids the @@ -350,7 +350,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): start_tz = timezones[str(e.start.tzinfo)] else: try: - start_tz = timezone(str(e.start.tzinfo)) + start_tz = e.start.tzinfo except: pass @@ -359,7 +359,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end_tz = timezones[str(e.end.tzinfo)] else: try: - end_tz = timezone(str(e.end.tzinfo)) + end_tz = e.end.tzinfo except: pass @@ -378,8 +378,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): if e.recurring: # Unfold recurring events according to their rrule rule = parse_rrule(component, cal_tz) - dur = e.end - e.start - after = start - dur + after = start - duration for dt in rule.between(after, end, inc=True): if start_tz is None: @@ -390,7 +389,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # date of *this* occurrence. This handles the case where the # recurrence has crossed over the daylight savings time boundary. naive = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) - dtstart = start_tz.localize(naive) + dtstart = normalize(naive, tz=start_tz) ecopy = e.copy_to(dtstart, e.uid) diff --git a/test/test_data/cest.ics b/test/test_data/cest.ics new file mode 100644 index 0000000..f117d8f --- /dev/null +++ b/test/test_data/cest.ics @@ -0,0 +1,2262 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Calendar +BEGIN:VTIMEZONE +TZID:W. Europe Standard Time +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20200521T220000Z;INTERVAL=1;BYDAY=MO,FR;WKST=MO +UID:040000008200E00074C5B7101A82E008000000001075617F3BE6D501000000000000000 + 0100000007B29D30C240193429FF8C08553783419 +SUMMARY:Away +DTSTART;VALUE=DATE:20200508 +DTEND;VALUE=DATE:20200509 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000B05AD42D8A27D601000000000000000 + 01000000030BB2D39F818C747A760BA7929ED3981 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20200512T090000 +DTEND;TZID=W. Europe Standard Time:20200512T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20200520T140000Z;INTERVAL=1;BYDAY=WE;WKST=MO +UID:040000008200E00074C5B7101A82E00800000000D0B7C0A43BE6D501000000000000000 + 0100000000C1F72E9A45C5D4ABC1F1AC4E9461313 +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20200513T160000 +DTEND;TZID=W. Europe Standard Time:20200513T220000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:DE298210-0417-46D3-B2B2-4038AA905649 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200526T090000 +DTEND;TZID=W. Europe Standard Time:20200526T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:B5E946AC-2F29-4F13-8922-5686D24E8187 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200526T114500 +DTEND;TZID=W. Europe Standard Time:20200526T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:EAE44C91-F256-4212-A477-6B5215AA8BC7 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200527T090000 +DTEND;TZID=W. Europe Standard Time:20200527T094500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000C01BF415203BD601000000000000000 + 0100000006B678962A55F6842977AD405D4ECAA8B +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200608T154500 +DTEND;TZID=W. Europe Standard Time:20200608T161500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:BF60522B-2482-4734-A552-14F8C39011CA +SUMMARY:Free +DTSTART;TZID=W. Europe Standard Time:20200701T090000 +DTEND;TZID=W. Europe Standard Time:20200701T110000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:AE5B3952-8663-432D-84C2-87D34E9C753F +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200706T090000 +DTEND;TZID=W. Europe Standard Time:20200706T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:8839D7E0-115A-4E61-B5B3-AE6307DAD3D4 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20200706T093000 +DTEND;TZID=W. Europe Standard Time:20200706T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:733C582E-47E9-405A-A365-D476589C803C +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200706T130000 +DTEND;TZID=W. Europe Standard Time:20200706T133000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:A3A5B2A9-6D72-4541-8F24-E77EA1BEFC3F +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200708T103000 +DTEND;TZID=W. Europe Standard Time:20200708T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:2 +X-MICROSOFT-CDO-APPT-SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:55B4C453-E46F-4C59-80F0-86795757EA54 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200713T153000 +DTEND;TZID=W. Europe Standard Time:20200713T160000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:2 +X-MICROSOFT-CDO-APPT-SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000908294864A50D601000000000000000 + 010000000B11AD25C8FAC044AA4B213C41795F969 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200716T120000 +DTEND;TZID=W. Europe Standard Time:20200716T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:4 +X-MICROSOFT-CDO-APPT-SEQUENCE:4 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:997EAA7F-3875-48A7-9F2C-625A9DA4C74B +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200716T143000 +DTEND;TZID=W. Europe Standard Time:20200716T150000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000DC9F44E10C5AD601000000000000000 + 0100000000F865D0942EDAE45AD168FD5AE0085DD +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200717T130000 +DTEND;TZID=W. Europe Standard Time:20200717T133000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000400D2869775ED601000000000000000 + 010000000712CF41D5BDC7B47AC67478991311A12 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200720T103000 +DTEND;TZID=W. Europe Standard Time:20200720T104500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000004C7923C4D522D601000000000000000 + 010000000A106A7E501F8324795D06DEA44182073 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200727T070000 +DTEND;TZID=W. Europe Standard Time:20200727T073000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:D03C22DA-13B7-49AE-9087-7CC1183EE7AA +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200727T134500 +DTEND;TZID=W. Europe Standard Time:20200727T154500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000005ABFDC9E1F64D601000000000000000 + 010000000A86E35E32541A043AF90A3B032210D57 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200730T133000 +DTEND;TZID=W. Europe Standard Time:20200730T140000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000503CA7354B50D601000000000000000 + 0100000000FEB7774C50048408D5F919D19319CAF +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200813T120000 +DTEND;TZID=W. Europe Standard Time:20200813T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:3 +X-MICROSOFT-CDO-APPT-SEQUENCE:3 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:171F5E53-F03C-4FD6-A92A-CCC3AEC9099B +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200826T103000 +DTEND;TZID=W. Europe Standard Time:20200826T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:34CC9FCD-AFBB-4DF1-B1B7-9E2FC79139B1 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200904T160000 +DTEND;TZID=W. Europe Standard Time:20200904T164500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:4E3984EFA34448D68E97183D8EAF5FB80 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200907T130000 +DTEND;TZID=W. Europe Standard Time:20200907T140000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:2 +X-MICROSOFT-CDO-APPT-SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20201216T080000Z;INTERVAL=1;BYDAY=WE;WKST=SU +UID:1F6F1A4B-A70F-45DA-8F31-A47AAE02F7AC +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200909T090000 +DTEND;TZID=W. Europe Standard Time:20200909T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:5 +X-MICROSOFT-CDO-APPT-SEQUENCE:5 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:DF3704CE-4336-42E3-9BBE-F4AB1AE33E36 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200911T160000 +DTEND;TZID=W. Europe Standard Time:20200911T210000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20201221T230000Z;INTERVAL=1;BYDAY=TU;WKST=MO +UID:040000008200E00074C5B7101A82E0080000000016888E6CBF86D601000000000000000 + 010000000448FF2FAF3F0424A89B6DC5D1FD02484 +SUMMARY:Away +DTSTART;VALUE=DATE:20200915 +DTEND;VALUE=DATE:20200916 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20201224T110000Z;INTERVAL=1;BYDAY=TH;WKST=MO +UID:040000008200E00074C5B7101A82E00800000000A429EDD6C086D601000000000000000 + 0100000009CEB2D590EF69D4F9F4A5CBEEB46C471 +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20200917T120000 +DTEND;TZID=W. Europe Standard Time:20200917T173000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:98E77A12-F595-4DFA-AAA4-70655A64AD22 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200918T110000 +DTEND;TZID=W. Europe Standard Time:20200918T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20201225T110000Z;INTERVAL=1;BYDAY=FR;WKST=MO +UID:040000008200E00074C5B7101A82E008000000003E664513C186D601000000000000000 + 01000000026A83D65744F3C4C80410BE9CFA1B03C +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20200918T120000 +DTEND;TZID=W. Europe Standard Time:20200918T170000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:91B8081F-4764-4108-97A9-E4C0AF6E33C3 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200924T093000 +DTEND;TZID=W. Europe Standard Time:20200924T113000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000103FADEF4C71D601000000000000000 + 010000000F08C89F594F6A645A59D42928F69DF4B +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200924T120000 +DTEND;TZID=W. Europe Standard Time:20200924T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:F9B8DF47-0F86-4845-BDED-646A38D03B0A +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20200930T101500 +DTEND;TZID=W. Europe Standard Time:20200930T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000349C10C11797D601000000000000000 + 010000000927A2F068F5FCA40829F0F35BA04ACFC +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201001T090000 +DTEND;TZID=W. Europe Standard Time:20201001T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000BB812ABD0A97D601000000000000000 + 010000000F4E98D4229BFFA49871BE9A4252B77BA +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201001T093000 +DTEND;TZID=W. Europe Standard Time:20201001T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000F152D604BB91D601000000000000000 + 010000000B062F09E1083D94E801010FDFBB3C179 +SUMMARY:Free +DTSTART;TZID=W. Europe Standard Time:20201002T080000 +DTEND;TZID=W. Europe Standard Time:20201002T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000000F12AF14197D601000000000000000 + 010000000B2F69E4E61595740A288CD8FF9191DC1 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20201002T090000 +DTEND;TZID=W. Europe Standard Time:20201002T094500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000002DCD218DF6A1D601000000000000000 + 0100000008C2B77CC1D0C8E4F9F75A410E5FB5170 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201016T090000 +DTEND;TZID=W. Europe Standard Time:20201016T094500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000E30226C965A1D601000000000000000 + 0100000000248855A551E4F409F1C134AB8B1E8F5 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20201016T130000 +DTEND;TZID=W. Europe Standard Time:20201016T133000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000B773F8E965A1D601000000000000000 + 0100000005AF82035561A6D438E87A4558E721B4D +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201016T160000 +DTEND;TZID=W. Europe Standard Time:20201016T170000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000006043B50DC4A7D601000000000000000 + 010000000504B9DE85AC0434BB1E1152DEC546CBC +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201022T090000 +DTEND;TZID=W. Europe Standard Time:20201022T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000050FC3101C4A7D601000000000000000 + 010000000886DDB26E9F8EF448AE0C6FBF58F9A50 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201022T100000 +DTEND;TZID=W. Europe Standard Time:20201022T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000803A2CD475ABD601000000000000000 + 010000000E8B2F7397211794A9A0039A9A547ECB6 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201026T150000 +DTEND;TZID=W. Europe Standard Time:20201026T160000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000A0AC4F364D71D601000000000000000 + 010000000598606CD1DBC3F458327B7940CCC0E4F +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201029T120000 +DTEND;TZID=W. Europe Standard Time:20201029T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000007D3D53B664ACD601000000000000000 + 010000000F8C3CFB29FA20B4FAC6AB54B9F985EFF +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201030T090000 +DTEND;TZID=W. Europe Standard Time:20201030T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000F0E7713464B3D601000000000000000 + 010000000F20CCD9063386848B1E6C38D56348A3A +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201105T113000 +DTEND;TZID=W. Europe Standard Time:20201105T114500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000B003167078B6D601000000000000000 + 0100000009566D0821DFAB444A78B69525A1F61A2 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201109T150000 +DTEND;TZID=W. Europe Standard Time:20201109T153000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000772606315EB6D601000000000000000 + 0100000001CCF2EBE9FEFE0439A7C3D07F4FE4883 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201113T090000 +DTEND;TZID=W. Europe Standard Time:20201113T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000008C9C524993B9D601000000000000000 + 0100000009EAAC0689CB0C1498DDA7C183750D6A2 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201113T110000 +DTEND;TZID=W. Europe Standard Time:20201113T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000198EAB2DE8BBD601000000000000000 + 0100000004B3E4AC2F61161469205D3DD9BF15047 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201118T090000 +DTEND;TZID=W. Europe Standard Time:20201118T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000007D0934885EB6D601000000000000000 + 0100000005D8D3D05DA30FF4AA18FC1A9BC69270C +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201119T110000 +DTEND;TZID=W. Europe Standard Time:20201119T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000021FE26DABEBDD601000000000000000 + 010000000A61457CDF929E84B9E540355583BECC6 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201120T090000 +DTEND;TZID=W. Europe Standard Time:20201120T093000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000591198E55EB6D601000000000000000 + 010000000CC7B25FF48720A42932E5EF20335F073 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201123T130000 +DTEND;TZID=W. Europe Standard Time:20201123T140000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000002FD322CF0EC3D601000000000000000 + 0100000002879E7E16395374FA08EA9A114429811 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201125T130000 +DTEND;TZID=W. Europe Standard Time:20201125T134500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000089F581662EC3D601000000000000000 + 0100000004D75E5BEC145564D9A55BBE5A1ED1800 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20201127T091500 +DTEND;TZID=W. Europe Standard Time:20201127T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000005F8D2ADC1BC7D601000000000000000 + 010000000DEF2117458876E41BCF3BA06CFCCA592 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201202T093000 +DTEND;TZID=W. Europe Standard Time:20201202T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000006CC4F013A9C4D601000000000000000 + 010000000F149D1D4B39A914CACE58508268387AD +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20201204T091500 +DTEND;TZID=W. Europe Standard Time:20201204T100000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000007BB741DEAFC8D601000000000000000 + 0100000001684F5DC2799904BA0520074A8841886 +SUMMARY:Free +DTSTART;VALUE=DATE:20201207 +DTEND;VALUE=DATE:20201208 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000002099F7108FCCD601000000000000000 + 01000000092773DDAACCD5343A8B16FCFEB67D040 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20201207T130000 +DTEND;TZID=W. Europe Standard Time:20201207T133000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000001F0DAC8B99C8D601000000000000000 + 010000000DAE6A8429A0E224BBEFA1ECD2FD61D5C +SUMMARY:Away +DTSTART;VALUE=DATE:20201214 +DTEND;VALUE=DATE:20210103 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000007D187AF9CAE4D601000000000000000 + 010000000FE63728EA079484EAD5DC5A25B5E16A7 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210107T094500 +DTEND;TZID=W. Europe Standard Time:20210107T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210125T230000Z;INTERVAL=1;BYDAY=MO,TU;WKST=SU +UID:040000008200E00074C5B7101A82E008000000002B891E7C27E4D601000000000000000 + 0100000006DE03E881A143B45AD0DCE35DAE22EAE +SUMMARY:Away +DTSTART;VALUE=DATE:20210111 +DTEND;VALUE=DATE:20210112 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000009CBE5D424AEAD601000000000000000 + 01000000022A6E26556835141B9E5E95142C40DEF +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210114T100000 +DTEND;TZID=W. Europe Standard Time:20210114T110000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000884B9DBFC7E9D601000000000000000 + 010000000A30A5DF8B2B46D488F11D6B274B544D6 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210121T110000 +DTEND;TZID=W. Europe Standard Time:20210121T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000060DBEE5200F0D601000000000000000 + 01000000023B33A24482A7D46B9B37405A78E3366 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210121T160000 +DTEND;TZID=W. Europe Standard Time:20210121T170000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000020F5C57016CFD601000000000000000 + 0100000007BB1D7474520E348A825D6909AA899FE +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210128T123000 +DTEND;TZID=W. Europe Standard Time:20210128T131500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000440773E767F5D601000000000000000 + 0100000009966933D911E7E42B6AB82C6BD6B4F32 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210129T143000 +DTEND;TZID=W. Europe Standard Time:20210129T151500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000000EEC6AE56EFDD601000000000000000 + 010000000A13DB89C59332647B6810D2CAF3934BE +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20210208T123000 +DTEND;TZID=W. Europe Standard Time:20210208T153000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000376247EB6EFDD601000000000000000 + 010000000D8E5E8A206338C4093D0A8D898092505 +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20210209T100000 +DTEND;TZID=W. Europe Standard Time:20210209T110000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000063A58C56C8FED601000000000000000 + 0100000000CA0BC7BC5659B4CADDDFC4178FB5E91 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210209T150000 +DTEND;TZID=W. Europe Standard Time:20210209T154500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210701T220000Z;INTERVAL=1;BYDAY=WE,FR;WKST=SU +EXDATE;TZID=W. Europe Standard Time:20210409T000000 +UID:040000008200E00074C5B7101A82E0080000000062FD14411601D701000000000000000 + 01000000052EAF66D5C3BC6458198F47BAE50996F +SUMMARY:Away +DTSTART;VALUE=DATE:20210210 +DTEND;VALUE=DATE:20210211 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000303073B75600D701000000000000000 + 010000000AE5A210A3F9BEE44B9B75195D0FAB846 +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20210211T102000 +DTEND;TZID=W. Europe Standard Time:20210211T105000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000001033B0755900D701000000000000000 + 010000000F21D5BA0A102E44FA1349F57C83524E9 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210212T100000 +DTEND;TZID=W. Europe Standard Time:20210212T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000009A51E42C8500D701000000000000000 + 01000000026D978CECD0ED94ABAE1820E59EBDACD +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210212T140000 +DTEND;TZID=W. Europe Standard Time:20210212T144500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000919F8FC44601D701000000000000000 + 010000000C69F0D73D52D9644856FB428C86A0058 +SUMMARY:Away +DTSTART;VALUE=DATE:20210215 +DTEND;VALUE=DATE:20210220 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000053199367C4FED601000000000000000 + 0100000007A757D6743D6AC45B928616252EEFD13 +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20210215T133000 +DTEND;TZID=W. Europe Standard Time:20210215T150000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000001A5783F69306D701000000000000000 + 01000000091231B1A616E6E4C83C33C02741BCCB2 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210222T111500 +DTEND;TZID=W. Europe Standard Time:20210222T114500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210614T140000Z;INTERVAL=1;BYDAY=MO;WKST=SU +UID:040000008200E00074C5B7101A82E0080000000032F6CC7BC4FED601000000000000000 + 01000000059A4AC935178BE498342C66FCD96605B +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210222T160000 +DTEND;TZID=W. Europe Standard Time:20210222T170000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000005A6368804A0BD701000000000000000 + 010000000282C19D7AAC16D42A5AE2A4BA42C666D +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210225T140000 +DTEND;TZID=W. Europe Standard Time:20210225T144500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000D0AABD9383F5D601000000000000000 + 0100000000A8335BCAA31684BA1520DCE5D660873 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210304T120000 +DTEND;TZID=W. Europe Standard Time:20210304T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000415891C7D211D701000000000000000 + 0100000005DECAE2F4DBDE242B2CCFD09A74DBABA +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210308T133000 +DTEND;TZID=W. Europe Standard Time:20210308T140000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000AF393EB98015D701000000000000000 + 010000000763A658A5A46CD44BE873E1C3015A606 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210311T110000 +DTEND;TZID=W. Europe Standard Time:20210311T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000005B85AB63FA14D701000000000000000 + 01000000004B8755F6F487641B55BB9603E985222 +SUMMARY:Free +DTSTART;TZID=W. Europe Standard Time:20210311T130000 +DTEND;TZID=W. Europe Standard Time:20210311T133000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +X-MICROSOFT-CDO-BUSYSTATUS:FREE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210527T130000Z;INTERVAL=1;BYDAY=TH;WKST=MO +UID:040000008200E00074C5B7101A82E00800000000E9C052427816D701000000000000000 + 0100000008F01F7E13E614043AD1987096D963151 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210311T150000 +DTEND;TZID=W. Europe Standard Time:20210311T153000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000502B3C36A30ED701000000000000000 + 010000000EAF014D5A35FB14B93C6395A458BF29A +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210315T100000 +DTEND;TZID=W. Europe Standard Time:20210315T103000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000F4592894950ED701000000000000000 + 010000000A7CBC87DBBCA6943AAA7201F572B2A11 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210316T100000 +DTEND;TZID=W. Europe Standard Time:20210316T110000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000020BBAF412A17D701000000000000000 + 01000000015C7B7FD9D3C704D8A71EA1095C4144C +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210316T133000 +DTEND;TZID=W. Europe Standard Time:20210316T143000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000EAB7100F3E1BD701000000000000000 + 01000000039AC9904FB7B574CACB169DCA9A308C5 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210318T110000 +DTEND;TZID=W. Europe Standard Time:20210318T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:3 +X-MICROSOFT-CDO-APPT-SEQUENCE:3 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000056708658D41BD701000000000000000 + 010000000288A95567748874395EB3C35488488C4 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210318T140000 +DTEND;TZID=W. Europe Standard Time:20210318T144500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000047612D687716D701000000000000000 + 010000000C64ADB397B7B5F46A00A2569EC5EA967 +RECURRENCE-ID;TZID=W. Europe Standard Time:20210325T110000 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210325T110000 +DTEND;TZID=W. Europe Standard Time:20210325T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +RRULE:FREQ=WEEKLY;UNTIL=20210520T090000Z;INTERVAL=2;BYDAY=TH;WKST=MO +UID:040000008200E00074C5B7101A82E0080000000047612D687716D701000000000000000 + 010000000C64ADB397B7B5F46A00A2569EC5EA967 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210325T110000 +DTEND;TZID=W. Europe Standard Time:20210325T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000E438F64A7D1AD701000000000000000 + 0100000006865BBCD6ADF3C498266727498DA68B8 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210325T130000 +DTEND;TZID=W. Europe Standard Time:20210325T134500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000009000BFC0C820D701000000000000000 + 01000000019AB21D3295D4041B98C981AF2630F56 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210325T140000 +DTEND;TZID=W. Europe Standard Time:20210325T150000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000004025612F8D21D701000000000000000 + 010000000EB5CE5C94A6D8D4DBFFEAE51936A4B0B +SUMMARY:Tentative +DTSTART;TZID=W. Europe Standard Time:20210326T203000 +DTEND;TZID=W. Europe Standard Time:20210326T213000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000E9C052427816D701000000000000000 + 0100000008F01F7E13E614043AD1987096D963151 +RECURRENCE-ID;TZID=W. Europe Standard Time:20210401T150000 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210401T140000 +DTEND;TZID=W. Europe Standard Time:20210401T143000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:2 +X-MICROSOFT-CDO-APPT-SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000011605C2ACD26D701000000000000000 + 010000000F798EE164A5A0649A59CE9127B6CFC1B +SUMMARY:Away +DTSTART;TZID=W. Europe Standard Time:20210401T153000 +DTEND;TZID=W. Europe Standard Time:20210401T170000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000047612D687716D701000000000000000 + 010000000C64ADB397B7B5F46A00A2569EC5EA967 +RECURRENCE-ID;TZID=W. Europe Standard Time:20210408T110000 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210408T110000 +DTEND;TZID=W. Europe Standard Time:20210408T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000FD7292805F2CD701000000000000000 + 01000000084E6928FD699C3428C99D6027BA86275 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210408T143000 +DTEND;TZID=W. Europe Standard Time:20210408T150000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000F610DC2BDE24D701000000000000000 + 010000000F0F7E5A9202B5C4DAF526C900716D097 +SUMMARY:Away +DTSTART;VALUE=DATE:20210409 +DTEND;VALUE=DATE:20210410 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:OOF +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000D05779292F2DD701000000000000000 + 010000000D150442AD591AE4B86FD598B7840020D +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210412T110000 +DTEND;TZID=W. Europe Standard Time:20210412T113000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000605EA78DFF10D701000000000000000 + 010000000EAE226447A402443AF0B8B003E0E015D +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210415T120000 +DTEND;TZID=W. Europe Standard Time:20210415T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E0080000000047612D687716D701000000000000000 + 010000000C64ADB397B7B5F46A00A2569EC5EA967 +RECURRENCE-ID;TZID=W. Europe Standard Time:20210422T110000 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210422T110000 +DTEND;TZID=W. Europe Standard Time:20210422T120000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000E9C052427816D701000000000000000 + 0100000008F01F7E13E614043AD1987096D963151 +RECURRENCE-ID;TZID=W. Europe Standard Time:20210422T150000 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210422T144500 +DTEND;TZID=W. Europe Standard Time:20210422T151500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:3 +X-MICROSOFT-CDO-APPT-SEQUENCE:3 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:3 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000A0167C30CF35D701000000000000000 + 010000000EFA0066ABD2B1B418293BD97F9CDBA8C +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210429T090000 +DTEND;TZID=W. Europe Standard Time:20210429T110000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:1 +X-MICROSOFT-CDO-APPT-SEQUENCE:1 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000532394E1CC3CD701000000000000000 + 0100000007DD8BEF5B4F8184EA8CEA7032E7534D7 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210429T110000 +DTEND;TZID=W. Europe Standard Time:20210429T113000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000A7CA7204D73CD701000000000000000 + 010000000BF412F30A119224B9A77AA835DF602C4 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210429T141500 +DTEND;TZID=W. Europe Standard Time:20210429T150000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000B0B38DC8F431D701000000000000000 + 01000000051CF385F745B294C895769D9C4C29FA1 +SUMMARY:Busy +DTSTART;TZID=W. Europe Standard Time:20210520T120000 +DTEND;TZID=W. Europe Standard Time:20210520T130000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210508T112140Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_icalevents.py b/test/test_icalevents.py index d059dda..5036273 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -325,4 +325,13 @@ def test_recurence_id_google(self): evs = icalevents.events(file=ical, start=start, end=end) - self.assertEqual(len(evs), 4, "4 events in total") \ No newline at end of file + self.assertEqual(len(evs), 4, "4 events in total") + + def test_cest(self): + ical = "test/test_data/cest.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + evs = icalevents.events(file=ical, start=start, end=end) + + self.assertEqual(len(evs), 115, "4 events in total") \ No newline at end of file From e77f41f9f04be35f4fd2ecda3833ebf534b2013a Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 16 May 2021 20:23:20 +0200 Subject: [PATCH 07/69] feat: respect transparency --- icalevents/icalparser.py | 5 +++ test/test_data/transparent.ics | 63 ++++++++++++++++++++++++++++++++++ test/test_icalevents.py | 12 ++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/test_data/transparent.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 1bd65f2..6beda1b 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -40,6 +40,7 @@ def __init__(self): self.start = None self.end = None self.all_day = True + self.transparent = False self.recurring = False self.location = None self.private = False @@ -133,6 +134,7 @@ def copy_to(self, new_start=None, uid=None): ne.attendee = self.attendee ne.organizer = self.organizer ne.private = self.private + ne.transparent = self.transparent ne.uid = uid ne.created = self.created ne.last_modified = self.last_modified @@ -201,6 +203,9 @@ def create_event(component, tz=UTC): event_class = component.get('class') event.private = event_class == 'PRIVATE' or event_class == 'CONFIDENTIAL' + if component.get('class'): + event.transparent = component.get('transp') == 'TRANSPARENT' + if component.get('created'): event.created = normalize(component.get('created').dt, tz) diff --git a/test/test_data/transparent.ics b/test/test_data/transparent.ics new file mode 100644 index 0000000..964e5c2 --- /dev/null +++ b/test/test_data/transparent.ics @@ -0,0 +1,63 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Kalender +BEGIN:VTIMEZONE +TZID:Central European Standard Time +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E008000000007BFBACB1614AD701000000000000000 + 0100000004A699A72684F7646A814017A9F145FE8 +SUMMARY:Free +DTSTART;VALUE=DATE:20210520 +DTEND;VALUE=DATE:20210521 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210516T144840Z +TRANSP:TRANSPARENT +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:TRUE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +UID:040000008200E00074C5B7101A82E00800000000F7B85AB6614AD701000000000000000 + 01000000005576067F6247C45A572F9B9E56ABB32 +SUMMARY:Busy +DTSTART;TZID=Central European Standard Time:20210521T070000 +DTEND;TZID=Central European Standard Time:20210521T223000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20210516T144840Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 5036273..e94d593 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -334,4 +334,14 @@ def test_cest(self): evs = icalevents.events(file=ical, start=start, end=end) - self.assertEqual(len(evs), 115, "4 events in total") \ No newline at end of file + self.assertEqual(len(evs), 115, "4 events in total") + + def test_transparent(self): + ical = "test/test_data/transparent.ics" + start = date(2021, 1, 1) + end = date(2021, 12, 31) + + [e1, e2] = icalevents.events(file=ical, start=start, end=end) + + self.assertEqual(e1.transparent, True, "respect transparency") + self.assertEqual(e2.transparent, False, "respect opaqueness") \ No newline at end of file From 71dd7bb5ccc342a8b686a7eec47061291c43e786 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 24 May 2021 21:22:09 +0200 Subject: [PATCH 08/69] doc: explain what x-wr-timezone is --- icalevents/icalparser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 6beda1b..016439d 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -274,6 +274,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Keep track of the timezones defined in the calendar timezones = {} + # Parse non standard timezone name if 'X-WR-TIMEZONE' in calendar: timezones[str(calendar['X-WR-TIMEZONE'])] = gettz(str(calendar['X-WR-TIMEZONE'])) From 8e429fecb2be28832f9734ada077a95fd948079c Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 24 May 2021 21:23:06 +0200 Subject: [PATCH 09/69] refactor: extract get_timezone function --- icalevents/icalparser.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 016439d..dfe74f3 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -251,6 +251,12 @@ def normalize(dt, tz=UTC): return dt +def get_timezone(tz_name): + if tz_name in WINDOWS_TO_OLSON: + return gettz(WINDOWS_TO_OLSON[tz_name]) + else: + return gettz(tz_name) + def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): """ Query the events occurring in a given time range. @@ -274,9 +280,11 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Keep track of the timezones defined in the calendar timezones = {} + # Parse non standard timezone name if 'X-WR-TIMEZONE' in calendar: - timezones[str(calendar['X-WR-TIMEZONE'])] = gettz(str(calendar['X-WR-TIMEZONE'])) + x_wr_timezone = str(calendar['X-WR-TIMEZONE']) + timezones[x_wr_timezone] = get_timezone(x_wr_timezone) for c in calendar.walk('VTIMEZONE'): name = str(c['TZID']) @@ -292,9 +300,7 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # If there's exactly one timezone in the file, # assume it applies globally, otherwise UTC if len(timezones) == 1: - cal_tz = gettz(list(timezones)[0]) - if not cal_tz and str(c['TZID']) in WINDOWS_TO_OLSON: - cal_tz = gettz(WINDOWS_TO_OLSON[str(c['TZID'])]) + cal_tz = get_timezone(list(timezones)[0]) else: cal_tz = UTC From 9f7096ad52e27eb3bff0cf0a1e5a83c5f86713a7 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 24 May 2021 21:49:49 +0200 Subject: [PATCH 10/69] refactor: remove extra code for recurring all day events - handle timezone issues in a generalized way for all reccuring events --- icalevents/icalparser.py | 67 +++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index dfe74f3..687bd91 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -336,44 +336,24 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # use it; otherwise, attempt to load the rules from pytz. start_tz = None end_tz = None - if e.all_day and e.recurring: - # Keep the timezone around if to apply later - start_tz = e.start.tzinfo - end_tz = e.end.tzinfo - - # Start and end times for all day events must not have - # a timezone because the specification forbids the - # RRULE UNTIL from having a timezone. On the other - # hand, they must be datetime values (not just dates) - # because RRULE UNTIL will do a comparison against a - # timezone naive datetime. So we coerce start and end - # times for all day events into timezone naive - # datetime values. - e.start = datetime.combine(e.start.date(), datetime.min.time()) - e.end = datetime.combine(e.end.date(), datetime.min.time()) - start = datetime.combine(start, datetime.min.time()) - end = datetime.combine(end, datetime.min.time()) - else: - # Work out the staring and ending timezone. We don't do - # this for all-day appointments because they aren't really - # in a timezone. - if e.start.tzinfo != UTC: - if str(e.start.tzinfo) in timezones: - start_tz = timezones[str(e.start.tzinfo)] - else: - try: - start_tz = e.start.tzinfo - except: - pass - - if e.end.tzinfo != UTC: - if str(e.end.tzinfo) in timezones: - end_tz = timezones[str(e.end.tzinfo)] - else: - try: - end_tz = e.end.tzinfo - except: - pass + + if e.start.tzinfo != UTC: + if str(e.start.tzinfo) in timezones: + start_tz = timezones[str(e.start.tzinfo)] + else: + try: + start_tz = e.start.tzinfo + except: + pass + + if e.end.tzinfo != UTC: + if str(e.end.tzinfo) in timezones: + end_tz = timezones[str(e.end.tzinfo)] + else: + try: + end_tz = e.end.tzinfo + except: + pass # If we've been passed or constructed start/end values # that are timezone naive, but the actual appointment @@ -391,6 +371,17 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Unfold recurring events according to their rrule rule = parse_rrule(component, cal_tz) after = start - duration + end = end + + # Remove timezone if none is present in component + if isinstance(component['dtstart'].dt, date) or component['dtstart'].dt.tzinfo is None: + after = after.replace(tzinfo=None) + end = end.replace(tzinfo=None) + + # Add timezone if one is present in component + if isinstance(component['dtstart'].dt, datetime) and not component['dtstart'].dt.tzinfo is None: + after = normalize(after, start_tz) + end = normalize(end, end_tz) for dt in rule.between(after, end, inc=True): if start_tz is None: From 2fd3520231126f2012dbe462ee92ea4194f58d78 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 24 May 2021 21:51:31 +0200 Subject: [PATCH 11/69] refactor: remove unused try-catch --- icalevents/icalparser.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 687bd91..0d4809d 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -341,19 +341,13 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): if str(e.start.tzinfo) in timezones: start_tz = timezones[str(e.start.tzinfo)] else: - try: - start_tz = e.start.tzinfo - except: - pass + start_tz = e.start.tzinfo if e.end.tzinfo != UTC: if str(e.end.tzinfo) in timezones: end_tz = timezones[str(e.end.tzinfo)] else: - try: - end_tz = e.end.tzinfo - except: - pass + end_tz = e.end.tzinfo # If we've been passed or constructed start/end values # that are timezone naive, but the actual appointment From efb7fbe3b4a762564c31ff9949daf04cff49c43b Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 24 May 2021 23:50:44 +0200 Subject: [PATCH 12/69] fix: add timezone for exdates only if component has tz --- icalevents/icalparser.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 0d4809d..523c5f5 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -257,6 +257,19 @@ def get_timezone(tz_name): else: return gettz(tz_name) + +def adjust_timezone(component, dates, tz=None): + # Remove timezone if none is present in component + if isinstance(component['dtstart'].dt, date) or component['dtstart'].dt.tzinfo is None: + dates = [date.replace(tzinfo=None) for date in dates] + + # Add timezone if one is present in component + if isinstance(component['dtstart'].dt, datetime) and not component['dtstart'].dt.tzinfo is None: + dates = [normalize(date) for date in dates] + + return dates + + def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): """ Query the events occurring in a given time range. @@ -364,18 +377,8 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): if e.recurring: # Unfold recurring events according to their rrule rule = parse_rrule(component, cal_tz) - after = start - duration - end = end - - # Remove timezone if none is present in component - if isinstance(component['dtstart'].dt, date) or component['dtstart'].dt.tzinfo is None: - after = after.replace(tzinfo=None) - end = end.replace(tzinfo=None) - - # Add timezone if one is present in component - if isinstance(component['dtstart'].dt, datetime) and not component['dtstart'].dt.tzinfo is None: - after = normalize(after, start_tz) - end = normalize(end, end_tz) + [after] = adjust_timezone(component, [start - duration], start_tz) + [end] = adjust_timezone(component, [end], start_tz) for dt in rule.between(after, end, inc=True): if start_tz is None: @@ -447,7 +450,7 @@ def parse_rrule(component, tz=UTC): # Add exdates to the rruleset for exd in extract_exdates(component): - rule.exdate(exd.replace(tzinfo=None) if type(rdtstart) is date else exd) + rule.exdate(exd) # TODO: What about rdates and exrules? @@ -476,4 +479,4 @@ def extract_exdates(component): elif isinstance(exd_prop, vDDDLists): dates.extend(normalize(exd.dt) for exd in exd_prop.dts) - return dates + return adjust_timezone(component, dates) From dc5ff993c103a80e771d2b5ff47c4d4dc9f3430c Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Tue, 25 May 2021 00:03:04 +0200 Subject: [PATCH 13/69] refactor: use adjust_timezone for parse_rrule --- icalevents/icalparser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 523c5f5..eec631a 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -261,7 +261,7 @@ def get_timezone(tz_name): def adjust_timezone(component, dates, tz=None): # Remove timezone if none is present in component if isinstance(component['dtstart'].dt, date) or component['dtstart'].dt.tzinfo is None: - dates = [date.replace(tzinfo=None) for date in dates] + dates = [date.replace(tzinfo=None) if type(date) is datetime else date for date in dates] # Add timezone if one is present in component if isinstance(component['dtstart'].dt, datetime) and not component['dtstart'].dt.tzinfo is None: @@ -432,10 +432,11 @@ def parse_rrule(component, tz=UTC): if type(rdtstart) is datetime: rdtstart = normalize(rdtstart, tz=tz) + # Remove/add timezone to rrule until dates depending on component if type(rdtstart) is date: for index, rru in enumerate(rrules): if 'UNTIL' in rru: - rrules[index]['UNTIL'] = [dt.date() if type(dt) is datetime else dt for dt in rru['UNTIL']] + rrules[index]['UNTIL'] = adjust_timezone(component, rru['UNTIL'], tz) # Parse the rrules, might return a rruleset instance, instead of rrule rule = rrulestr('\n'.join(x.to_ical().decode() for x in rrules), From 14afda6aeffd180ad2ed6e4cd379f71e1ab2756e Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Tue, 25 May 2021 00:11:38 +0200 Subject: [PATCH 14/69] doc: add comment to clarify the exclusion --- test/test_icalevents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_icalevents.py b/test/test_icalevents.py index e94d593..33c7b5a 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -115,7 +115,7 @@ def test_events_rrule_until_all_day_ms(self): evs = icalevents.events(file=ical, start=start, end=end) ev_0 = evs[0] - self.assertEqual(len(evs), 6, "Seven events and one is excluded") + self.assertEqual(len(evs), 6, "Seven events and one is excluded") # rrule_until_all_day_ms has one exdate (EXDATE;TZID=W. Europe Standard Time:20210430T000000) self.assertEqual(ev_0.start, datetime(2021, 3, 19, 00, 0, 0, tzinfo=gettz('Europe/Berlin'))) self.assertEqual(ev_0.recurring, True, "Recurring all day event") self.assertEqual(ev_0.summary, "Away") From 8441affb21179664ef7e03dcf7e4be3bc7063b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Hultn=C3=A9r?= Date: Mon, 6 Sep 2021 15:58:22 +0200 Subject: [PATCH 15/69] Split up list comprehension for readability. --- icalevents/icalparser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index eec631a..63614db 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -408,7 +408,10 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): 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] + return [ + event for event in found + if e.sequence is None or not (event.uid, event.start, e.sequence) in recurrence_ids + ] def parse_rrule(component, tz=UTC): From 7df46d63f9f1176217c9f005d938d80fde076a50 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Tue, 7 Sep 2021 18:49:37 +0200 Subject: [PATCH 16/69] added jazzband tag --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5203540..4b662f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # iCalEvents Simple Python 3 library to download, parse and query iCal sources. +[![Jazzband](https://jazzband.co/static/img/badge.svg)](https://jazzband.co/) ## Build info From 415daf4976b38aa3dac7832ece38613afceb9e83 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Tue, 7 Sep 2021 18:49:58 +0200 Subject: [PATCH 17/69] fix formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4b662f2..81410d2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # iCalEvents Simple Python 3 library to download, parse and query iCal sources. + [![Jazzband](https://jazzband.co/static/img/badge.svg)](https://jazzband.co/) ## Build info From fa8db407c4a97a33c3a5363c1d31cf6fbd05cd39 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Tue, 7 Sep 2021 18:51:28 +0200 Subject: [PATCH 18/69] Create CONTRIBUTING.md added contributing.md, as required by https://jazzband.co/about/guidelines --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ad78220 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) + +This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines). From 21779f8a1a5fd9c976b12bc6fb2200ff203409ef Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Tue, 7 Sep 2021 19:52:16 +0200 Subject: [PATCH 19/69] update repo url --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b0f9b7b..e842472 100755 --- a/setup.py +++ b/setup.py @@ -16,8 +16,8 @@ description = 'iCal downloader and parser', author = 'Thomas Irgang', author_email = 'thomas@irgang-la.de', - url = 'https://github.com/irgangla/icalevents', - download_url = 'https://github.com/irgangla/icalevents/archive/v' + version + '.tar.gz', + url = 'https://github.com/jazzband/icalevents', + download_url = 'https://github.com/jazzband/icalevents/archive/v' + version + '.tar.gz', keywords = ['iCal'], classifiers = [ 'Programming Language :: Python', From fccb3fa9f7c5cac05feef7f5980f8e9c108c1b57 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Tue, 7 Sep 2021 19:53:20 +0200 Subject: [PATCH 20/69] update repo url --- test/test_icalevents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 0561271..64c2752 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -9,7 +9,7 @@ class ICalEventsTests(unittest.TestCase): def test_events_url(self): - url = "https://raw.githubusercontent.com/irgangla/icalevents/master/test/test_data/basic.ics" + url = "https://raw.githubusercontent.com/jazzband/icalevents/master/test/test_data/basic.ics" start = date(2017, 5, 18) end = date(2017, 5, 19) @@ -151,7 +151,7 @@ def test_event_recurring_attribute(self): self.assertEqual(e2.recurring, True, "check recurring=True for recurring event (2)") def test_events_async_url(self): - url = "https://raw.githubusercontent.com/irgangla/icalevents/master/test/test_data/basic.ics" + url = "https://raw.githubusercontent.com/jazzband/icalevents/master/test/test_data/basic.ics" start = date(2017, 5, 18) end = date(2017, 5, 19) key = "basic" From 279e0d865d05ec90edd38badef34b44485491bea Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 21:46:11 +0200 Subject: [PATCH 21/69] delete old sphinx setup --- Makefile | 20 ------ make.bat | 36 ---------- source/conf.py | 180 ----------------------------------------------- source/index.rst | 41 ----------- 4 files changed, 277 deletions(-) delete mode 100644 Makefile delete mode 100644 make.bat delete mode 100644 source/conf.py delete mode 100644 source/index.rst diff --git a/Makefile b/Makefile deleted file mode 100644 index 0ef7755..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = iCalEvents -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/make.bat b/make.bat deleted file mode 100644 index 16288c8..0000000 --- a/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=source -set BUILDDIR=build -set SPHINXPROJ=iCalEvents - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/source/conf.py b/source/conf.py deleted file mode 100644 index 0eafcd5..0000000 --- a/source/conf.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -# -# iCalEvents documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 19 16:55:32 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.imgmath', - 'sphinx.ext.ifconfig'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'iCalEvents' -copyright = u'2017, Thomas Irgang' -author = u'Thomas Irgang' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'0.0.1' -# The full version, including alpha/beta/rc tags. -release = u'0.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'iCalEventsdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'iCalEvents.tex', u'iCalEvents Documentation', - u'Thomas Irgang', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'icalevents', u'iCalEvents Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'iCalEvents', u'iCalEvents Documentation', - author, 'iCalEvents', 'One line description of project.', - 'Miscellaneous'), -] - - - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/source/index.rst b/source/index.rst deleted file mode 100644 index 25bd50e..0000000 --- a/source/index.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. iCalEvents documentation master file, created by - sphinx-quickstart on Wed Jul 19 16:55:32 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to iCalEvents's documentation! -====================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -* :ref:`genindex` -* :ref:`modindex` - -icalevents -========== - -.. automodule:: icalevents.icalevents - :members: - :undoc-members: - :inherited-members: - :show-inheritance: - -icaldownload -============ - -.. automodule:: icalevents.icaldownload - :members: - :undoc-members: - :inherited-members: - :show-inheritance: - -icalparser -========== - -.. automodule:: icalevents.icalparser - :members: - :undoc-members: - :inherited-members: - :show-inheritance: From 6b52e5f680f68e167697445e8661298b864b82dd Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:07:12 +0200 Subject: [PATCH 22/69] add new Sphinx docs --- .gitignore | 2 ++ docs/Makefile | 20 +++++++++++++++ docs/conf.py | 61 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/.gitignore b/.gitignore index 0280133..c1b7833 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ dist/* MANIFEST icalevents.egg-info/* /build + +docs/_build/** \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..12cc5d7 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,61 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- Project information ----------------------------------------------------- + +project = 'iCalEvents' +copyright = '2021, Thomas Irgang' +author = 'Thomas Irgang' + +# The full version, including alpha/beta/rc tags +release = '0.1.25' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +html_theme_options = { + # Disable showing the sidebar. Defaults to 'false' + 'nosidebar': True, +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..8b70655 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,70 @@ +.. iCalEvents documentation master file, created by + sphinx-quickstart on Wed Sep 8 21:48:51 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to iCalEvents's documentation! +====================================== + +Simple Python 3 library to download, parse and query iCal sources. + + +Usage +===== + +iCloud: +------- + +.. code:: python + + from icalevents.icalevents import events + + es = events(, fix_apple=True) + +Google: +------- + +.. code:: python + + from icalevents.icalevents import events + + es = events() + + +API +=== + +Module contents +--------------- + +.. automodule:: icalevents + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +icalevents.icaldownload module +------------------------------ + +.. automodule:: icalevents.icaldownload + :members: + :undoc-members: + :show-inheritance: + +icalevents.icalevents module +---------------------------- + +.. automodule:: icalevents.icalevents + :members: + :undoc-members: + :show-inheritance: + +icalevents.icalparser module +---------------------------- + +.. automodule:: icalevents.icalparser + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From 535c203d3e9c919d60772a94d0c899de53450dbc Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:20:47 +0200 Subject: [PATCH 23/69] add documentation link --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 81410d2..68df33f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ https://travis-ci.org/irgangla/icalevents https://codecov.io/gh/irgangla/icalevents +## Documentation + +https://icalevents.readthedocs.io/en/latest/ + ## Usage ### iCloud: From 34292bb1ed3896797b075fb7402187759bf3d1f6 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:22:11 +0200 Subject: [PATCH 24/69] add Github action for tests --- .github/workflows/python-test.yml | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/python-test.yml diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 0000000..70bba55 --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 0b6c55f247eaf544a463281010202159a63aeebc Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:34:07 +0200 Subject: [PATCH 25/69] rename action to "run pytest" --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 70bba55..0b82ff6 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python application +name: Run pytest on: push: From 475fcc0c65eeb213a3238f429ca811d8d789b50a Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:34:23 +0200 Subject: [PATCH 26/69] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 68df33f..d781921 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,7 @@ Simple Python 3 library to download, parse and query iCal sources. ## Build info -[![Build Status](https://travis-ci.org/irgangla/icalevents.svg?branch=master)](https://travis-ci.org/irgangla/icalevents) - -https://travis-ci.org/irgangla/icalevents +See Github Action [run pytest](https://github.com/jazzband/icalevents/actions) [![codecov](https://codecov.io/gh/irgangla/icalevents/branch/master/graph/badge.svg)](https://codecov.io/gh/irgangla/icalevents) From 41f287eb52d7c2de5a5ea7bf16e3be4866bb423e Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:38:20 +0200 Subject: [PATCH 27/69] add Github Action badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d781921..5672dbb 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Simple Python 3 library to download, parse and query iCal sources. ## Build info +![run pytest](https://github.com/jazzband/icalevents/actions/workflows/python-test.yml/badge.svg) + See Github Action [run pytest](https://github.com/jazzband/icalevents/actions) [![codecov](https://codecov.io/gh/irgangla/icalevents/branch/master/graph/badge.svg)](https://codecov.io/gh/irgangla/icalevents) From f7caa0a5b8695e8817d705ca308cca5401ae341d Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:42:37 +0200 Subject: [PATCH 28/69] add example --- docs/index.rst | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 8b70655..042aa7c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,6 +31,56 @@ Google: es = events() +Example +======= + +see main.py + +.. code:: python + + from icalevents.icalevents import events_async, latest_events, all_done + from time import sleep + + if __name__ == '__main__': + keys = [] + + with open('calendars.txt', mode='r', encoding='utf-8') as f: + counter = 1 + + while True: + line = f.readline() + if not line: + break + + name, url = line.split(maxsplit=1) + name = name.strip() + url = url.strip() + + fix_apple = False + if name == 'icloud': + fix_apple = True + + key = "req_%d" % counter + counter += 1 + keys.append(key) + events_async(key, url, fix_apple=fix_apple) + + while keys: + print("%d request running." % len(keys)) + + for k in keys[:]: + if all_done(k): + print("Request %s finished." % k) + keys.remove(k) + + es = latest_events(k) + + for e in es: + print(e) + + sleep(2) + + API === From 01097c0da15db373d976bdfd494eaea345c9c24c Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Wed, 8 Sep 2021 22:59:12 +0200 Subject: [PATCH 29/69] remove broken codecov link --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 5672dbb..e5ac1f5 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,6 @@ Simple Python 3 library to download, parse and query iCal sources. See Github Action [run pytest](https://github.com/jazzband/icalevents/actions) -[![codecov](https://codecov.io/gh/irgangla/icalevents/branch/master/graph/badge.svg)](https://codecov.io/gh/irgangla/icalevents) - -https://codecov.io/gh/irgangla/icalevents - ## Documentation https://icalevents.readthedocs.io/en/latest/ From 21586582e3e7062c3de0791d4a9b1d705a686d87 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Fri, 10 Sep 2021 19:57:05 +0200 Subject: [PATCH 30/69] Create python-publish.yml --- .github/workflows/python-publish.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..54e319b --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,40 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + + + - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: jazzband + password: ${{ secrets.JAZZBAND_RELEASE_KEY }} + repository_url: https://jazzband.co/projects/django-formtools/upload From a5da451d5f103fc091faae4f278f61ed18015cb7 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Fri, 10 Sep 2021 20:00:23 +0200 Subject: [PATCH 31/69] add build status for master branch --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e5ac1f5..f762dbd 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Simple Python 3 library to download, parse and query iCal sources. ## Build info -![run pytest](https://github.com/jazzband/icalevents/actions/workflows/python-test.yml/badge.svg) +last push: ![run pytest](https://github.com/jazzband/icalevents/actions/workflows/python-test.yml/badge.svg) -See Github Action [run pytest](https://github.com/jazzband/icalevents/actions) +master: [![Run pytest](https://github.com/jazzband/icalevents/actions/workflows/python-test.yml/badge.svg?branch=master)](https://github.com/jazzband/icalevents/actions/workflows/python-test.yml) ## Documentation From f6bd89849dc219826471f91921817bf45c76379d Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Fri, 10 Sep 2021 20:03:50 +0200 Subject: [PATCH 32/69] test: run on push to check result --- .github/workflows/python-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 54e319b..4ece198 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -11,6 +11,8 @@ name: Upload Python Package on: release: types: [published] + push: + branches: [ master ] jobs: deploy: From ebe2c2c832eb393615d80629ae5494dee1adecde Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Fri, 10 Sep 2021 20:06:26 +0200 Subject: [PATCH 33/69] run publish only on release --- .github/workflows/python-publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4ece198..54e319b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -11,8 +11,6 @@ name: Upload Python Package on: release: types: [published] - push: - branches: [ master ] jobs: deploy: From 091ce500c39888a5caef7dafe833f2fea7f3fd71 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 08:36:39 +0200 Subject: [PATCH 34/69] feat: add codecov task --- .github/workflows/python-test.yml | 48 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 0b82ff6..2a3b786 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -5,32 +5,36 @@ name: Run pytest on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest coverage + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + coverage run tests.py + coverage xml + - name: 'Upload coverage to Codecov' + uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true From 5aa36f0c443655b71963057cc30268fe5a6d070d Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 08:37:04 +0200 Subject: [PATCH 35/69] feat: configure coverate --- .coveragerc | 28 ++++------------------------ .gitignore | 3 ++- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/.coveragerc b/.coveragerc index fc0969a..4673dc2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,33 +1,13 @@ -# .coveragerc to control coverage.py [run] branch = True -omit = - # omit tests - */test/* - # omit test runner - test.py - # omit example runner - main.py +source = icalevents [report] -# Regexes for lines to exclude from consideration exclude_lines = - # Have to re-enable the standard pragma + if self.debug: pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: if __name__ == .__main__.: - ignore_errors = True - -[html] -directory = coverage_html_report \ No newline at end of file +omit = + tests/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index c1b7833..0dbb58f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ MANIFEST icalevents.egg-info/* /build -docs/_build/** \ No newline at end of file +docs/_build/** +coverage.xml \ No newline at end of file From 74b2bdcd09d1d2f3655d3224ed7f148286e3ea06 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 08:39:44 +0200 Subject: [PATCH 36/69] fix: make it run --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 2a3b786..6136ef1 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -32,7 +32,7 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - coverage run tests.py + coverage run test.py coverage xml - name: 'Upload coverage to Codecov' uses: codecov/codecov-action@v2 From 5f51f1896844fd3ee75a6fae615a2d638de5c3c6 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 09:49:29 +0200 Subject: [PATCH 37/69] feat: add black, flake config and pre-commit --- .flake8 | 3 +++ .github/workflows/python-test.yml | 12 +++++------- .pre-commit-config.yaml | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..c2fdddc --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-complexity = 10 +select = E9,F63,F7,F82 diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 6136ef1..d180b71 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -22,14 +22,12 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest coverage - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 + pip install pipenv==v2021.5.29 + pipenv install --dev --deploy + - name: Lint with black and flake run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + black --check icalevents + flake8 . --count --exit-zero - name: Test with pytest run: | coverage run test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1b47c3c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +ci: + skip: [pylint] + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/psf/black + rev: 21.9b0 + hooks: + - id: black + args: ['--check'] + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.9 + hooks: + - id: flake8 From 8d0d3c2745b343db19639df53b03a8576173ca09 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 09:50:07 +0200 Subject: [PATCH 38/69] feat: uses pipenv and version locking for dependencies --- Pipfile | 20 +++ Pipfile.lock | 416 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 7 - 3 files changed, 436 insertions(+), 7 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 requirements.txt diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..eabc0a1 --- /dev/null +++ b/Pipfile @@ -0,0 +1,20 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +httplib2 = "==0.20.1" +icalendar = "==4.0.8" +python-dateutil = "==2.8.2" +pytz = "==2021.3" +DateTime = "==4.3" + +[dev-packages] +coverage = "==6.0.1" +codecov = "==2.1.12" +pytest = "==6.2.5" +black = "===21.9b0" + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..e570935 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,416 @@ +{ + "_meta": { + "hash": { + "sha256": "7b58fd26bb7d2a7799a903c1f4ed1dcbc6541dc145b47b4aadeb0e5e56e98c1f" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "datetime": { + "hashes": [ + "sha256:371dba07417b929a4fa685c2f7a3eaa6a62d60c02947831f97d4df9a9e70dfd0", + "sha256:5cef605bab8259ff61281762cdf3290e459fbf0b4719951d5fab967d5f2ea0ea" + ], + "index": "pypi", + "version": "==4.3" + }, + "httplib2": { + "hashes": [ + "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327", + "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185" + ], + "index": "pypi", + "version": "==0.20.1" + }, + "icalendar": { + "hashes": [ + "sha256:7508a92b4e36049777640b0ae393e7219a16488d852841a0e57b44fe51d9f848", + "sha256:825209edeb8f3067cf31f0712b0fc9507b1ded270165f98f3176d6b50e8c7b78" + ], + "index": "pypi", + "version": "==4.0.8" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "index": "pypi", + "version": "==2.8.2" + }, + "pytz": { + "hashes": [ + "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", + "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" + ], + "index": "pypi", + "version": "==2021.3" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "zope.interface": { + "hashes": [ + "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", + "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", + "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", + "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", + "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", + "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", + "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", + "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", + "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", + "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", + "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", + "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", + "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", + "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", + "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", + "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", + "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", + "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", + "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", + "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", + "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", + "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", + "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", + "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", + "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", + "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", + "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", + "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", + "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", + "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", + "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", + "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", + "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", + "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", + "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", + "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", + "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", + "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", + "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", + "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", + "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", + "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", + "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", + "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", + "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", + "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", + "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", + "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", + "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", + "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", + "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==5.4.0" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "black": { + "hashes": [ + "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115", + "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91" + ], + "index": "pypi", + "version": "===21.9b0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6", + "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f" + ], + "markers": "python_version >= '3'", + "version": "==2.0.6" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "codecov": { + "hashes": [ + "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47", + "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635", + "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1" + ], + "index": "pypi", + "version": "==2.1.12" + }, + "coverage": { + "hashes": [ + "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26", + "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666", + "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee", + "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345", + "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5", + "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad", + "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f", + "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854", + "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32", + "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7", + "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12", + "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec", + "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc", + "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5", + "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724", + "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7", + "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d", + "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194", + "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e", + "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a", + "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b", + "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a", + "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e", + "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22", + "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b", + "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793", + "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397", + "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815", + "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827", + "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb", + "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43", + "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf", + "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16" + ], + "index": "pypi", + "version": "==6.0.1" + }, + "flake8": { + "hashes": [ + "sha256:124554bfd067e04d891258c0208a764b512ca3a94c8a3c06bea56af539dd74db", + "sha256:b52d27e627676b015340c3b1c72bc9259a6cacc9341712fb8f01ddfaaa2c651a" + ], + "index": "pypi", + "version": "==4.0.0" + }, + "idna": { + "hashes": [ + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + ], + "markers": "python_version >= '3'", + "version": "==3.2" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + ], + "markers": "python_version >= '3.6'", + "version": "==21.0" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", + "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.4.0" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", + "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.8.0" + }, + "pyflakes": { + "hashes": [ + "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", + "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "regex": { + "hashes": [ + "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f", + "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3", + "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838", + "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01", + "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f", + "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a", + "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432", + "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f", + "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc", + "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9", + "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152", + "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493", + "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361", + "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61", + "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593", + "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354", + "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee", + "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3", + "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741", + "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b", + "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb", + "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca", + "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3", + "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072", + "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d", + "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b", + "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf", + "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd", + "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e", + "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700", + "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59", + "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991", + "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287", + "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7", + "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1", + "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e", + "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92", + "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820", + "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4", + "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2", + "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f" + ], + "version": "==2021.10.8" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + ], + "version": "==3.10.0.2" + }, + "urllib3": { + "hashes": [ + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.7" + } + } +} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a09d117..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -httplib2 -icalendar -python-dateutil >= 2.7.0 -pytz -datetime -coverage -codecov From 21b9f99b6f5ff786dcb07c2e696ef75d2ba2fc48 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 10:01:43 +0200 Subject: [PATCH 39/69] doc: add hint for contribution in readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index f762dbd..d1503cc 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,13 @@ from icalevents.icalevents import events es = events() ``` + +# Contributing + +You will need [pipenv](https://pipenv.pypa.io/en/latest/) and [pre-commit](https://pre-commit.com/index.html) installed and than run. + +```bash +pre-commit install +``` + +Happy contributing! From 1eda28ff87b3fda9391fb1c77beab48a2ad048e9 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 10:02:44 +0200 Subject: [PATCH 40/69] ci: removes unused ci skip in pre-commit --- .pre-commit-config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b47c3c..0af632a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,3 @@ -ci: - skip: [pylint] - repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 From 524a79a11d5e31358539c953a1b142091bcc72a5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 08:03:07 +0000 Subject: [PATCH 41/69] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .coveragerc | 2 +- .github/workflows/python-publish.yml | 4 ++-- .gitignore | 2 +- docs/conf.py | 2 +- docs/index.rst | 2 +- test/test_data/categories_test.ics | 2 +- test/test_data/icloud.ics | 4 ++-- test/test_data/icloud_content.txt | 4 ++-- test/test_icaldownload.py | 1 - test/test_icalparser.py | 2 +- 10 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.coveragerc b/.coveragerc index 4673dc2..51243ad 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,4 +10,4 @@ exclude_lines = if __name__ == .__main__.: ignore_errors = True omit = - tests/* \ No newline at end of file + tests/* diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 54e319b..e49f459 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -29,7 +29,7 @@ jobs: pip install build - name: Build package run: python -m build - + - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') @@ -37,4 +37,4 @@ jobs: with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} - repository_url: https://jazzband.co/projects/django-formtools/upload + repository_url: https://jazzband.co/projects/django-formtools/upload diff --git a/.gitignore b/.gitignore index 0dbb58f..e126df8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ icalevents.egg-info/* /build docs/_build/** -coverage.xml \ No newline at end of file +coverage.xml diff --git a/docs/conf.py b/docs/conf.py index 12cc5d7..f2b1d94 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,4 +58,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index 042aa7c..e9ad1ef 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ iCloud: ------- .. code:: python - + from icalevents.icalevents import events es = events(, fix_apple=True) diff --git a/test/test_data/categories_test.ics b/test/test_data/categories_test.ics index 756d8f6..c1e2e76 100644 --- a/test/test_data/categories_test.ics +++ b/test/test_data/categories_test.ics @@ -24,4 +24,4 @@ DTSTART:20201118T021500Z DTEND:20201118T041500Z CATEGORIES:In19-S04-IT2406,In19-S04-IT2405 END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/icloud.ics b/test/test_data/icloud.ics index 1679376..5075ce3 100644 --- a/test/test_data/icloud.ics +++ b/test/test_data/icloud.ics @@ -246,12 +246,12 @@ DTEND;TZID=Europe/Berlin:20161103T183000 DTSTAMP:20161026T154649Z DTSTART;TZID=Europe/Berlin:20161103T180000 LAST-MODIFIED:20161026T154648Z -LOCATION:Naturpark Altmühltal\nKanalstraße 5\n91757 +LOCATION:Naturpark Altmühltal\nKanalstraße 5\n91757 Treuchtlingen\nDeutschland SEQUENCE:0 SUMMARY:Friseur UID:003AFB7E-BA60-481A-A087-23024D956074 -X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Kanalstraße 5\\n91757 +X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Kanalstraße 5\\n91757 Treuchtlingen\\nDeutschland;X-APPLE-RADIUS=100;X-APPLE-REFERENCEFRAME=1; X-TITLE=Naturpark Altmühltal:geo:48.954682,10.909644 END:VEVENT diff --git a/test/test_data/icloud_content.txt b/test/test_data/icloud_content.txt index aeb9bc1..bed4f1b 100644 --- a/test/test_data/icloud_content.txt +++ b/test/test_data/icloud_content.txt @@ -246,12 +246,12 @@ DTEND;TZID=Europe/Berlin:20161103T183000 DTSTAMP:20161026T154649Z DTSTART;TZID=Europe/Berlin:20161103T180000 LAST-MODIFIED:20161026T154648Z -LOCATION:Naturpark Altmühltal\nKanalstraße 5\n91757 +LOCATION:Naturpark Altmühltal\nKanalstraße 5\n91757 Treuchtlingen\nDeutschland SEQUENCE:0 SUMMARY:Friseur UID:003AFB7E-BA60-481A-A087-23024D956074 -X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Kanalstraße 5\\n91757 +X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Kanalstraße 5\\n91757 Treuchtlingen\\nDeutschland;X-APPLE-RADIUS=100;X-APPLE-REFERENCEFRAME=1; X-TITLE=Naturpark Altmühltal:geo:48.954682,10.909644 END:VEVENT diff --git a/test/test_icaldownload.py b/test/test_icaldownload.py index adbe8f8..908a47f 100644 --- a/test/test_icaldownload.py +++ b/test/test_icaldownload.py @@ -92,4 +92,3 @@ def test_read_only_directory(self): # Delete tmp dir os.chdir("..") shutil.rmtree("tmp") - diff --git a/test/test_icalparser.py b/test/test_icalparser.py index fba9710..7d944ba 100644 --- a/test/test_icalparser.py +++ b/test/test_icalparser.py @@ -100,7 +100,7 @@ def test_attendee(self): def test_organizer(self): self.assertIsInstance(self.eventA.organizer, str) self.assertIsInstance(self.eventB.organizer, str) - + def test_str(self): self.eventA.start = datetime(year=2017, month=2, day=3, hour=12, minute=5) self.eventA.end = datetime(year=2017, month=2, day=3, hour=15, minute=5) From 08659d80ae46aad5297c235b9449b75afba6c3cb Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 10:05:15 +0200 Subject: [PATCH 42/69] deps: updates pipfile.lock --- Pipfile.lock | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index e570935..0f002a0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7b58fd26bb7d2a7799a903c1f4ed1dcbc6541dc145b47b4aadeb0e5e56e98c1f" + "sha256": "85609d2dddc8429567481e65ffe1f49f5df31002d2d8f60bd29c6cd499d4cad4" }, "pipfile-spec": 6, "requires": { @@ -45,7 +45,7 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, "python-dateutil": { @@ -69,7 +69,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "zope.interface": { @@ -218,14 +218,6 @@ "index": "pypi", "version": "==6.0.1" }, - "flake8": { - "hashes": [ - "sha256:124554bfd067e04d891258c0208a764b512ca3a94c8a3c06bea56af539dd74db", - "sha256:b52d27e627676b015340c3b1c72bc9259a6cacc9341712fb8f01ddfaaa2c651a" - ], - "index": "pypi", - "version": "==4.0.0" - }, "idna": { "hashes": [ "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", @@ -241,13 +233,6 @@ ], "version": "==1.1.1" }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, "mypy-extensions": { "hashes": [ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", @@ -294,28 +279,12 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.10.0" }, - "pycodestyle": { - "hashes": [ - "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", - "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.8.0" - }, - "pyflakes": { - "hashes": [ - "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", - "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.0" - }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, "pytest": { @@ -385,7 +354,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomli": { From c5f45125bbc55d4f9b917710546db152289fa4d7 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 19:42:30 +0200 Subject: [PATCH 43/69] ci: fix all black proposals --- .pre-commit-config.yaml | 2 +- icalevents/icaldownload.py | 21 +++++--- icalevents/icalevents.py | 23 +++++--- icalevents/icalparser.py | 107 ++++++++++++++++++++----------------- 4 files changed, 88 insertions(+), 65 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0af632a..3369728 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: rev: 21.9b0 hooks: - id: black - args: ['--check'] + args: ['--check', 'icalevents'] - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 hooks: diff --git a/icalevents/icaldownload.py b/icalevents/icaldownload.py index a7fe21f..0a215b2 100644 --- a/icalevents/icaldownload.py +++ b/icalevents/icaldownload.py @@ -12,7 +12,7 @@ def apple_data_fix(content): :param content: content to fix :return: fixed content """ - return content.replace('TZOFFSETFROM:+5328', 'TZOFFSETFROM:+0053') + return content.replace("TZOFFSETFROM:+5328", "TZOFFSETFROM:+0053") def apple_url_fix(url): @@ -23,7 +23,7 @@ def apple_url_fix(url): :return: fixed URL """ if url.startswith("webcal://"): - url = url.replace('webcal://', 'http://', 1) + url = url.replace("webcal://", "http://", 1) return url @@ -31,17 +31,22 @@ class ICalDownload: """ Downloads or reads and decodes iCal sources. """ - def __init__(self, http=None, encoding='utf-8'): + + def __init__(self, http=None, encoding="utf-8"): # Get logger logger = logging.getLogger() # default http connection to use if http is None: try: - http = Http('.cache') + http = Http(".cache") except (PermissionError, OSError) as e: # Cache disabled if no write permission in working directory - logger.warning(("Caching is disabled due to a read-only working directory: {}").format(e)) + logger.warning( + ( + "Caching is disabled due to a read-only working directory: {}" + ).format(e) + ) http = Http() self.http = http @@ -61,7 +66,7 @@ def data_from_url(self, url, apple_fix=False): _, content = self.http.request(url) if not content: - raise ConnectionError('Could not get data from %s!' % url) + raise ConnectionError("Could not get data from %s!" % url) return self.decode(content, apple_fix=apple_fix) @@ -73,7 +78,7 @@ def data_from_file(self, file, apple_fix=False): :param apple_fix: fix wrong Apple tzdata in iCal :return: decoded (and fixed) iCal data """ - with open(file, mode='rb') as f: + with open(file, mode="rb") as f: content = f.read() if not content: @@ -96,7 +101,7 @@ def decode(self, content, apple_fix=False): :return: decoded (and fixed) content """ content = content.decode(self.encoding) - content = content.replace('\r', '') + content = content.replace("\r", "") if apple_fix: content = apple_data_fix(content) diff --git a/icalevents/icalevents.py b/icalevents/icalevents.py index f0d8d8f..bf6082d 100644 --- a/icalevents/icalevents.py +++ b/icalevents/icalevents.py @@ -44,8 +44,7 @@ def events( content = ical_download.data_from_file(file, apple_fix=fix_apple) if not content and string_content: - content = ical_download.data_from_string( - string_content, apple_fix=fix_apple) + content = ical_download.data_from_string(string_content, apple_fix=fix_apple) found_events += parse_events(content, start=start, end=end) @@ -67,15 +66,22 @@ def request_data(key, url, file, string_content, start, end, fix_apple): data = [] try: - data += events(url=url, file=file, string_content=string_content, - start=start, end=end, fix_apple=fix_apple) + data += events( + url=url, + file=file, + string_content=string_content, + start=start, + end=end, + fix_apple=fix_apple, + ) finally: update_events(key, data) request_finished(key) -def events_async(key, url=None, file=None, start=None, string_content=None, - end=None, fix_apple=False): +def events_async( + key, url=None, file=None, start=None, string_content=None, end=None, fix_apple=False +): """ Trigger an asynchronous data request. @@ -87,7 +93,10 @@ def events_async(key, url=None, file=None, start=None, string_content=None, :param end: end date :param fix_apple: fix known Apple iCal issues """ - t = Thread(target=request_data, args=(key, url, file, string_content, start, end, fix_apple)) + t = Thread( + target=request_data, + args=(key, url, file, string_content, start, end, fix_apple), + ) with event_lock: if key not in threads: diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 46bcd79..eaa4eb4 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -66,7 +66,9 @@ def __lt__(self, other): :return: True if start of this event is smaller than other """ if not other or not isinstance(other, Event): - raise ValueError('Only events can be compared with each other! Other is %s' % type(other)) + raise ValueError( + "Only events can be compared with each other! Other is %s" % type(other) + ) else: return self.start < other.start @@ -124,7 +126,7 @@ def copy_to(self, new_start=None, uid=None): if self.end: duration = self.end - self.start - ne.end = (new_start + duration) + ne.end = new_start + duration ne.all_day = self.all_day ne.recurring = self.recurring @@ -146,7 +148,7 @@ def encode(value: Optional[vText]) -> Optional[str]: try: return str(value) except UnicodeEncodeError: - return str(value.encode('utf-8')) + return str(value.encode("utf-8")) def create_event(component, tz=UTC): @@ -160,56 +162,56 @@ def create_event(component, tz=UTC): event = Event() - event.start = normalize(component.get('dtstart').dt, tz=tz) + event.start = normalize(component.get("dtstart").dt, tz=tz) - if component.get('dtend'): - event.end = normalize(component.get('dtend').dt, tz=tz) - elif component.get('duration'): # compute implicit end as start + duration - event.end = event.start + component.get('duration').dt - else: # compute implicit end as start + 0 + if component.get("dtend"): + event.end = normalize(component.get("dtend").dt, tz=tz) + elif component.get("duration"): # compute implicit end as start + duration + event.end = event.start + component.get("duration").dt + else: # compute implicit end as start + 0 event.end = event.start - event.summary = encode(component.get('summary')) - event.description = encode(component.get('description')) - event.all_day = type(component.get('dtstart').dt) is date - if component.get('rrule'): + event.summary = encode(component.get("summary")) + event.description = encode(component.get("description")) + event.all_day = type(component.get("dtstart").dt) is date + if component.get("rrule"): event.recurring = True - event.location = encode(component.get('location')) + event.location = encode(component.get("location")) - if component.get('attendee'): - event.attendee = component.get('attendee') + if component.get("attendee"): + event.attendee = component.get("attendee") if type(event.attendee) is list: temp = [] for a in event.attendee: - temp.append(a.encode('utf-8').decode('ascii')) + temp.append(a.encode("utf-8").decode("ascii")) event.attendee = temp else: - event.attendee = event.attendee.encode('utf-8').decode('ascii') + event.attendee = event.attendee.encode("utf-8").decode("ascii") else: event.attendee = str(None) - if component.get('uid'): - event.uid = component.get('uid').encode('utf-8').decode('ascii') + if component.get("uid"): + event.uid = component.get("uid").encode("utf-8").decode("ascii") - if component.get('organizer'): - event.organizer = component.get('organizer').encode('utf-8').decode('ascii') + if component.get("organizer"): + event.organizer = component.get("organizer").encode("utf-8").decode("ascii") else: event.organizer = str(None) - if component.get('class'): - event_class = component.get('class') - event.private = event_class == 'PRIVATE' or event_class == 'CONFIDENTIAL' + if component.get("class"): + event_class = component.get("class") + event.private = event_class == "PRIVATE" or event_class == "CONFIDENTIAL" - if component.get('created'): - event.created = normalize(component.get('created').dt, tz) + if component.get("created"): + event.created = normalize(component.get("created").dt, tz) - if component.get('last-modified'): - event.last_modified = normalize(component.get('last-modified').dt, tz) + if component.get("last-modified"): + event.last_modified = normalize(component.get("last-modified").dt, tz) elif event.created: event.last_modified = event.created - if component.get('sequence'): - event.sequence = component.get('sequence') + if component.get("sequence"): + event.sequence = component.get("sequence") if component.get("categories"): categories = component.get("categories").cats @@ -261,14 +263,14 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): end = start + default_span if not content: - raise ValueError('Content is invalid!') + raise ValueError("Content is invalid!") calendar = Calendar.from_ical(content) # Keep track of the timezones defined in the calendar timezones = {} - for c in calendar.walk('VTIMEZONE'): - name = str(c['TZID']) + for c in calendar.walk("VTIMEZONE"): + name = str(c["TZID"]) try: timezones[name] = c.to_tz() except IndexError: @@ -296,14 +298,14 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): if component.name == "VEVENT": e = create_event(component, cal_tz) - if 'EXDATE' in component: + 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'] + if isinstance(component["EXDATE"], list): + exlist = component["EXDATE"] else: - exlist.append(component['EXDATE']) + exlist.append(component["EXDATE"]) for ex in exlist: exdate = ex.to_ical().decode("UTF-8") exceptions[exdate[0:8]] = exdate @@ -375,7 +377,9 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Recompute the start time in the current timezone *on* the # date of *this* occurrence. This handles the case where the # recurrence has crossed over the daylight savings time boundary. - naive = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) + naive = datetime( + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second + ) dtstart = start_tz.localize(naive) ecopy = e.copy_to(dtstart, e.uid) @@ -387,7 +391,11 @@ 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) + exdate = "%04d%02d%02d" % ( + ecopy.start.year, + ecopy.start.month, + ecopy.start.day, + ) if exdate not in exceptions: found.append(ecopy) elif e.end >= start and e.start <= end: @@ -407,22 +415,23 @@ def parse_rrule(component, tz=UTC): :param tz: timezone for DST handling :return: extracted rrule or rruleset """ - if component.get('rrule'): + if component.get("rrule"): # component['rrule'] can be both a scalar and a list - rrules = component['rrule'] + rrules = component["rrule"] if not isinstance(rrules, list): rrules = [rrules] # If dtstart is a datetime, make sure it's in a timezone. - rdtstart = component['dtstart'].dt + rdtstart = component["dtstart"].dt if type(rdtstart) is datetime: rdtstart = normalize(rdtstart, tz=tz) # Parse the rrules, might return a rruleset instance, instead of rrule - rule = rrulestr('\n'.join(x.to_ical().decode() for x in rrules), - dtstart=rdtstart) + rule = rrulestr( + "\n".join(x.to_ical().decode() for x in rrules), dtstart=rdtstart + ) - if component.get('exdate'): + if component.get("exdate"): # Make sure, to work with a rruleset if isinstance(rule, rrule): rules = rruleset() @@ -438,7 +447,7 @@ def parse_rrule(component, tz=UTC): # You really want an rrule for a component without rrule? Here you are. else: rule = rruleset() - rule.rdate(normalize(component['dtstart'].dt, tz=tz)) + rule.rdate(normalize(component["dtstart"].dt, tz=tz)) return rule @@ -452,9 +461,9 @@ def extract_exdates(component): """ dates = [] - exd_prop = component.get('exdate') + exd_prop = component.get("exdate") if exd_prop: - if isinstance(exd_prop, list): # In case there is more than one exdate property + if isinstance(exd_prop, list): # In case there is more than one exdate property for exd_list in exd_prop: dates.extend(normalize(exd.dt) for exd in exd_list.dts) elif isinstance(exd_prop, vDDDLists): From 9df25ba5602bd0a2382772492ce4703a78f2b2a6 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 19:45:16 +0200 Subject: [PATCH 44/69] ci: use pipenv to run shell --- .github/workflows/python-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index d180b71..69af727 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -26,12 +26,12 @@ jobs: pipenv install --dev --deploy - name: Lint with black and flake run: | - black --check icalevents - flake8 . --count --exit-zero + pipenv run black --check icalevents + pipenv run flake8 . --count --exit-zero - name: Test with pytest run: | - coverage run test.py - coverage xml + pipenv run coverage run test.py + pipenv run coverage xml - name: 'Upload coverage to Codecov' uses: codecov/codecov-action@v2 with: From ffeef918d92b53094181be7595ef5a81478abd6e Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 19:49:07 +0200 Subject: [PATCH 45/69] ci: add missing dep --- Pipfile | 1 + Pipfile.lock | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index eabc0a1..6fa62a4 100644 --- a/Pipfile +++ b/Pipfile @@ -15,6 +15,7 @@ coverage = "==6.0.1" codecov = "==2.1.12" pytest = "==6.2.5" black = "===21.9b0" +flake8 = "===3.7.9" [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 0f002a0..d8ceab4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "85609d2dddc8429567481e65ffe1f49f5df31002d2d8f60bd29c6cd499d4cad4" + "sha256": "75fa47a603ce4c5f14f1a98b7654b147f1e25197409d4b0e5c0ded351aae84c6" }, "pipfile-spec": 6, "requires": { @@ -218,6 +218,22 @@ "index": "pypi", "version": "==6.0.1" }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "markers": "python_version >= '2.7'", + "version": "==0.3" + }, + "flake8": { + "hashes": [ + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + ], + "index": "pypi", + "version": "===3.7.9" + }, "idna": { "hashes": [ "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", @@ -233,6 +249,13 @@ ], "version": "==1.1.1" }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, "mypy-extensions": { "hashes": [ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", @@ -279,6 +302,22 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.10.0" }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.1.1" + }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", From ff17a9ea675f26fe1340db1fcc813beb9683ab36 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 19:58:18 +0200 Subject: [PATCH 46/69] ci: only run linting in pre-commit --- .github/workflows/python-test.yml | 4 - .pre-commit-config.yaml | 1 - docs/conf.py | 25 +++---- main.py | 6 +- setup.py | 52 ++++++------- test/test_icaldownload.py | 9 ++- test/test_icalevents.py | 120 +++++++++++++++++++++++------- test/test_icalparser.py | 35 ++++++--- 8 files changed, 165 insertions(+), 87 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 69af727..a2f3ce6 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -24,10 +24,6 @@ jobs: python -m pip install --upgrade pip pip install pipenv==v2021.5.29 pipenv install --dev --deploy - - name: Lint with black and flake - run: | - pipenv run black --check icalevents - pipenv run flake8 . --count --exit-zero - name: Test with pytest run: | pipenv run coverage run test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3369728..2306bd7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,6 @@ repos: rev: 21.9b0 hooks: - id: black - args: ['--check', 'icalevents'] - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 hooks: diff --git a/docs/conf.py b/docs/conf.py index f2b1d94..032a499 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,17 +12,18 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = 'iCalEvents' -copyright = '2021, Thomas Irgang' -author = 'Thomas Irgang' +project = "iCalEvents" +copyright = "2021, Thomas Irgang" +author = "Thomas Irgang" # The full version, including alpha/beta/rc tags -release = '0.1.25' +release = "0.1.25" # -- General configuration --------------------------------------------------- @@ -30,17 +31,15 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.autodoc' -] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -48,14 +47,14 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" html_theme_options = { # Disable showing the sidebar. Defaults to 'false' - 'nosidebar': True, + "nosidebar": True, } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] diff --git a/main.py b/main.py index 26c9bd3..50099d7 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ from icalevents.icalevents import events_async, latest_events, all_done from time import sleep -if __name__ == '__main__': +if __name__ == "__main__": keys = [] - with open('calendars.txt', mode='r', encoding='utf-8') as f: + with open("calendars.txt", mode="r", encoding="utf-8") as f: counter = 1 while True: @@ -17,7 +17,7 @@ url = url.strip() fix_apple = False - if name == 'icloud': + if name == "icloud": fix_apple = True key = "req_%d" % counter diff --git a/setup.py b/setup.py index e842472..2bfa3db 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 from setuptools import setup -version = '0.1.25' +version = "0.1.25" setup( - name = 'icalevents', - packages = ['icalevents'], + name="icalevents", + packages=["icalevents"], install_requires=[ "httplib2", "icalendar", @@ -13,27 +13,29 @@ "datetime", ], version=version, - description = 'iCal downloader and parser', - author = 'Thomas Irgang', - author_email = 'thomas@irgang-la.de', - url = 'https://github.com/jazzband/icalevents', - download_url = 'https://github.com/jazzband/icalevents/archive/v' + version + '.tar.gz', - keywords = ['iCal'], - classifiers = [ - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Development Status :: 4 - Beta', - 'Environment :: Other Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Operating System :: OS Independent', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - long_description = """\ + description="iCal downloader and parser", + author="Thomas Irgang", + author_email="thomas@irgang-la.de", + url="https://github.com/jazzband/icalevents", + download_url="https://github.com/jazzband/icalevents/archive/v" + + version + + ".tar.gz", + keywords=["iCal"], + classifiers=[ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + long_description="""\ iCal download, parse and query tool ------------------------------------- @@ -44,5 +46,5 @@ This version requires Python 3 or later. -""" +""", ) diff --git a/test/test_icaldownload.py b/test/test_icaldownload.py index 908a47f..e0831f6 100644 --- a/test/test_icaldownload.py +++ b/test/test_icaldownload.py @@ -6,7 +6,6 @@ class ICalDownloadTests(unittest.TestCase): - def test_apple_data_fix(self): data = """ DTSTART:18831118T120702 @@ -50,7 +49,7 @@ def test_data_from_file_google(self): expected = None - with open(result, mode='r', encoding='utf-8') as f: + with open(result, mode="r", encoding="utf-8") as f: expected = f.read() content = icalevents.icaldownload.ICalDownload().data_from_file(file) @@ -63,10 +62,12 @@ def test_data_from_file_apple(self): expected = None - with open(result, mode='r', encoding='utf-8') as f: + with open(result, mode="r", encoding="utf-8") as f: expected = f.read() - content = icalevents.icaldownload.ICalDownload().data_from_file(file, apple_fix=True) + content = icalevents.icaldownload.ICalDownload().data_from_file( + file, apple_fix=True + ) self.assertEqual(expected, content, "content form iCal file, google format") diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 64c2752..53fbe22 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -62,11 +62,19 @@ def test_events_recurring(self): e1 = evs[1] self.assertEqual(e1.start.hour, 10, "check time with DST") - self.assertEqual(e1.start.tzinfo.utcoffset(e1.start), timedelta(seconds=7200), "check UTC offset with DST") + self.assertEqual( + e1.start.tzinfo.utcoffset(e1.start), + timedelta(seconds=7200), + "check UTC offset with DST", + ) e2 = evs[2] self.assertEqual(e2.start.hour, 10, "check time without DST") - self.assertEqual(e2.start.tzinfo.utcoffset(e2.start), timedelta(seconds=3600), "check UTC offset without DST") + self.assertEqual( + e2.start.tzinfo.utcoffset(e2.start), + timedelta(seconds=3600), + "check UTC offset without DST", + ) self.assertEqual(e2.start.day, 5, "Check observance of exdate.") @@ -94,18 +102,27 @@ def test_events_all_day_recurring(self): self.assertEqual(len(event_set), 1) self.assertEqual(ev.summary, "Recurring All-day Event") self.assertEqual(ev.description, "All-day event recurring on tuesday each week") - self.assertTrue(ev.all_day, "Recurring All-day Event's first instance is an all-day event") + self.assertTrue( + ev.all_day, "Recurring All-day Event's first instance is an all-day event" + ) start_2nd_instance = date(2018, 11, 6) end_2nd_instance = date(2018, 11, 7) - event_set2 = icalevents.events(url=None, file=ical, start=start_2nd_instance, end=end_2nd_instance) + event_set2 = icalevents.events( + url=None, file=ical, start=start_2nd_instance, end=end_2nd_instance + ) ev_2 = event_set2[0] self.assertEqual(len(event_set2), 1) self.assertEqual(ev_2.summary, "Recurring All-day Event") - self.assertEqual(ev_2.description, "All-day event recurring on tuesday each week") - self.assertTrue(ev_2.all_day, "Recurring All-day Event's second instance is an all-day event") + self.assertEqual( + ev_2.description, "All-day event recurring on tuesday each week" + ) + self.assertTrue( + ev_2.all_day, + "Recurring All-day Event's second instance is an all-day event", + ) def test_events_rrule_until(self): ical = "test/test_data/rrule_until.ics" @@ -137,7 +154,9 @@ def test_event_recurring_attribute(self): end = date(2017, 7, 13) ev = icalevents.events(url=None, file=ical, start=start, end=end)[0] - self.assertEqual(ev.recurring, False, "check recurring=False for non recurring event") + self.assertEqual( + ev.recurring, False, "check recurring=False for non recurring event" + ) ical = "test/test_data/recurring.ics" start = date(2018, 10, 15) @@ -147,8 +166,12 @@ def test_event_recurring_attribute(self): e1 = evs[1] e2 = evs[2] - self.assertEqual(e1.recurring, True, "check recurring=True for recurring event (1)") - self.assertEqual(e2.recurring, True, "check recurring=True for recurring event (2)") + self.assertEqual( + e1.recurring, True, "check recurring=True for recurring event (1)" + ) + self.assertEqual( + e2.recurring, True, "check recurring=True for recurring event (2)" + ) def test_events_async_url(self): url = "https://raw.githubusercontent.com/jazzband/icalevents/master/test/test_data/basic.ics" @@ -182,7 +205,15 @@ def test_request_data(self): end = date(2017, 5, 19) key = "basic" - icalevents.request_data(key, url=None, file=ical, string_content=None, start=start, end=end, fix_apple=False) + icalevents.request_data( + key, + url=None, + file=ical, + string_content=None, + start=start, + end=end, + fix_apple=False, + ) self.assertTrue(icalevents.all_done(key), "request is finished") self.assertEqual(len(icalevents.latest_events(key)), 2, "two events are found") @@ -190,15 +221,22 @@ def test_request_data(self): def test_string_data(self): ical = "test/test_data/basic.ics" - with open(ical, mode='rb') as f: + with open(ical, mode="rb") as f: string_content = f.read() start = date(2017, 5, 18) end = date(2017, 5, 19) key = "basic" - icalevents.request_data(key, url=None, file=None, string_content=string_content, start=start, end=end, - fix_apple=False) + icalevents.request_data( + key, + url=None, + file=None, + string_content=string_content, + start=start, + end=end, + fix_apple=False, + ) self.assertTrue(icalevents.all_done(key), "request is finished") self.assertEqual(len(icalevents.latest_events(key)), 2, "two events are found") @@ -213,18 +251,36 @@ def test_event_str(self): evs = icalevents.events(file=ical, start=start, end=end) e1 = evs[0] - self.assertIsNotNone(search(r"ended", str(e1.copy_to(n - relativedelta(days=5) + m))), "stringify past event") - self.assertIsNotNone(search(r"today", str(e1.copy_to(n - relativedelta(days=1) + m))), - "stringify ongoing event") - self.assertIsNotNone(search(r"days left", str(e1.copy_to(n + relativedelta(days=3) + m))), - "stringify future event") + self.assertIsNotNone( + search(r"ended", str(e1.copy_to(n - relativedelta(days=5) + m))), + "stringify past event", + ) + self.assertIsNotNone( + search(r"today", str(e1.copy_to(n - relativedelta(days=1) + m))), + "stringify ongoing event", + ) + self.assertIsNotNone( + search(r"days left", str(e1.copy_to(n + relativedelta(days=3) + m))), + "stringify future event", + ) e2 = evs[1] - self.assertIsNotNone(search(r"ended", str(e2.copy_to(n - relativedelta(hours=5)))), "stringify past event") - self.assertIsNotNone(search(r"now", str(e2.copy_to(n - relativedelta(hours=1)))), "stringify ongoing event") - self.assertIsNotNone(search(r"hours left", str(e2.copy_to(n + relativedelta(hours=3)))), - "stringify future event") - self.assertIsNotNone(search(r"days left", str(e2.copy_to(n + relativedelta(days=3)))), "stringify future event") + self.assertIsNotNone( + search(r"ended", str(e2.copy_to(n - relativedelta(hours=5)))), + "stringify past event", + ) + self.assertIsNotNone( + search(r"now", str(e2.copy_to(n - relativedelta(hours=1)))), + "stringify ongoing event", + ) + self.assertIsNotNone( + search(r"hours left", str(e2.copy_to(n + relativedelta(hours=3)))), + "stringify future event", + ) + self.assertIsNotNone( + search(r"days left", str(e2.copy_to(n + relativedelta(days=3)))), + "stringify future event", + ) def test_events_no_description(self): ical = "test/test_data/no_description.ics" @@ -245,10 +301,14 @@ def test_event_created_last_modified(self): events = icalevents.events(url=None, file=ical, start=start, end=end) self.assertEqual(events[0].created, datetime(2017, 1, 3, 7, 4, 1, tzinfo=UTC)) - self.assertEqual(events[0].last_modified, datetime(2017, 7, 11, 14, 0, 50, tzinfo=UTC)) + self.assertEqual( + events[0].last_modified, datetime(2017, 7, 11, 14, 0, 50, tzinfo=UTC) + ) self.assertEqual(events[1].created, datetime(2017, 1, 4, 8, 4, 1, tzinfo=UTC)) - self.assertEqual(events[1].last_modified, datetime(2017, 1, 4, 8, 4, 1, tzinfo=UTC)) + self.assertEqual( + events[1].last_modified, datetime(2017, 1, 4, 8, 4, 1, tzinfo=UTC) + ) self.assertEqual(events[2].created, None) self.assertEqual(events[2].last_modified, None) @@ -258,5 +318,11 @@ def test_event_categories(self): start = date(2020, 11, 10) end = date(2020, 11, 19) events = icalevents.events(url=None, file=ical, start=start, end=end) - self.assertEqual(events[0].categories, ["In19-S04-IT2403"], "event 1 is not equal") - self.assertEqual(events[1].categories, ["In19-S04-IT2406", "In19-S04-IT2405"], "event 2 is not equal") + self.assertEqual( + events[0].categories, ["In19-S04-IT2403"], "event 1 is not equal" + ) + self.assertEqual( + events[1].categories, + ["In19-S04-IT2406", "In19-S04-IT2405"], + "event 2 is not equal", + ) diff --git a/test/test_icalparser.py b/test/test_icalparser.py index 7d944ba..16ca87f 100644 --- a/test/test_icalparser.py +++ b/test/test_icalparser.py @@ -5,12 +5,15 @@ class ICalParserTests(unittest.TestCase): - def setUp(self): self.eventA = icalevents.icalparser.Event() self.eventA.uid = 1234 - self.eventA.start = datetime(year=2017, month=2, day=3, hour=12, minute=5, tzinfo=UTC) - self.eventA.end = datetime(year=2017, month=2, day=3, hour=15, minute=5, tzinfo=UTC) + self.eventA.start = datetime( + year=2017, month=2, day=3, hour=12, minute=5, tzinfo=UTC + ) + self.eventA.end = datetime( + year=2017, month=2, day=3, hour=15, minute=5, tzinfo=UTC + ) self.eventA.all_day = False self.eventA.summary = "Event A" self.eventA.attendee = "name@example.com" @@ -18,15 +21,19 @@ def setUp(self): self.eventB = icalevents.icalparser.Event() self.eventB.uid = 1234 - self.eventB.start = datetime(year=2017, month=2, day=1, hour=15, minute=5, tzinfo=UTC) - self.eventB.end = datetime(year=2017, month=2, day=1, hour=16, minute=5, tzinfo=UTC) + self.eventB.start = datetime( + year=2017, month=2, day=1, hour=15, minute=5, tzinfo=UTC + ) + self.eventB.end = datetime( + year=2017, month=2, day=1, hour=16, minute=5, tzinfo=UTC + ) self.eventB.all_day = False self.eventB.summary = "Event B" self.eventB.attendee = ["name@example.com", "another@example.com"] self.eventB.organizer = "name@example.com" self.dtA = datetime(2018, 6, 21, 12) - self.dtB = datetime(2018, 6, 21, 12, tzinfo=gettz('Europe/Berlin')) + self.dtB = datetime(2018, 6, 21, 12, tzinfo=gettz("Europe/Berlin")) def test_now(self): n = icalevents.icalparser.now() @@ -47,16 +54,24 @@ def test_event_copy_to(self): self.assertNotEqual(eventC.uid, self.eventA.uid, "new event has new UID") self.assertEqual(eventC.start, new_start, "new event has new start") - self.assertEqual(eventC.end - eventC.start, self.eventA.end - self.eventA.start, "new event has same duration") + self.assertEqual( + eventC.end - eventC.start, + self.eventA.end - self.eventA.start, + "new event has same duration", + ) self.assertEqual(eventC.all_day, False, "new event is no all day event") self.assertEqual(eventC.summary, self.eventA.summary, "copy to: summary") - self.assertEqual(eventC.description, self.eventA.description, "copy to: description") + self.assertEqual( + eventC.description, self.eventA.description, "copy to: description" + ) eventD = eventC.copy_to(uid=new_uid) self.assertEqual(eventD.uid, new_uid, "new event has specified UID") self.assertEqual(eventD.start, eventC.start, "new event has same start") self.assertEqual(eventD.end, eventC.end, "new event has same end") - self.assertEqual(eventD.all_day, eventC.all_day, "new event is no all day event") + self.assertEqual( + eventD.all_day, eventC.all_day, "new event is no all day event" + ) self.assertEqual(eventD.summary, eventC.summary, "copy to: summary") self.assertEqual(eventD.description, eventC.description, "copy to: description") @@ -104,4 +119,4 @@ def test_organizer(self): def test_str(self): self.eventA.start = datetime(year=2017, month=2, day=3, hour=12, minute=5) self.eventA.end = datetime(year=2017, month=2, day=3, hour=15, minute=5) - self.assertEqual('2017-02-03 12:05:00+00:00: Event A (ended)', str(self.eventA)) + self.assertEqual("2017-02-03 12:05:00+00:00: Event A (ended)", str(self.eventA)) From 0c375b589f706c7edeba4240aacdc5df038de8da Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 20:21:21 +0200 Subject: [PATCH 47/69] ci: switch to poetry --- .github/workflows/python-publish.yml | 40 +- .github/workflows/python-test.yml | 8 +- Pipfile | 21 - Pipfile.lock | 424 ----------------- README.md | 2 +- poetry.lock | 660 +++++++++++++++++++++++++++ pyproject.toml | 25 + 7 files changed, 709 insertions(+), 471 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e49f459..dbc6e89 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -14,27 +14,25 @@ on: jobs: deploy: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - - - name: Upload packages to Jazzband - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master - with: - user: jazzband - password: ${{ secrets.JAZZBAND_RELEASE_KEY }} - repository_url: https://jazzband.co/projects/django-formtools/upload + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry==1.1.11 + - name: Build package + run: poetry build + + - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: jazzband + password: ${{ secrets.JAZZBAND_RELEASE_KEY }} + repository_url: https://jazzband.co/projects/django-formtools/upload diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index a2f3ce6..8d09d89 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -22,12 +22,12 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pipenv==v2021.5.29 - pipenv install --dev --deploy + pip install poetry==1.1.11 + poetry install - name: Test with pytest run: | - pipenv run coverage run test.py - pipenv run coverage xml + poetry run coverage run test.py + poetry run coverage xml - name: 'Upload coverage to Codecov' uses: codecov/codecov-action@v2 with: diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 6fa62a4..0000000 --- a/Pipfile +++ /dev/null @@ -1,21 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -httplib2 = "==0.20.1" -icalendar = "==4.0.8" -python-dateutil = "==2.8.2" -pytz = "==2021.3" -DateTime = "==4.3" - -[dev-packages] -coverage = "==6.0.1" -codecov = "==2.1.12" -pytest = "==6.2.5" -black = "===21.9b0" -flake8 = "===3.7.9" - -[requires] -python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index d8ceab4..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,424 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "75fa47a603ce4c5f14f1a98b7654b147f1e25197409d4b0e5c0ded351aae84c6" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "datetime": { - "hashes": [ - "sha256:371dba07417b929a4fa685c2f7a3eaa6a62d60c02947831f97d4df9a9e70dfd0", - "sha256:5cef605bab8259ff61281762cdf3290e459fbf0b4719951d5fab967d5f2ea0ea" - ], - "index": "pypi", - "version": "==4.3" - }, - "httplib2": { - "hashes": [ - "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327", - "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185" - ], - "index": "pypi", - "version": "==0.20.1" - }, - "icalendar": { - "hashes": [ - "sha256:7508a92b4e36049777640b0ae393e7219a16488d852841a0e57b44fe51d9f848", - "sha256:825209edeb8f3067cf31f0712b0fc9507b1ded270165f98f3176d6b50e8c7b78" - ], - "index": "pypi", - "version": "==4.0.8" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.4.7" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "index": "pypi", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" - ], - "index": "pypi", - "version": "==2021.3" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" - }, - "zope.interface": { - "hashes": [ - "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", - "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", - "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", - "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", - "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", - "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", - "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", - "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", - "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", - "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", - "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", - "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", - "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", - "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", - "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", - "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", - "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", - "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", - "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", - "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", - "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", - "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", - "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", - "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", - "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", - "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", - "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", - "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", - "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", - "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", - "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", - "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", - "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", - "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", - "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", - "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", - "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", - "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", - "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", - "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", - "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", - "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", - "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", - "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", - "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", - "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", - "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", - "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", - "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", - "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", - "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==5.4.0" - } - }, - "develop": { - "attrs": { - "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" - }, - "black": { - "hashes": [ - "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115", - "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91" - ], - "index": "pypi", - "version": "===21.9b0" - }, - "certifi": { - "hashes": [ - "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", - "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" - ], - "version": "==2021.10.8" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6", - "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f" - ], - "markers": "python_version >= '3'", - "version": "==2.0.6" - }, - "click": { - "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" - }, - "codecov": { - "hashes": [ - "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47", - "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635", - "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1" - ], - "index": "pypi", - "version": "==2.1.12" - }, - "coverage": { - "hashes": [ - "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26", - "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666", - "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee", - "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345", - "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5", - "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad", - "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f", - "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854", - "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32", - "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7", - "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12", - "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec", - "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc", - "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5", - "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724", - "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7", - "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d", - "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194", - "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e", - "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a", - "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b", - "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a", - "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e", - "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22", - "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b", - "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793", - "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397", - "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815", - "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827", - "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb", - "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43", - "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf", - "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16" - ], - "index": "pypi", - "version": "==6.0.1" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "markers": "python_version >= '2.7'", - "version": "==0.3" - }, - "flake8": { - "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" - ], - "index": "pypi", - "version": "===3.7.9" - }, - "idna": { - "hashes": [ - "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", - "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" - ], - "markers": "python_version >= '3'", - "version": "==3.2" - }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "version": "==0.4.3" - }, - "packaging": { - "hashes": [ - "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", - "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" - ], - "markers": "python_version >= '3.6'", - "version": "==21.0" - }, - "pathspec": { - "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" - ], - "version": "==0.9.0" - }, - "platformdirs": { - "hashes": [ - "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", - "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" - ], - "markers": "python_version >= '3.6'", - "version": "==2.4.0" - }, - "pluggy": { - "hashes": [ - "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", - "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" - ], - "markers": "python_version >= '3.6'", - "version": "==1.0.0" - }, - "py": { - "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.10.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.1.1" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.4.7" - }, - "pytest": { - "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" - ], - "index": "pypi", - "version": "==6.2.5" - }, - "regex": { - "hashes": [ - "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f", - "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3", - "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838", - "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01", - "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f", - "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a", - "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432", - "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f", - "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc", - "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9", - "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152", - "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493", - "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361", - "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61", - "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593", - "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354", - "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee", - "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3", - "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741", - "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b", - "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb", - "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca", - "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3", - "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072", - "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d", - "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b", - "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf", - "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd", - "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e", - "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700", - "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59", - "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991", - "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287", - "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7", - "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1", - "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e", - "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92", - "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820", - "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4", - "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2", - "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f" - ], - "version": "==2021.10.8" - }, - "requests": { - "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.26.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==0.10.2" - }, - "tomli": { - "hashes": [ - "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", - "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" - ], - "markers": "python_version >= '3.6'", - "version": "==1.2.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", - "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", - "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" - ], - "version": "==3.10.0.2" - }, - "urllib3": { - "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" - } - } -} diff --git a/README.md b/README.md index d1503cc..5b0816a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ es = events() # Contributing -You will need [pipenv](https://pipenv.pypa.io/en/latest/) and [pre-commit](https://pre-commit.com/index.html) installed and than run. +You will need [poetry](https://github.com/python-poetry/poetry) and [pre-commit](https://pre-commit.com/index.html) installed and than run. ```bash pre-commit install diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..4d42c1a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,660 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "21.9b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +regex = ">=2020.1.8" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.6" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "codecov" +version = "2.1.12" +description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +coverage = "*" +requests = ">=2.7.9" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "6.0.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "datetime" +version = "4.3" +description = "This package provides a DateTime data type, as known from Zope. Unless you need to communicate with Zope APIs, you're probably better off using Python's built-in datetime module." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pytz = "*" +"zope.interface" = "*" + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "httplib2" +version = "0.20.1" +description = "A comprehensive HTTP client library." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.4.2,<3" + +[[package]] +name = "icalendar" +version = "4.0.8" +description = "iCalendar parser/generator" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +python-dateutil = "*" +pytz = "*" + +[[package]] +name = "idna" +version = "3.2" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2021.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "regex" +version = "2021.10.8" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "zope.interface" +version = "5.4.0" +description = "Interfaces for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +docs = ["sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "df78c6613912c3547a62e5c37d8cfa6c5e4e0866437dd117b6747ed71ea678e2" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, + {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, + {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +codecov = [ + {file = "codecov-2.1.12-py2.py3-none-any.whl", hash = "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47"}, + {file = "codecov-2.1.12-py3.8.egg", hash = "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635"}, + {file = "codecov-2.1.12.tar.gz", hash = "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec"}, + {file = "coverage-6.0.1-cp310-cp310-win32.whl", hash = "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194"}, + {file = "coverage-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f"}, + {file = "coverage-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b"}, + {file = "coverage-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a"}, + {file = "coverage-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb"}, + {file = "coverage-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666"}, + {file = "coverage-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b"}, + {file = "coverage-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724"}, + {file = "coverage-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827"}, + {file = "coverage-6.0.1-cp38-cp38-win32.whl", hash = "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee"}, + {file = "coverage-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12"}, + {file = "coverage-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf"}, + {file = "coverage-6.0.1-cp39-cp39-win32.whl", hash = "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad"}, + {file = "coverage-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815"}, + {file = "coverage-6.0.1-pp36-none-any.whl", hash = "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5"}, + {file = "coverage-6.0.1-pp37-none-any.whl", hash = "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26"}, + {file = "coverage-6.0.1.tar.gz", hash = "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854"}, +] +datetime = [ + {file = "DateTime-4.3-py2.py3-none-any.whl", hash = "sha256:371dba07417b929a4fa685c2f7a3eaa6a62d60c02947831f97d4df9a9e70dfd0"}, + {file = "DateTime-4.3.tar.gz", hash = "sha256:5cef605bab8259ff61281762cdf3290e459fbf0b4719951d5fab967d5f2ea0ea"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +httplib2 = [ + {file = "httplib2-0.20.1-py3-none-any.whl", hash = "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185"}, + {file = "httplib2-0.20.1.tar.gz", hash = "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327"}, +] +icalendar = [ + {file = "icalendar-4.0.8-py2.py3-none-any.whl", hash = "sha256:825209edeb8f3067cf31f0712b0fc9507b1ded270165f98f3176d6b50e8c7b78"}, + {file = "icalendar-4.0.8.tar.gz", hash = "sha256:7508a92b4e36049777640b0ae393e7219a16488d852841a0e57b44fe51d9f848"}, +] +idna = [ + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, +] +regex = [ + {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, + {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, + {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, + {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, + {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, + {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, + {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, + {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, + {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, + {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, + {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, + {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, + {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, + {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, + {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, + {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, +] +requests = [ + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, + {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] +urllib3 = [ + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, +] +"zope.interface" = [ + {file = "zope.interface-5.4.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7"}, + {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021"}, + {file = "zope.interface-5.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192"}, + {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a"}, + {file = "zope.interface-5.4.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531"}, + {file = "zope.interface-5.4.0-cp27-cp27m-win32.whl", hash = "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325"}, + {file = "zope.interface-5.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155"}, + {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"}, + {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959"}, + {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e"}, + {file = "zope.interface-5.4.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c"}, + {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702"}, + {file = "zope.interface-5.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f"}, + {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05"}, + {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004"}, + {file = "zope.interface-5.4.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117"}, + {file = "zope.interface-5.4.0-cp35-cp35m-win32.whl", hash = "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8"}, + {file = "zope.interface-5.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63"}, + {file = "zope.interface-5.4.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f"}, + {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920"}, + {file = "zope.interface-5.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46"}, + {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc"}, + {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9"}, + {file = "zope.interface-5.4.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2"}, + {file = "zope.interface-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78"}, + {file = "zope.interface-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1"}, + {file = "zope.interface-5.4.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e"}, + {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b"}, + {file = "zope.interface-5.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f"}, + {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d"}, + {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8"}, + {file = "zope.interface-5.4.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf"}, + {file = "zope.interface-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7"}, + {file = "zope.interface-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94"}, + {file = "zope.interface-5.4.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3"}, + {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e"}, + {file = "zope.interface-5.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7"}, + {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120"}, + {file = "zope.interface-5.4.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48"}, + {file = "zope.interface-5.4.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4"}, + {file = "zope.interface-5.4.0-cp38-cp38-win32.whl", hash = "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb"}, + {file = "zope.interface-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54"}, + {file = "zope.interface-5.4.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4"}, + {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d"}, + {file = "zope.interface-5.4.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83"}, + {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25"}, + {file = "zope.interface-5.4.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1"}, + {file = "zope.interface-5.4.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c"}, + {file = "zope.interface-5.4.0-cp39-cp39-win32.whl", hash = "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e"}, + {file = "zope.interface-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09"}, + {file = "zope.interface-5.4.0.tar.gz", hash = "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6e27ff5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[tool.poetry] +name = "icalevents" +version = "0.1.26" +description = "Simple Python 3 library to download, parse and query iCal sources." +authors = ["Martin Eigenmann ", "Thomas Irgang ", "Alexander Hultnér"] +license = "MIT Licence" + +[tool.poetry.dependencies] +python = "^3.9" +httplib2 = "==0.20.1" +icalendar = "==4.0.8" +python-dateutil = "==2.8.2" +pytz = "==2021.3" +DateTime = "==4.3" + +[tool.poetry.dev-dependencies] +coverage = "==6.0.1" +codecov = "==2.1.12" +pytest = "==6.2.5" +black = "===21.9b0" +flake8 = "===3.7.9" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From adc7d674bc71a1b03fbd30c6a59128bd0787223e Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Mon, 11 Oct 2021 20:25:57 +0200 Subject: [PATCH 48/69] doc: add hultner email/ --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e27ff5..f0920e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "icalevents" version = "0.1.26" description = "Simple Python 3 library to download, parse and query iCal sources." -authors = ["Martin Eigenmann ", "Thomas Irgang ", "Alexander Hultnér"] +authors = ["Martin Eigenmann ", "Thomas Irgang ", "Alexander Hultnér "] license = "MIT Licence" [tool.poetry.dependencies] From 29182491c7cfbb406ab711455078fd805ee96323 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 17 Oct 2021 18:30:56 +0200 Subject: [PATCH 49/69] style: apply black styling --- icalevents/icalparser.py | 55 ++++++++++++++++++++++++++-------------- test/test_icalevents.py | 30 +++++++++++++++++----- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 67274ef..950528d 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -205,11 +205,11 @@ def create_event(component, tz=UTC): event_class = component.get("class") event.private = event_class == "PRIVATE" or event_class == "CONFIDENTIAL" - if component.get('class'): - event.transparent = component.get('transp') == 'TRANSPARENT' + if component.get("class"): + event.transparent = component.get("transp") == "TRANSPARENT" - if component.get('created'): - event.created = normalize(component.get('created').dt, tz) + if component.get("created"): + event.created = normalize(component.get("created").dt, tz) if component.get("last-modified"): event.last_modified = normalize(component.get("last-modified").dt, tz) @@ -217,8 +217,8 @@ def create_event(component, tz=UTC): event.last_modified = event.created # sequence can be 0 - test for None instead - if not component.get('sequence') is None: - event.sequence = component.get('sequence') + if not component.get("sequence") is None: + event.sequence = component.get("sequence") if component.get("categories"): categories = component.get("categories").cats @@ -262,11 +262,20 @@ def get_timezone(tz_name): def adjust_timezone(component, dates, tz=None): # Remove timezone if none is present in component - if isinstance(component['dtstart'].dt, date) or component['dtstart'].dt.tzinfo is None: - dates = [date.replace(tzinfo=None) if type(date) is datetime else date for date in dates] + if ( + isinstance(component["dtstart"].dt, date) + or component["dtstart"].dt.tzinfo is None + ): + dates = [ + date.replace(tzinfo=None) if type(date) is datetime else date + for date in dates + ] # Add timezone if one is present in component - if isinstance(component['dtstart'].dt, datetime) and not component['dtstart'].dt.tzinfo is None: + if ( + isinstance(component["dtstart"].dt, datetime) + and not component["dtstart"].dt.tzinfo is None + ): dates = [normalize(date) for date in dates] return dates @@ -297,8 +306,8 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): timezones = {} # Parse non standard timezone name - if 'X-WR-TIMEZONE' in calendar: - x_wr_timezone = str(calendar['X-WR-TIMEZONE']) + if "X-WR-TIMEZONE" in calendar: + x_wr_timezone = str(calendar["X-WR-TIMEZONE"]) timezones[x_wr_timezone] = get_timezone(x_wr_timezone) for c in calendar.walk("VTIMEZONE"): @@ -330,9 +339,11 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): 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 "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 @@ -390,7 +401,9 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): # Recompute the start time in the current timezone *on* the # date of *this* occurrence. This handles the case where the # recurrence has crossed over the daylight savings time boundary. - naive = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) + naive = datetime( + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second + ) dtstart = normalize(naive, tz=start_tz) ecopy = e.copy_to(dtstart, e.uid) @@ -415,8 +428,10 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): 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 + event + for event in found + if e.sequence is None + or not (event.uid, event.start, e.sequence) in recurrence_ids ] @@ -444,8 +459,10 @@ def parse_rrule(component, tz=UTC): # Remove/add timezone to rrule until dates depending on component if type(rdtstart) is date: for index, rru in enumerate(rrules): - if 'UNTIL' in rru: - rrules[index]['UNTIL'] = adjust_timezone(component, rru['UNTIL'], tz) + if "UNTIL" in rru: + rrules[index]["UNTIL"] = adjust_timezone( + component, rru["UNTIL"], tz + ) # Parse the rrules, might return a rruleset instance, instead of rrule rule = rrulestr( diff --git a/test/test_icalevents.py b/test/test_icalevents.py index 3b52d47..f40d723 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -132,8 +132,12 @@ def test_events_rrule_until_all_day_ms(self): evs = icalevents.events(file=ical, start=start, end=end) ev_0 = evs[0] - self.assertEqual(len(evs), 6, "Seven events and one is excluded") # rrule_until_all_day_ms has one exdate (EXDATE;TZID=W. Europe Standard Time:20210430T000000) - self.assertEqual(ev_0.start, datetime(2021, 3, 19, 00, 0, 0, tzinfo=gettz('Europe/Berlin'))) + self.assertEqual( + len(evs), 6, "Seven events and one is excluded" + ) # rrule_until_all_day_ms has one exdate (EXDATE;TZID=W. Europe Standard Time:20210430T000000) + self.assertEqual( + ev_0.start, datetime(2021, 3, 19, 00, 0, 0, tzinfo=gettz("Europe/Berlin")) + ) self.assertEqual(ev_0.recurring, True, "Recurring all day event") self.assertEqual(ev_0.summary, "Away") @@ -146,7 +150,9 @@ def test_events_rrule_until_all_day_google(self): ev_2 = evs[2] self.assertEqual(len(evs), 3) - self.assertEqual(ev_2.start, datetime(2021, 3, 24, 00, 0, 0, tzinfo=gettz('Europe/Zurich'))) + self.assertEqual( + ev_2.start, datetime(2021, 3, 24, 00, 0, 0, tzinfo=gettz("Europe/Zurich")) + ) self.assertEqual(ev_2.all_day, True, "All day event") self.assertEqual(ev_2.summary, "Busy") @@ -344,8 +350,14 @@ def test_event_categories(self): start = date(2020, 11, 10) end = date(2020, 11, 19) events = icalevents.events(url=None, file=ical, start=start, end=end) - self.assertEqual(events[0].categories, ["In19-S04-IT2403"], "event 1 is not equal") - self.assertEqual(events[1].categories, ["In19-S04-IT2406", "In19-S04-IT2405"], "event 2 is not equal") + self.assertEqual( + events[0].categories, ["In19-S04-IT2403"], "event 1 is not equal" + ) + self.assertEqual( + events[1].categories, + ["In19-S04-IT2406", "In19-S04-IT2405"], + "event 2 is not equal", + ) def test_google_timezone(self): ical = "test/test_data/google_tz.ics" @@ -356,7 +368,9 @@ def test_google_timezone(self): e1 = evs[0] self.assertEqual(e1.start.hour, 0, "check start of the day") - self.assertEqual(e1.start.tzinfo, gettz('Europe/Zurich'), "check tz as specified in calendar") + self.assertEqual( + e1.start.tzinfo, gettz("Europe/Zurich"), "check tz as specified in calendar" + ) def test_ms_timezone(self): ical = "test/test_data/ms_tz.ics" @@ -367,7 +381,9 @@ def test_ms_timezone(self): e1 = evs[0] self.assertEqual(e1.start.hour, 0, "check start of the day") - self.assertEqual(e1.start.tzinfo, gettz('Europe/Berlin'), "check tz as specified in calendar") + self.assertEqual( + e1.start.tzinfo, gettz("Europe/Berlin"), "check tz as specified in calendar" + ) def test_recurence_id_ms(self): ical = "test/test_data/recurrenceid_ms.ics" From 5c9a839851b04fd0248ced5ce4f1d2e6eabf7779 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:31:13 +0000 Subject: [PATCH 50/69] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/test_data/cest.ics | 2 +- test/test_data/google_tz.ics | 2 +- test/test_data/ms_tz.ics | 2 +- test/test_data/recurrenceid_google.ics | 2 +- test/test_data/recurrenceid_ms.ics | 2 +- test/test_data/rrule_until_all_day_google.ics | 2 +- test/test_data/rrule_until_all_day_ms.ics | 2 +- test/test_data/transparent.ics | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_data/cest.ics b/test/test_data/cest.ics index f117d8f..8c07e39 100644 --- a/test/test_data/cest.ics +++ b/test/test_data/cest.ics @@ -2259,4 +2259,4 @@ X-MICROSOFT-CDO-INSTTYPE:0 X-MICROSOFT-DONOTFORWARDMEETING:FALSE X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/google_tz.ics b/test/test_data/google_tz.ics index 9cbb9e1..405f842 100644 --- a/test/test_data/google_tz.ics +++ b/test/test_data/google_tz.ics @@ -14,4 +14,4 @@ ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go ogle.com SUMMARY:Busy END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/ms_tz.ics b/test/test_data/ms_tz.ics index 84f6fc9..ab5f861 100644 --- a/test/test_data/ms_tz.ics +++ b/test/test_data/ms_tz.ics @@ -39,4 +39,4 @@ X-MICROSOFT-CDO-INSTTYPE:0 X-MICROSOFT-DONOTFORWARDMEETING:FALSE X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/recurrenceid_google.ics b/test/test_data/recurrenceid_google.ics index 043825c..41d0722 100644 --- a/test/test_data/recurrenceid_google.ics +++ b/test/test_data/recurrenceid_google.ics @@ -45,4 +45,4 @@ ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go RECURRENCE-ID:20210324T110000Z SUMMARY:Busy END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/recurrenceid_ms.ics b/test/test_data/recurrenceid_ms.ics index 6ae0577..6b599c4 100644 --- a/test/test_data/recurrenceid_ms.ics +++ b/test/test_data/recurrenceid_ms.ics @@ -62,4 +62,4 @@ X-MICROSOFT-CDO-INSTTYPE:3 X-MICROSOFT-DONOTFORWARDMEETING:FALSE X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/rrule_until_all_day_google.ics b/test/test_data/rrule_until_all_day_google.ics index 7e7b91e..9ce3845 100644 --- a/test/test_data/rrule_until_all_day_google.ics +++ b/test/test_data/rrule_until_all_day_google.ics @@ -35,4 +35,4 @@ ATTENDEE;X-NUM-GUESTS=0:mailto:hst9ma4s2h5hf2iafmrqffki9o@group.calendar.go RECURRENCE-ID;VALUE=DATE:20210324 SUMMARY:Busy END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/rrule_until_all_day_ms.ics b/test/test_data/rrule_until_all_day_ms.ics index e59d120..6bb938c 100644 --- a/test/test_data/rrule_until_all_day_ms.ics +++ b/test/test_data/rrule_until_all_day_ms.ics @@ -33,4 +33,4 @@ TRANSP:OPAQUE STATUS:CONFIRMED SEQUENCE:0 END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test/test_data/transparent.ics b/test/test_data/transparent.ics index 964e5c2..b9b66d0 100644 --- a/test/test_data/transparent.ics +++ b/test/test_data/transparent.ics @@ -60,4 +60,4 @@ X-MICROSOFT-CDO-INSTTYPE:0 X-MICROSOFT-DONOTFORWARDMEETING:FALSE X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR From b2cf6c8e0842f46f3055586425d119d2a34a6595 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 17 Oct 2021 23:40:11 +0200 Subject: [PATCH 51/69] ci: upload to pypi on release --- .github/workflows/python-publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index dbc6e89..76f6cbe 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -30,7 +30,6 @@ jobs: run: poetry build - name: Upload packages to Jazzband - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: user: jazzband From cf14f3ae98fbfe7d88bbe8ff636f4de3fc8623f7 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 17 Oct 2021 23:50:44 +0200 Subject: [PATCH 52/69] fix: update description for release --- pyproject.toml | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f0920e9..dec433f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ version = "0.1.26" description = "Simple Python 3 library to download, parse and query iCal sources." authors = ["Martin Eigenmann ", "Thomas Irgang ", "Alexander Hultnér "] license = "MIT Licence" +readme = "README.md" [tool.poetry.dependencies] python = "^3.9" diff --git a/setup.py b/setup.py index 2bfa3db..93e2422 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from setuptools import setup -version = "0.1.25" +version = "0.1.26" setup( name="icalevents", From da40e9b6b81e9d654b6883374bf4a4df11793596 Mon Sep 17 00:00:00 2001 From: Martin Eigenmann Date: Sun, 17 Oct 2021 23:55:20 +0200 Subject: [PATCH 53/69] ci: set correct repo url for upload --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 76f6cbe..6c136c0 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -34,4 +34,4 @@ jobs: with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} - repository_url: https://jazzband.co/projects/django-formtools/upload + repository_url: https://jazzband.co/projects/icalevents/upload From f6473c018d8b866465e4c61ace1492bd839bf46a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 16:54:40 +0000 Subject: [PATCH 54/69] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v2.3.0 → v4.0.1](https://github.com/pre-commit/pre-commit-hooks/compare/v2.3.0...v4.0.1) - https://gitlab.com/pycqa/flake8 → https://github.com/PyCQA/flake8 - [github.com/PyCQA/flake8: 3.7.9 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.7.9...4.0.1) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2306bd7..bd9c774 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v4.0.1 hooks: - id: check-yaml - id: end-of-file-fixer @@ -9,7 +9,7 @@ repos: rev: 21.9b0 hooks: - id: black - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.9 + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - id: flake8 From b1dfa916a343bdc2929ce2fdde05b7ee5c02b487 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 21 Oct 2021 13:38:20 +0200 Subject: [PATCH 55/69] Only release on tagging. Also rename the workflow files to better match Jazzband practices. --- .github/workflows/{python-publish.yml => release.yml} | 1 + .github/workflows/{python-test.yml => tests.yml} | 0 2 files changed, 1 insertion(+) rename .github/workflows/{python-publish.yml => release.yml} (93%) rename .github/workflows/{python-test.yml => tests.yml} (100%) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/release.yml similarity index 93% rename from .github/workflows/python-publish.yml rename to .github/workflows/release.yml index 6c136c0..cbfbf59 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,7 @@ jobs: run: poetry build - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: user: jazzband diff --git a/.github/workflows/python-test.yml b/.github/workflows/tests.yml similarity index 100% rename from .github/workflows/python-test.yml rename to .github/workflows/tests.yml From 13b3ca5cbc3d8babc06d044d70f088b381dfb643 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 21 Oct 2021 15:59:31 +0200 Subject: [PATCH 56/69] Push on tag, not just on release. --- .github/workflows/release.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbfbf59..4e20212 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,31 +1,30 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package +name: Release on: - release: - types: [published] + push: + tags: + - '*' jobs: deploy: + if: github.repository == 'jazzband/icalevents' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' + - name: Install dependencies run: | python -m pip install --upgrade pip pip install poetry==1.1.11 + - name: Build package run: poetry build From 06379e0bd9bfc4180b2201e79cfedeca146b2c9c Mon Sep 17 00:00:00 2001 From: jazzband-bot Date: Thu, 21 Oct 2021 14:38:19 +0000 Subject: [PATCH 57/69] Jazzband: Created local 'CODE_OF_CONDUCT.md' from remote 'CODE_OF_CONDUCT.md' --- CODE_OF_CONDUCT.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e0d5efa --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Code of Conduct + +As contributors and maintainers of the Jazzband projects, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in the Jazzband a harassment-free experience +for everyone, regardless of the level of experience, gender, gender identity and +expression, sexual orientation, disability, personal appearance, body size, race, +ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery +- Personal attacks +- Trolling or insulting/derogatory comments +- Public or private harassment +- Publishing other's private information, such as physical or electronic addresses, + without explicit permission +- Other unethical or unprofessional conduct + +The Jazzband roadies have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are not +aligned to this Code of Conduct, or to ban temporarily or permanently any contributor +for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, the roadies commit themselves to fairly and +consistently applying these principles to every aspect of managing the jazzband +projects. Roadies who do not follow or enforce the Code of Conduct may be permanently +removed from the Jazzband roadies. + +This code of conduct applies both within project spaces and in public spaces when an +individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and +investigated and will result in a response that is deemed necessary and appropriate to +the circumstances. Roadies are obligated to maintain confidentiality with regard to the +reporter of an incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version +1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/3/0/ From e455fe8d76c8fcde75cd19b8e237eb323e130789 Mon Sep 17 00:00:00 2001 From: rickmeasham Date: Sat, 30 Oct 2021 22:59:26 +1100 Subject: [PATCH 58/69] fix: Template on empty/unreadable file exception is a string, not a float --- icalevents/icaldownload.py | 2 +- test/test_data/empty.ics | 0 test/test_icaldownload.py | 11 +++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/test_data/empty.ics diff --git a/icalevents/icaldownload.py b/icalevents/icaldownload.py index 0a215b2..6aadedd 100644 --- a/icalevents/icaldownload.py +++ b/icalevents/icaldownload.py @@ -82,7 +82,7 @@ def data_from_file(self, file, apple_fix=False): content = f.read() if not content: - raise IOError("File %f is not readable or is empty!" % file) + raise IOError("File %s is not readable or is empty!" % file) return self.decode(content, apple_fix=apple_fix) diff --git a/test/test_data/empty.ics b/test/test_data/empty.ics new file mode 100644 index 0000000..e69de29 diff --git a/test/test_icaldownload.py b/test/test_icaldownload.py index e0831f6..17dd79d 100644 --- a/test/test_icaldownload.py +++ b/test/test_icaldownload.py @@ -93,3 +93,14 @@ def test_read_only_directory(self): # Delete tmp dir os.chdir("..") shutil.rmtree("tmp") + + def test_empty_file(self): + empty_ical = "test/test_data/empty.ics" + + with self.assertRaises(IOError) as cm: + icalevents.icaldownload.ICalDownload().data_from_file(empty_ical) + + self.assertEqual( + str(cm.exception), + "File test/test_data/empty.ics is not readable or is empty!" + ) From 47ade195d70ab4efb6b7a69a2cd72f1fd3bcee4d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 30 Oct 2021 12:04:32 +0000 Subject: [PATCH 59/69] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/test_icaldownload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_icaldownload.py b/test/test_icaldownload.py index 17dd79d..09dbc9c 100644 --- a/test/test_icaldownload.py +++ b/test/test_icaldownload.py @@ -102,5 +102,5 @@ def test_empty_file(self): self.assertEqual( str(cm.exception), - "File test/test_data/empty.ics is not readable or is empty!" + "File test/test_data/empty.ics is not readable or is empty!", ) From a37ce57d6528a8bdddaffff2ad784c3e56b63ddc Mon Sep 17 00:00:00 2001 From: Brian Hartvigsen Date: Thu, 28 Oct 2021 18:51:33 -0600 Subject: [PATCH 60/69] Fix reversion of RRULE UNTIL behavior Prior to 0.1.25, ics files containing events like the following worked normally. [A change](https://github.com/jazzband/icalevents/commit/11dfa8c64c5eca807e370067e5b30b52602b0d07#diff-6659079db1c5e26c4bd075f64b432522644ff3ee3bd4264f2863bd6f69d63e72L287-R405) in the 11dfa8c64c5eca807e370067e5b30b52602b0d07 caused these to break. This is an ics I'm getting from Google for my scheduled lunches :) This restores the pre-0.1.25 behavior and continues to pass all tests. ``` BEGIN:VEVENT SUMMARY:LUNCH DTSTART;TZID=America/Boise:20210929T130000 DTEND;TZID=America/Boise:20210929T135000 DTSTAMP:20211029T005456Z UID:not_sure_if_this_uid_is_secret@google.com SEQUENCE:3 RRULE:FREQ=WEEKLY;UNTIL=20211020;BYDAY=MO,TH,WE;WKST=SU EXDATE;TZID=America/Boise:20211013T130000 CREATED:20210811T153934Z DESCRIPTION: LAST-MODIFIED:20210927T184338Z LOCATION: STATUS:CONFIRMED TRANSP:OPAQUE X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC BEGIN:VALARM ACKNOWLEDGED:20210830T185450Z ACTION:NONE TRIGGER;VALUE=DATE-TIME:19760401T005545Z UID:343EB08E-9C4C-4151-92D0-80B351A080B2 X-WR-ALARMUID:343EB08E-9C4C-4151-92D0-80B351A080B2 END:VALARM END:VEVENT ``` --- icalevents/icalparser.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 950528d..71eb2b5 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -457,12 +457,9 @@ def parse_rrule(component, tz=UTC): rdtstart = normalize(rdtstart, tz=tz) # Remove/add timezone to rrule until dates depending on component - if type(rdtstart) is date: - for index, rru in enumerate(rrules): - if "UNTIL" in rru: - rrules[index]["UNTIL"] = adjust_timezone( - component, rru["UNTIL"], tz - ) + for index, rru in enumerate(rrules): + if "UNTIL" in rru: + rrules[index]["UNTIL"] = adjust_timezone(component, rru["UNTIL"], tz) # Parse the rrules, might return a rruleset instance, instead of rrule rule = rrulestr( From 16573fc2dc98f6c13820a9eab74b462bbe2a7893 Mon Sep 17 00:00:00 2001 From: Brian Hartvigsen Date: Thu, 28 Oct 2021 19:29:54 -0600 Subject: [PATCH 61/69] Adding test for rrule until with only a date --- test/test_data/rrule_until_only_date.ics | 43 ++++++++++++++++++++++++ test/test_icalevents.py | 15 +++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/test_data/rrule_until_only_date.ics diff --git a/test/test_data/rrule_until_only_date.ics b/test/test_data/rrule_until_only_date.ics new file mode 100644 index 0000000..a7a831c --- /dev/null +++ b/test/test_data/rrule_until_only_date.ics @@ -0,0 +1,43 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:NotYourCalendar +X-WR-TIMEZONE:America/Boise +BEGIN:VTIMEZONE +TZID:America/Boise +X-LIC-LOCATION:America/Boise +BEGIN:DAYLIGHT +TZOFFSETFROM:-0700 +TZOFFSETTO:-0600 +TZNAME:MDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0600 +TZOFFSETTO:-0700 +TZNAME:MST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Boise:20210929T130000 +DTEND;TZID=America/Boise:20210929T135000 +RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20211020;BYDAY=MO,TH,WE +EXDATE;TZID=America/Boise:20211013T130000 +DTSTAMP:20211029T011330Z +UID:9cntado4fkl7simv3loh94ua1m@google.com +CREATED:20210811T153934Z +DESCRIPTION: +LAST-MODIFIED:20210927T184338Z +LOCATION: +SEQUENCE:3 +STATUS:CONFIRMED +SUMMARY:LUNCH +TRANSP:OPAQUE +X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC +END:VEVENT +END:VCALENDAR diff --git a/test/test_icalevents.py b/test/test_icalevents.py index f40d723..61300be 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -156,6 +156,21 @@ def test_events_rrule_until_all_day_google(self): self.assertEqual(ev_2.all_day, True, "All day event") self.assertEqual(ev_2.summary, "Busy") + def test_events_rrule_until_only_date(self): + ical = "test/test_data/rrule_until_only_date.ics" + start = date(2021, 9, 29) + end = date(2021, 10, 19) + evs = icalevents.events(file=ical, start=start, end=end) + self.assertEqual(len(evs), 8) + self.assertEqual( + evs[0].start, + datetime(2021, 9, 29, 13, 0, 0, 0, tzinfo=gettz("America/Boise")), + ) + self.assertEqual( + evs[-1].start, + datetime(2021, 10, 18, 13, 0, 0, 0, tzinfo=gettz("America/Boise")), + ) + def test_events_rrule_until(self): ical = "test/test_data/rrule_until.ics" start = date(2019, 4, 2) From 7446b0326945e899940d477a864ac01e74944eff Mon Sep 17 00:00:00 2001 From: AndCycle Date: Mon, 8 Jun 2020 21:22:11 +0800 Subject: [PATCH 62/69] Add status event attribute --- icalevents/icalparser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 950528d..3d383d0 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -50,6 +50,7 @@ def __init__(self): self.attendee = None self.organizer = None self.categories = None + self.status = None def time_left(self, time=None): """ @@ -227,6 +228,9 @@ def create_event(component, tz=UTC): encoded_categories.append(encode(category)) event.categories = encoded_categories + if component.get("status"): + event.status = component.get("status") + return event From d92a2eb735cc4dfa7a32b6c1b10701d65afb99c0 Mon Sep 17 00:00:00 2001 From: AndCycle Date: Wed, 10 Jun 2020 22:25:56 +0800 Subject: [PATCH 63/69] Add url event attribute --- icalevents/icalparser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 3d383d0..096021a 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -51,6 +51,7 @@ def __init__(self): self.organizer = None self.categories = None self.status = None + self.url = None def time_left(self, time=None): """ @@ -142,6 +143,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.url = self.url return ne @@ -231,6 +233,9 @@ def create_event(component, tz=UTC): if component.get("status"): event.status = component.get("status") + if component.get("url"): + event.url = component.get("url") + return event From 0edd8b9d1903f63f145c612bf3cdf548b588105b Mon Sep 17 00:00:00 2001 From: Brian Hartvigsen Date: Sat, 30 Oct 2021 17:24:26 -0600 Subject: [PATCH 64/69] Add tests for status and url --- icalevents/icalparser.py | 5 ++-- test/test_data/status_and_url.ics | 41 +++++++++++++++++++++++++++++++ test/test_icalevents.py | 14 +++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test/test_data/status_and_url.ics diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index 096021a..fcdf79c 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -143,6 +143,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.status = self.status ne.url = self.url return ne @@ -231,10 +232,10 @@ def create_event(component, tz=UTC): event.categories = encoded_categories if component.get("status"): - event.status = component.get("status") + event.status = encode(component.get("status")) if component.get("url"): - event.url = component.get("url") + event.url = encode(component.get("url")) return event diff --git a/test/test_data/status_and_url.ics b/test/test_data/status_and_url.ics new file mode 100644 index 0000000..be26ade --- /dev/null +++ b/test/test_data/status_and_url.ics @@ -0,0 +1,41 @@ +BEGIN:VCALENDAR +BEGIN:VTIMEZONE +TZID:Europe/Berlin +END:VTIMEZONE +BEGIN:VEVENT +DESCRIPTION:Event with Status and URL +SUMMARY:Tentative Event w/ Recurrance to test copy +STATUS:TENTATIVE +DTSTART;VALUE=DATE:20181030 +DTEND;VALUE=DATE:20181031 +RRULE:FREQ=WEEKLY;BYDAY=TU +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:Event with Status and URL +SUMMARY:Confirmed Event +STATUS:CONFIRMED +URL:https://example.com/ +DTSTART;VALUE=DATE:20181030 +DTEND;VALUE=DATE:20181031 +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:Event with Status +SUMMARY:Cancelled Event +STATUS:CANCELLED +DTSTART;VALUE=DATE:20181030 +DTEND;VALUE=DATE:20181031 +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:Event with Status +SUMMARY:XPARAM Event +STATUS;X-SOMETHING=IGNOREME:CANCELLED +DTSTART;VALUE=DATE:20181030 +DTEND;VALUE=DATE:20181031 +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:Event without Status +SUMMARY:Event +DTSTART;VALUE=DATE:20181030 +DTEND;VALUE=DATE:20181031 +END:VEVENT +END:VCALENDAR diff --git a/test/test_icalevents.py b/test/test_icalevents.py index f40d723..165d600 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -421,3 +421,17 @@ def test_transparent(self): self.assertEqual(e1.transparent, True, "respect transparency") self.assertEqual(e2.transparent, False, "respect opaqueness") + + def test_status_and_url(self): + ical = "test/test_data/status_and_url.ics" + start = date(2018, 10, 30) + end = date(2018, 10, 31) + + [ev1, ev2, ev3, ev4, ev5] = icalevents.events(file=ical, start=start, end=end) + self.assertEqual(ev1.status, "TENTATIVE") + self.assertEqual(ev1.url, None) + self.assertEqual(ev2.status, "CONFIRMED") + self.assertEqual(ev2.url, "https://example.com/") + self.assertEqual(ev3.status, "CANCELLED") + self.assertEqual(ev4.status, "CANCELLED") + self.assertEqual(ev5.status, None) From 883d4e2e8440cf98a153e26417aa35bff5b7d663 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 17:10:07 +0000 Subject: [PATCH 65/69] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.9b0 → 21.10b0](https://github.com/psf/black/compare/21.9b0...21.10b0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd9c774..ca341d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 21.10b0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 From 82aa4043f06c45710127bfb3970a973d92f32693 Mon Sep 17 00:00:00 2001 From: sunny Date: Mon, 13 Sep 2021 17:00:52 -0400 Subject: [PATCH 66/69] fix recurrence id events --- icalevents/icalparser.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index a35469e..a02885d 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -145,6 +145,7 @@ def copy_to(self, new_start=None, uid=None): ne.categories = self.categories ne.status = self.status ne.url = self.url + ne.sequence = self.sequence return ne @@ -436,14 +437,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 + event for event in found + 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 97dae23a6ad5eaf0dd0288d7cd27fb69f7361593 Mon Sep 17 00:00:00 2001 From: Jack Cross Date: Thu, 16 Sep 2021 20:11:16 -0400 Subject: [PATCH 67/69] Replace faulty exception logic and handle more recurrence id cases --- icalevents/icalparser.py | 48 +++++++++++++++------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index a02885d..f3aa67f 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -345,8 +345,6 @@ 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) @@ -356,18 +354,6 @@ def parse_events(content, start=None, end=None, default_span=timedelta(days=7)): (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. @@ -426,30 +412,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 0b32bd0e7d47e8bf586e4eacc68cc846a4fac050 Mon Sep 17 00:00:00 2001 From: Jack Cross Date: Sat, 18 Sep 2021 20:16:11 -0400 Subject: [PATCH 68/69] 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 f3aa67f..bb63661 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -332,9 +332,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 From 6a46c486e3df6400ad004c08604a08b0c59968e6 Mon Sep 17 00:00:00 2001 From: Adam Kuhn Date: Mon, 24 Mar 2025 12:19:53 -0400 Subject: [PATCH 69/69] Update setup tools specification - Set up tools has deprecated using - in favor of _ --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b88034e..08aedd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.md +description_file = README.md