Skip to content

Commit

Permalink
Merge pull request #133 from jazzband/feat/broken-floating
Browse files Browse the repository at this point in the history
fix: use floating events if not tz is set
  • Loading branch information
eigenmannmartin authored Sep 8, 2024
2 parents f9dedc0 + 45aaf61 commit 593e026
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 498 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install poetry==1.1.11
pip install poetry==1.8.3
poetry install
- name: Test with pytest
run: |
Expand Down
77 changes: 46 additions & 31 deletions icalevents/icalparser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Parse iCal data to Events.
"""

# for UID generation
from faulthandler import is_enabled
from random import randint
Expand Down Expand Up @@ -166,7 +167,7 @@ def encode(value: Optional[vText]) -> Optional[str]:
return str(value.encode("utf-8"))


def create_event(component, utc_default):
def create_event(component, utc_default, strict):
"""
Create an event from its iCal representation.
Expand All @@ -179,7 +180,16 @@ def create_event(component, utc_default):
event.start = component.get("dtstart").dt
# The RFC specifies that the TZID parameter must be specified for datetime or time
# Otherwise we set a default timezone (if only one is set with VTIMEZONE) or utc
event.floating = type(component.get("dtstart").dt) == date and utc_default
if not strict:
event.floating = (
type(component.get("dtstart").dt) == date
or component.get("dtstart").dt.tzinfo is None
)
else:
event.floating = (
type(component.get("dtstart").dt) == datetime
and component.get("dtstart").dt.tzinfo is None
)

if component.get("dtend"):
event.end = component.get("dtend").dt
Expand Down Expand Up @@ -323,15 +333,14 @@ def parse_events(

found = []

def add_if_not_exception(event):
def is_not_exception(date):
exdate = "%04d%02d%02d" % (
event.start.year,
event.start.month,
event.start.day,
date.year,
date.month,
date.day,
)

if exdate not in exceptions:
found.append(event)
return exdate not in exceptions

for component in calendar.walk():
exceptions = {}
Expand All @@ -349,14 +358,12 @@ def add_if_not_exception(event):
exceptions[exdate[0:8]] = exdate

if component.name == "VEVENT":
e = create_event(component, utc_default)
e = create_event(component, utc_default, strict)

# make rule.between happy and provide from, to points in time that have the same format as dtstart
s = component["dtstart"].dt
if type(s) is date and not e.recurring:
f, t = date(start.year, start.month, start.day), date(
end.year, end.month, end.day
)
if type(s) is date and e.recurring == False:
f, t = start, end
elif type(s) is datetime and s.tzinfo:
f, t = datetime(
start.year, start.month, start.day, tzinfo=s.tzinfo
Expand All @@ -372,25 +379,29 @@ def add_if_not_exception(event):
# 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.
if type(dt) is datetime and dt.tzinfo:
dtstart = dt.replace(tzinfo=get_timezone(str(dt.tzinfo)))
ecopy = e.copy_to(
dtstart.date() if type(s) is date else dtstart, e.uid
)
else:
ecopy = e.copy_to(dt.date() if type(s) is date else dt, e.uid)
add_if_not_exception(ecopy)

elif e.end >= f and e.start <= t:
add_if_not_exception(e)
if is_not_exception(dt):
if type(dt) is datetime and dt.tzinfo:
ecopy = e.copy_to(
dt.replace(tzinfo=get_timezone(str(dt.tzinfo))),
e.uid,
)
else:
ecopy = e.copy_to(
dt.date() if type(s) is date else dt, e.uid
)
found.append(ecopy)

elif e.end >= f and e.start <= t and is_not_exception(e.start):
found.append(e)

result = found.copy()

# Remove events that are replaced in ical
for event in found:
if not event.recurrence_id and (event.uid, event.start) in [
(f.uid, f.recurrence_id) for f in found
]:
if not event.recurrence_id and (
event.uid,
event.start,
) in [(f.uid, f.recurrence_id) for f in found]:
result.remove(event)

# > Will be deprecated ========================
Expand Down Expand Up @@ -502,9 +513,8 @@ def conform_until(until, dtstart):
year=until.year, month=until.month, day=until.day, tzinfo=UTC
)
) + (
(dtstart.tzinfo.utcoffset(dtstart) if dtstart.tzinfo else None)
or timedelta()
)
dtstart.tzinfo.utcoffset(dtstart) if dtstart.tzinfo else None
) or timedelta()

return until.date() + timedelta(days=1) if type(until) is datetime else until

Expand All @@ -525,7 +535,12 @@ def conform_until(until, dtstart):
# Add exdates to the rruleset
for exd in extract_exdates(component):
if type(dtstart) is date:
rule.exdate(exd.replace(tzinfo=None))
if type(exd) is date:
# Always convert exdates to datetimes because rrule.between does not like dates
# https://github.com/dateutil/dateutil/issues/938
rule.exdate(datetime.combine(exd, datetime.min.time()))
else:
rule.exdate(exd.replace(tzinfo=None))
else:
rule.exdate(exd)

Expand Down
Loading

0 comments on commit 593e026

Please sign in to comment.