Skip to content

Commit 593e026

Browse files
Merge pull request #133 from jazzband/feat/broken-floating
fix: use floating events if not tz is set
2 parents f9dedc0 + 45aaf61 commit 593e026

File tree

7 files changed

+430
-498
lines changed

7 files changed

+430
-498
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Install dependencies
2323
run: |
2424
python -m pip install --upgrade pip
25-
pip install poetry==1.1.11
25+
pip install poetry==1.8.3
2626
poetry install
2727
- name: Test with pytest
2828
run: |

icalevents/icalparser.py

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Parse iCal data to Events.
33
"""
4+
45
# for UID generation
56
from faulthandler import is_enabled
67
from 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

Comments
 (0)