"""Time helpers: the DB holds naive UTC, presentation converts to display_timezone (#42).""" from datetime import datetime, timezone from zoneinfo import ZoneInfo from src.backend.config import settings def utcnow() -> datetime: """Naive UTC now (the whole DB moves to naive UTC, #42).""" return datetime.now(timezone.utc).replace(tzinfo=None) def as_utc_naive(dt: datetime | None) -> datetime | None: """Aware -> UTC naive; naive is taken as UTC already; None stays None.""" if dt is None or dt.tzinfo is None: return dt return dt.astimezone(timezone.utc).replace(tzinfo=None) def to_utc_aware(dt: datetime) -> datetime: """Naive (UTC) or aware datetime -> aware UTC.""" if dt.tzinfo is None: return dt.replace(tzinfo=timezone.utc) return dt.astimezone(timezone.utc) def iso_utc(dt: datetime | None) -> str: """ISO 8601 with explicit +00:00; "" for None (CSV cells).""" return to_utc_aware(dt).isoformat() if dt else "" def to_local(dt: datetime) -> datetime: """Naive UTC (or aware) datetime -> aware datetime in settings.display_timezone.""" return to_utc_aware(dt).astimezone(ZoneInfo(settings.display_timezone))