11"""
22Parse iCal data to Events.
33"""
4+
45# for UID generation
56from faulthandler import is_enabled
67from random import randint
@@ -166,7 +167,7 @@ def encode(value: Optional[vText]) -> Optional[str]:
166167 return str (value .encode ("utf-8" ))
167168
168169
169- def create_event (component , utc_default ):
170+ def create_event (component , utc_default , strict ):
170171 """
171172 Create an event from its iCal representation.
172173
@@ -179,7 +180,16 @@ def create_event(component, utc_default):
179180 event .start = component .get ("dtstart" ).dt
180181 # The RFC specifies that the TZID parameter must be specified for datetime or time
181182 # Otherwise we set a default timezone (if only one is set with VTIMEZONE) or utc
182- event .floating = type (component .get ("dtstart" ).dt ) == date and utc_default
183+ if not strict :
184+ event .floating = (
185+ type (component .get ("dtstart" ).dt ) == date
186+ or component .get ("dtstart" ).dt .tzinfo is None
187+ )
188+ else :
189+ event .floating = (
190+ type (component .get ("dtstart" ).dt ) == datetime
191+ and component .get ("dtstart" ).dt .tzinfo is None
192+ )
183193
184194 if component .get ("dtend" ):
185195 event .end = component .get ("dtend" ).dt
@@ -323,15 +333,14 @@ def parse_events(
323333
324334 found = []
325335
326- def add_if_not_exception ( event ):
336+ def is_not_exception ( date ):
327337 exdate = "%04d%02d%02d" % (
328- event . start .year ,
329- event . start .month ,
330- event . start .day ,
338+ date .year ,
339+ date .month ,
340+ date .day ,
331341 )
332342
333- if exdate not in exceptions :
334- found .append (event )
343+ return exdate not in exceptions
335344
336345 for component in calendar .walk ():
337346 exceptions = {}
@@ -349,14 +358,12 @@ def add_if_not_exception(event):
349358 exceptions [exdate [0 :8 ]] = exdate
350359
351360 if component .name == "VEVENT" :
352- e = create_event (component , utc_default )
361+ e = create_event (component , utc_default , strict )
353362
354363 # make rule.between happy and provide from, to points in time that have the same format as dtstart
355364 s = component ["dtstart" ].dt
356- if type (s ) is date and not e .recurring :
357- f , t = date (start .year , start .month , start .day ), date (
358- end .year , end .month , end .day
359- )
365+ if type (s ) is date and e .recurring == False :
366+ f , t = start , end
360367 elif type (s ) is datetime and s .tzinfo :
361368 f , t = datetime (
362369 start .year , start .month , start .day , tzinfo = s .tzinfo
@@ -372,25 +379,29 @@ def add_if_not_exception(event):
372379 # Recompute the start time in the current timezone *on* the
373380 # date of *this* occurrence. This handles the case where the
374381 # recurrence has crossed over the daylight savings time boundary.
375- if type (dt ) is datetime and dt .tzinfo :
376- dtstart = dt .replace (tzinfo = get_timezone (str (dt .tzinfo )))
377- ecopy = e .copy_to (
378- dtstart .date () if type (s ) is date else dtstart , e .uid
379- )
380- else :
381- ecopy = e .copy_to (dt .date () if type (s ) is date else dt , e .uid )
382- add_if_not_exception (ecopy )
383-
384- elif e .end >= f and e .start <= t :
385- add_if_not_exception (e )
382+ if is_not_exception (dt ):
383+ if type (dt ) is datetime and dt .tzinfo :
384+ ecopy = e .copy_to (
385+ dt .replace (tzinfo = get_timezone (str (dt .tzinfo ))),
386+ e .uid ,
387+ )
388+ else :
389+ ecopy = e .copy_to (
390+ dt .date () if type (s ) is date else dt , e .uid
391+ )
392+ found .append (ecopy )
393+
394+ elif e .end >= f and e .start <= t and is_not_exception (e .start ):
395+ found .append (e )
386396
387397 result = found .copy ()
388398
389399 # Remove events that are replaced in ical
390400 for event in found :
391- if not event .recurrence_id and (event .uid , event .start ) in [
392- (f .uid , f .recurrence_id ) for f in found
393- ]:
401+ if not event .recurrence_id and (
402+ event .uid ,
403+ event .start ,
404+ ) in [(f .uid , f .recurrence_id ) for f in found ]:
394405 result .remove (event )
395406
396407 # > Will be deprecated ========================
@@ -502,9 +513,8 @@ def conform_until(until, dtstart):
502513 year = until .year , month = until .month , day = until .day , tzinfo = UTC
503514 )
504515 ) + (
505- (dtstart .tzinfo .utcoffset (dtstart ) if dtstart .tzinfo else None )
506- or timedelta ()
507- )
516+ dtstart .tzinfo .utcoffset (dtstart ) if dtstart .tzinfo else None
517+ ) or timedelta ()
508518
509519 return until .date () + timedelta (days = 1 ) if type (until ) is datetime else until
510520
@@ -525,7 +535,12 @@ def conform_until(until, dtstart):
525535 # Add exdates to the rruleset
526536 for exd in extract_exdates (component ):
527537 if type (dtstart ) is date :
528- rule .exdate (exd .replace (tzinfo = None ))
538+ if type (exd ) is date :
539+ # Always convert exdates to datetimes because rrule.between does not like dates
540+ # https://github.com/dateutil/dateutil/issues/938
541+ rule .exdate (datetime .combine (exd , datetime .min .time ()))
542+ else :
543+ rule .exdate (exd .replace (tzinfo = None ))
529544 else :
530545 rule .exdate (exd )
531546
0 commit comments