diff --git a/agenda/trip.py b/agenda/trip.py index 0aadcfb..b47e185 100644 --- a/agenda/trip.py +++ b/agenda/trip.py @@ -4,7 +4,7 @@ import decimal import hashlib import os import typing -from datetime import date, datetime, timedelta, timezone, tzinfo as datetime_tzinfo +from datetime import date, datetime, timedelta, timezone import flask import pycountry @@ -548,43 +548,16 @@ def _append_ical_property(lines: list[str], name: str, value: str) -> None: lines.append(line) -def _append_datetime_property(lines: list[str], name: str, dt_value: datetime) -> None: - """Append a datetime property preserving timezone.""" - formatted, tzid = _format_ical_datetime(dt_value) - prop_name = f"{name};TZID={tzid}" if tzid else name - _append_ical_property(lines, prop_name, formatted) - - -def _ensure_timezone(dt_value: datetime) -> datetime: - """Ensure datetimes are timezone-aware.""" - if dt_value.tzinfo is None or dt_value.tzinfo.utcoffset(dt_value) is None: +def _ensure_utc(dt_value: datetime) -> datetime: + """Ensure datetimes are timezone-aware in UTC.""" + if dt_value.tzinfo is None: return dt_value.replace(tzinfo=timezone.utc) - return dt_value + return dt_value.astimezone(timezone.utc) -def _tzid_from_tzinfo(tzinfo_obj: datetime_tzinfo, dt_value: datetime) -> str | None: - """Return TZID string for timezone-aware datetime.""" - for attr in ("zone", "key"): - tzid = getattr(tzinfo_obj, attr, None) - if isinstance(tzid, str): - return tzid - tzname = tzinfo_obj.tzname(dt_value) - if isinstance(tzname, str) and tzname.upper() not in {"UTC", "GMT"}: - return tzname - return None - - -def _format_ical_datetime(dt_value: datetime) -> tuple[str, str | None]: - """Format datetime objects, preserving timezone when available.""" - dt_value = _ensure_timezone(dt_value) - tzinfo = dt_value.tzinfo - assert tzinfo - tzid = _tzid_from_tzinfo(tzinfo, dt_value) - if tzid: - local_dt = dt_value.astimezone(tzinfo).replace(tzinfo=None) - return local_dt.strftime("%Y%m%dT%H%M%S"), tzid - utc_dt = dt_value.astimezone(timezone.utc) - return utc_dt.strftime("%Y%m%dT%H%M%SZ"), None +def _format_ical_datetime(dt_value: datetime) -> str: + """Format datetime objects using UTC and RFC5545 format.""" + return _ensure_utc(dt_value).strftime("%Y%m%dT%H%M%SZ") def _format_ical_date(date_value: date) -> str: @@ -598,7 +571,7 @@ def _event_datetimes(element: TripElement) -> tuple[datetime, datetime]: end_dt = as_datetime(element.end_time) if element.end_time else None if end_dt is None or end_dt <= start_dt: end_dt = start_dt + DEFAULT_EVENT_DURATION - return _ensure_timezone(start_dt), _ensure_timezone(end_dt) + return _ensure_utc(start_dt), _ensure_utc(end_dt) def _event_all_day_dates(element: TripElement) -> tuple[date, date]: @@ -784,7 +757,7 @@ def build_trip_ical(trips: list[Trip]) -> bytes: for index, element in enumerate(trip.elements()): lines.append("BEGIN:VEVENT") _append_ical_property(lines, "UID", _trip_element_uid(trip, element, index)) - _append_datetime_property(lines, "DTSTAMP", generated) + _append_ical_property(lines, "DTSTAMP", _format_ical_datetime(generated)) if element.all_day: start_date, end_date = _event_all_day_dates(element) _append_ical_property( @@ -795,8 +768,8 @@ def build_trip_ical(trips: list[Trip]) -> bytes: ) else: start_dt, end_dt = _event_datetimes(element) - _append_datetime_property(lines, "DTSTART", start_dt) - _append_datetime_property(lines, "DTEND", end_dt) + _append_ical_property(lines, "DTSTART", _format_ical_datetime(start_dt)) + _append_ical_property(lines, "DTEND", _format_ical_datetime(end_dt)) summary = _escape_ical_text(_trip_element_summary(trip, element)) _append_ical_property(lines, "SUMMARY", summary)