fix: calendar recurrence tz/overrides, today habits schedule, list archive mobile access
ci / docker (push) Successful in 3m23s
ci / docker (push) Successful in 3m23s
This commit is contained in:
+33
-11
@@ -280,16 +280,19 @@ def _is_forbidden_ip(value: str) -> bool:
|
||||
return ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved
|
||||
|
||||
|
||||
def _as_utc(value, default_tz=LOCAL_TZ):
|
||||
def _localized_ics_datetime(value, default_tz=LOCAL_TZ):
|
||||
if isinstance(value, datetime):
|
||||
if value.tzinfo is None:
|
||||
value = value.replace(tzinfo=default_tz)
|
||||
return value.astimezone(UTC), False
|
||||
return value if value.tzinfo else value.replace(tzinfo=default_tz), False
|
||||
if isinstance(value, date):
|
||||
return datetime.combine(value, time.min, tzinfo=default_tz).astimezone(UTC), True
|
||||
return datetime.combine(value, time.min, tzinfo=default_tz), True
|
||||
raise ValueError("unsupported ICS datetime")
|
||||
|
||||
|
||||
def _as_utc(value, default_tz=LOCAL_TZ):
|
||||
localized, all_day = _localized_ics_datetime(value, default_tz)
|
||||
return localized.astimezone(UTC), all_day
|
||||
|
||||
|
||||
def _event_duration(event, starts_at: datetime, all_day: bool):
|
||||
if event.get("dtend"):
|
||||
ends_at, _ = _as_utc(event.decoded("dtend"))
|
||||
@@ -334,19 +337,28 @@ def _overlaps(starts_at: datetime, ends_at: datetime | None, window_start: datet
|
||||
|
||||
def parse_ics_events(content: str, source_name: str, color: str, window_start: datetime | None = None, window_end: datetime | None = None):
|
||||
calendar = Calendar.from_ical(content)
|
||||
components = list(calendar.walk("VEVENT"))
|
||||
overrides = {}
|
||||
for event in components:
|
||||
recurrence_id = event.get("recurrence-id")
|
||||
if recurrence_id:
|
||||
overrides[(str(event.get("uid") or ""), _as_utc(event.decoded("recurrence-id"))[0])] = event
|
||||
|
||||
events = []
|
||||
for event in calendar.walk("VEVENT"):
|
||||
if not event.get("dtstart"):
|
||||
for event in components:
|
||||
if not event.get("dtstart") or event.get("recurrence-id") or str(event.get("status") or "").upper() == "CANCELLED":
|
||||
continue
|
||||
starts_at, all_day = _as_utc(event.decoded("dtstart"))
|
||||
localized_start, all_day = _localized_ics_datetime(event.decoded("dtstart"))
|
||||
starts_at = localized_start.astimezone(UTC)
|
||||
duration = _event_duration(event, starts_at, all_day)
|
||||
excluded = _excluded_starts(event)
|
||||
uid = str(event.get("uid") or "")
|
||||
if event.get("rrule") and window_start and window_end:
|
||||
rule_text = event.get("rrule").to_ical().decode()
|
||||
rule = rrulestr(rule_text, dtstart=starts_at)
|
||||
rule = rrulestr(rule_text, dtstart=localized_start)
|
||||
for occurrence in rule.between(window_start - duration, window_end, inc=True):
|
||||
occurrence = occurrence.astimezone(UTC) if occurrence.tzinfo else occurrence.replace(tzinfo=UTC)
|
||||
if occurrence in excluded:
|
||||
occurrence = occurrence.astimezone(UTC) if occurrence.tzinfo else occurrence.replace(tzinfo=LOCAL_TZ).astimezone(UTC)
|
||||
if occurrence in excluded or (uid, occurrence) in overrides:
|
||||
continue
|
||||
ends_at = occurrence + duration
|
||||
if _overlaps(occurrence, ends_at, window_start, window_end):
|
||||
@@ -355,6 +367,16 @@ def parse_ics_events(content: str, source_name: str, color: str, window_start: d
|
||||
ends_at = starts_at + duration
|
||||
if _overlaps(starts_at, ends_at, window_start, window_end):
|
||||
_append_calendar_event(events, source_name, color, event, starts_at, ends_at, all_day)
|
||||
|
||||
for event in overrides.values():
|
||||
if not event.get("dtstart") or str(event.get("status") or "").upper() == "CANCELLED":
|
||||
continue
|
||||
starts_at, all_day = _as_utc(event.decoded("dtstart"))
|
||||
duration = _event_duration(event, starts_at, all_day)
|
||||
ends_at = starts_at + duration
|
||||
if _overlaps(starts_at, ends_at, window_start, window_end):
|
||||
_append_calendar_event(events, source_name, color, event, starts_at, ends_at, all_day)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user