53 lines
1.4 KiB
Python
53 lines
1.4 KiB
Python
"""Shared helpers for generating iCalendar feeds."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, timezone
|
|
from typing import Iterable
|
|
|
|
|
|
def escape_text(value: str) -> str:
|
|
"""Escape text for safer ICS output."""
|
|
return (
|
|
value.replace("\\", "\\\\")
|
|
.replace(";", "\\;")
|
|
.replace(",", "\\,")
|
|
.replace("\n", "\\n")
|
|
)
|
|
|
|
|
|
def _fold_line(value: str) -> Iterable[str]:
|
|
"""Yield RFC5545 folded lines."""
|
|
if len(value) <= 75:
|
|
yield value
|
|
return
|
|
|
|
remaining = value
|
|
first = True
|
|
while remaining:
|
|
segment = remaining[:75]
|
|
remaining = remaining[75:]
|
|
if not first:
|
|
segment = " " + segment
|
|
yield segment
|
|
first = False
|
|
|
|
|
|
def append_property(lines: list[str], name: str, value: str) -> None:
|
|
"""Append a folded property line to the ICS output."""
|
|
for line in _fold_line(f"{name}:{value}"):
|
|
lines.append(line)
|
|
|
|
|
|
def format_datetime_utc(dt_value: datetime) -> str:
|
|
"""Return datetime formatted in UTC for ICS."""
|
|
if dt_value.tzinfo is None:
|
|
dt_value = dt_value.replace(tzinfo=timezone.utc)
|
|
else:
|
|
dt_value = dt_value.astimezone(timezone.utc)
|
|
return dt_value.strftime("%Y%m%dT%H%M%SZ")
|
|
|
|
|
|
def format_date(date_value: date) -> str:
|
|
"""Return date formatted for all-day ICS events."""
|
|
return date_value.strftime("%Y%m%d")
|