54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
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
|