From 0edd41e46a655c8c744ad1d5fff3fc4ae819b36a Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sun, 12 Apr 2026 14:21:56 +0200 Subject: [PATCH] fix: CalDAV REPORT time-range - 500 wenn end fehlt DAVx5 sendet bei calendar-query oft nur ohne end. Mein Code hat dann blind CalendarEvent.dtstart < None gefiltert, was SQLAlchemy mit TypeError abbrechen liess - Ergebnis HTTP 500, Sync scheitert komplett. Zwei Korrekturen: * end-Filter wird nur gesetzt wenn end wirklich vorhanden ist * time-range-Parser strippt tzinfo, damit Vergleiche mit den tz-naiven DB-Spalten keine Exception werfen Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/dav/caldav.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/app/dav/caldav.py b/backend/app/dav/caldav.py index 698dcd2..7ba0de9 100644 --- a/backend/app/dav/caldav.py +++ b/backend/app/dav/caldav.py @@ -343,11 +343,13 @@ def report(subpath): return _xml_response(multistatus) if tag == _qn('c', 'calendar-query'): - # Parse optional time-range + # Parse optional time-range. start ohne end = "ab jetzt offen"; + # end ohne start = "bis X"; beide None = kein Filter. start, end = _extract_time_range(root) q = CalendarEvent.query.filter_by(calendar_id=cal.id) - if start is not None: + if end is not None: q = q.filter(CalendarEvent.dtstart < end) + if start is not None: q = q.filter( (CalendarEvent.dtend >= start) | (CalendarEvent.dtstart >= start) | (CalendarEvent.recurrence_rule.isnot(None)) @@ -368,17 +370,22 @@ def _extract_time_range(root: ET.Element): if not s: return None s = s.replace('Z', '+00:00') + dt = None try: - return datetime.fromisoformat(s) + dt = datetime.fromisoformat(s) except ValueError: - # Compact ICS form: 20260412T120000Z try: - return datetime.strptime(s, '%Y%m%dT%H%M%S%z') + dt = datetime.strptime(s, '%Y%m%dT%H%M%S%z') except ValueError: try: - return datetime.strptime(s[:15], '%Y%m%dT%H%M%S').replace(tzinfo=timezone.utc) + dt = datetime.strptime(s[:15], '%Y%m%dT%H%M%S').replace(tzinfo=timezone.utc) except ValueError: return None + # Unsere DB-Spalten sind tz-naive (lokal UTC) - Vergleich ginge + # sonst mit TypeError. Also tz-Info abstreifen. + if dt.tzinfo is not None: + dt = dt.astimezone(timezone.utc).replace(tzinfo=None) + return dt return parse(tr.get('start')), parse(tr.get('end'))