Serve events in ics with UTZ timestamps.
This commit is contained in:
parent
77ea6a8afa
commit
5eab5361b2
|
|
@ -4,7 +4,7 @@ import decimal
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import typing
|
import typing
|
||||||
from datetime import date, datetime, timedelta, timezone, tzinfo as datetime_tzinfo
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import pycountry
|
import pycountry
|
||||||
|
|
@ -548,43 +548,16 @@ def _append_ical_property(lines: list[str], name: str, value: str) -> None:
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
|
||||||
|
|
||||||
def _append_datetime_property(lines: list[str], name: str, dt_value: datetime) -> None:
|
def _ensure_utc(dt_value: datetime) -> datetime:
|
||||||
"""Append a datetime property preserving timezone."""
|
"""Ensure datetimes are timezone-aware in UTC."""
|
||||||
formatted, tzid = _format_ical_datetime(dt_value)
|
if dt_value.tzinfo is None:
|
||||||
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:
|
|
||||||
return dt_value.replace(tzinfo=timezone.utc)
|
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:
|
def _format_ical_datetime(dt_value: datetime) -> str:
|
||||||
"""Return TZID string for timezone-aware datetime."""
|
"""Format datetime objects using UTC and RFC5545 format."""
|
||||||
for attr in ("zone", "key"):
|
return _ensure_utc(dt_value).strftime("%Y%m%dT%H%M%SZ")
|
||||||
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_date(date_value: date) -> str:
|
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
|
end_dt = as_datetime(element.end_time) if element.end_time else None
|
||||||
if end_dt is None or end_dt <= start_dt:
|
if end_dt is None or end_dt <= start_dt:
|
||||||
end_dt = start_dt + DEFAULT_EVENT_DURATION
|
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]:
|
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()):
|
for index, element in enumerate(trip.elements()):
|
||||||
lines.append("BEGIN:VEVENT")
|
lines.append("BEGIN:VEVENT")
|
||||||
_append_ical_property(lines, "UID", _trip_element_uid(trip, element, index))
|
_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:
|
if element.all_day:
|
||||||
start_date, end_date = _event_all_day_dates(element)
|
start_date, end_date = _event_all_day_dates(element)
|
||||||
_append_ical_property(
|
_append_ical_property(
|
||||||
|
|
@ -795,8 +768,8 @@ def build_trip_ical(trips: list[Trip]) -> bytes:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
start_dt, end_dt = _event_datetimes(element)
|
start_dt, end_dt = _event_datetimes(element)
|
||||||
_append_datetime_property(lines, "DTSTART", start_dt)
|
_append_ical_property(lines, "DTSTART", _format_ical_datetime(start_dt))
|
||||||
_append_datetime_property(lines, "DTEND", end_dt)
|
_append_ical_property(lines, "DTEND", _format_ical_datetime(end_dt))
|
||||||
summary = _escape_ical_text(_trip_element_summary(trip, element))
|
summary = _escape_ical_text(_trip_element_summary(trip, element))
|
||||||
_append_ical_property(lines, "SUMMARY", summary)
|
_append_ical_property(lines, "SUMMARY", summary)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue