Strip tz in _parse_dt dateutil fallback (naive-datetime contract) (#2557)

_parse_dt documents that it returns naive datetimes (CalendarEvent.dtstart is
naive) and every return path strips tz — except the last-resort dateutil
fallback, which returned dateutil's value verbatim. An offset-bearing non-ISO
input (e.g. RFC-2822 'Mon, 05 Jan 2026 14:00:00 +0900', which fromisoformat
rejects but dateutil parses) leaked a tz-aware datetime into the naive dtstart
column via create_event/update_event -> _parse_dt_pair. On read-back,
_expand_rrule compares ev.dtstart against naive window bounds and raised
'can't compare offset-naive and offset-aware datetimes' (500 / no events).

Normalize the fallback to UTC-naive, mirroring the fromisoformat branch. Naive
inputs are unchanged.

(cherry picked from commit b03b6b91df)

Co-authored-by: ghreprimand <203024559+ghreprimand@users.noreply.github.com>
This commit is contained in:
ghreprimand
2026-06-05 02:18:26 -05:00
committed by GitHub
parent 9ffa87e394
commit e0097c9c48
2 changed files with 57 additions and 1 deletions
+11 -1
View File
@@ -399,7 +399,17 @@ def _parse_dt(s: str) -> datetime:
# Last resort: dateutil's fuzzy parser
try:
from dateutil import parser as _du
return _du.parse(s)
parsed = _du.parse(s)
# Strip tz like every other return path above — this function's
# contract is naive datetimes (CalendarEvent.dtstart is naive). An
# offset-bearing non-ISO input (e.g. RFC-2822 "Mon, 05 Jan 2026
# 14:00:00 +0900") otherwise leaked tz-aware into the naive column and
# crashed read-back comparisons in _expand_rrule with "can't compare
# offset-naive and offset-aware datetimes".
if parsed.tzinfo is not None:
from datetime import timezone as _tz
return parsed.astimezone(_tz.utc).replace(tzinfo=None)
return parsed
except Exception:
raise ValueError(f"could not parse datetime: {s!r}")