Files
remnawave-bedolaga-telegram…/app/utils/timezone.py
Egor e557504309 Implement panel_datetime_to_naive_utc function
Add function to convert panel datetime to naive UTC.
2026-01-28 11:42:45 +03:00

95 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Timezone utilities for consistent local time handling."""
from __future__ import annotations
import logging
from datetime import UTC, datetime
from functools import lru_cache
from zoneinfo import ZoneInfo
from app.config import settings
logger = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def get_local_timezone() -> ZoneInfo:
"""Return the configured local timezone.
Falls back to UTC if the configured timezone cannot be loaded. The
fallback is logged once and cached for subsequent calls.
"""
tz_name = settings.TIMEZONE
try:
return ZoneInfo(tz_name)
except Exception as exc: # pragma: no cover - defensive branch
logger.warning(
"⚠️ Не удалось загрузить временную зону '%s': %s. Используем UTC.",
tz_name,
exc,
)
return ZoneInfo('UTC')
def panel_datetime_to_naive_utc(dt: datetime) -> datetime:
"""Convert a panel datetime to naive UTC.
Panel API returns local time with a misleading UTC offset (+00:00 / Z).
This strips the offset, interprets the raw value as panel-local time,
then converts to naive UTC for database storage.
"""
naive = dt.replace(tzinfo=None)
localized = naive.replace(tzinfo=get_local_timezone())
return localized.astimezone(ZoneInfo('UTC')).replace(tzinfo=None)
def to_local_datetime(dt: datetime | None) -> datetime | None:
"""Convert a datetime value to the configured local timezone."""
if dt is None:
return None
aware_dt = dt if dt.tzinfo is not None else dt.replace(tzinfo=UTC)
return aware_dt.astimezone(get_local_timezone())
def format_local_datetime(
dt: datetime | None,
fmt: str = '%Y-%m-%d %H:%M:%S %Z',
na_placeholder: str = 'N/A',
) -> str:
"""Format a datetime value in the configured local timezone."""
localized = to_local_datetime(dt)
if localized is None:
return na_placeholder
return localized.strftime(fmt)
class TimezoneAwareFormatter(logging.Formatter):
"""Logging formatter that renders timestamps in the configured timezone."""
def __init__(self, *args, timezone_name: str | None = None, **kwargs):
super().__init__(*args, **kwargs)
if timezone_name:
try:
self._timezone = ZoneInfo(timezone_name)
except Exception as exc: # pragma: no cover - defensive branch
logger.warning(
"⚠️ Не удалось загрузить временную зону '%s': %s. Используем UTC.",
timezone_name,
exc,
)
self._timezone = ZoneInfo('UTC')
else:
self._timezone = get_local_timezone()
def formatTime(self, record, datefmt=None):
dt = datetime.fromtimestamp(record.created, tz=self._timezone)
if datefmt:
return dt.strftime(datefmt)
return dt.strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]