70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
"""Timezone helpers for web UI."""
|
|
from contextvars import ContextVar
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
|
|
_current_timezone: ContextVar[str] = ContextVar("current_timezone", default="UTC")
|
|
|
|
|
|
def set_current_timezone(tz: str) -> None:
|
|
_current_timezone.set(tz)
|
|
|
|
|
|
def get_current_timezone() -> str:
|
|
return _current_timezone.get()
|
|
|
|
|
|
def get_common_timezones() -> list[str]:
|
|
"""Return a curated list of common IANA timezones for the UI."""
|
|
return [
|
|
"UTC",
|
|
"Europe/Moscow",
|
|
"Europe/Samara",
|
|
"Asia/Krasnoyarsk",
|
|
"Asia/Novosibirsk",
|
|
"Asia/Irkutsk",
|
|
"Asia/Yakutsk",
|
|
"Asia/Vladivostok",
|
|
"Asia/Kamchatka",
|
|
"Asia/Almaty",
|
|
"Asia/Tashkent",
|
|
"Asia/Tbilisi",
|
|
]
|
|
|
|
|
|
def _ensure_aware(dt: datetime, default_tz: str = "UTC") -> datetime:
|
|
if dt.tzinfo is None:
|
|
return dt.replace(tzinfo=ZoneInfo(default_tz))
|
|
return dt
|
|
|
|
|
|
def localtime(
|
|
value: Optional[datetime],
|
|
tz: Optional[str] = None,
|
|
fmt: str = "%Y-%m-%d %H:%M",
|
|
) -> str:
|
|
"""Convert a UTC datetime to the configured timezone and format it."""
|
|
if value is None:
|
|
return "-"
|
|
tz_name = tz or get_current_timezone()
|
|
try:
|
|
zone = ZoneInfo(tz_name)
|
|
except ZoneInfoNotFoundError:
|
|
zone = ZoneInfo("UTC")
|
|
dt = _ensure_aware(value)
|
|
return dt.astimezone(zone).strftime(fmt)
|
|
|
|
|
|
def localtime_iso(value: Optional[datetime], tz: Optional[str] = None) -> str:
|
|
"""Convert a UTC datetime to the configured timezone and return ISO string."""
|
|
if value is None:
|
|
return ""
|
|
tz_name = tz or get_current_timezone()
|
|
try:
|
|
zone = ZoneInfo(tz_name)
|
|
except ZoneInfoNotFoundError:
|
|
zone = ZoneInfo("UTC")
|
|
dt = _ensure_aware(value)
|
|
return dt.astimezone(zone).isoformat()
|