from __future__ import annotations from datetime import UTC, datetime, timedelta from zoneinfo import ZoneInfo, ZoneInfoNotFoundError DEFAULT_TIME_ZONE = "UTC" _TIMESTAMP_KEYS = ("observed_at", "opened_at", "resolved_at") def validate_time_zone(value: str) -> str: name = value.strip() or DEFAULT_TIME_ZONE try: ZoneInfo(name) except ZoneInfoNotFoundError as exc: raise ValueError(f"invalid timezone: {name}") from exc return name def require_utc_datetime(value: datetime) -> datetime: if value.tzinfo is None or value.utcoffset() is None: raise ValueError("timestamp must include UTC timezone") if value.utcoffset() != timedelta(0): raise ValueError("timestamp must be UTC") return value.astimezone(UTC) def to_utc(value: datetime) -> datetime: if value.tzinfo is None or value.utcoffset() is None: return value.replace(tzinfo=UTC) return value.astimezone(UTC) def format_display_datetime(value: str | datetime | None, time_zone: str) -> str: if not value: return "" if isinstance(value, datetime): dt = value else: dt = datetime.fromisoformat(value.replace("Z", "+00:00")) return ( to_utc(dt) .astimezone(ZoneInfo(validate_time_zone(time_zone))) .strftime("%Y-%m-%d %H:%M:%S %Z") ) def payload_with_display_times(payload: dict, time_zone: str) -> dict: out = dict(payload) out["time_zone"] = validate_time_zone(time_zone) for key in _TIMESTAMP_KEYS: if payload.get(key): out[f"{key}_display"] = format_display_datetime(payload[key], out["time_zone"]) return out