Files
bakery-ia/shared/dt_utils/timezone.py

161 lines
3.8 KiB
Python
Raw Normal View History

2025-10-12 23:16:04 +02:00
"""
Timezone Operations
Timezone metadata and conversion utilities.
"""
from datetime import datetime, date, time
from zoneinfo import ZoneInfo
from typing import Optional
import structlog
from .constants import DEFAULT_TIMEZONE, SUPPORTED_TIMEZONES
logger = structlog.get_logger()
def get_timezone_info(tz: str) -> ZoneInfo:
"""
Get ZoneInfo object for timezone.
Args:
tz: IANA timezone string
Returns:
ZoneInfo object
Raises:
ZoneInfoNotFoundError: If timezone is invalid
"""
return ZoneInfo(tz)
def get_utc_offset_hours(tz: str) -> float:
"""
Get current UTC offset for timezone in hours.
Args:
tz: IANA timezone string
Returns:
UTC offset in hours (e.g., +2.0 for CEST)
"""
try:
zone = ZoneInfo(tz)
now = datetime.now(zone)
offset_seconds = now.utcoffset().total_seconds()
return offset_seconds / 3600
except Exception as e:
logger.warning(f"Could not get offset for {tz}", error=str(e))
return 0.0
def list_supported_timezones() -> list[str]:
"""
Get list of supported timezones.
Returns:
List of IANA timezone strings
"""
return sorted(SUPPORTED_TIMEZONES)
def get_current_date_in_tz(tz: str = DEFAULT_TIMEZONE) -> date:
"""
Get current date in specified timezone.
Args:
tz: IANA timezone string
Returns:
Current date in the specified timezone
"""
try:
zone = ZoneInfo(tz)
return datetime.now(zone).date()
except Exception as e:
logger.warning(f"Invalid timezone {tz}, using default", error=str(e))
return datetime.now(ZoneInfo(DEFAULT_TIMEZONE)).date()
def get_current_datetime_in_tz(tz: str = DEFAULT_TIMEZONE) -> datetime:
"""
Get current datetime in specified timezone.
Args:
tz: IANA timezone string
Returns:
Current datetime in the specified timezone
"""
try:
zone = ZoneInfo(tz)
return datetime.now(zone)
except Exception as e:
logger.warning(f"Invalid timezone {tz}, using default", error=str(e))
return datetime.now(ZoneInfo(DEFAULT_TIMEZONE))
def combine_date_time_in_tz(
target_date: date,
target_time: time,
tz: str = DEFAULT_TIMEZONE
) -> datetime:
"""
Combine date and time in specified timezone.
Args:
target_date: Date component
target_time: Time component
tz: IANA timezone string
Returns:
Datetime combining date and time in specified timezone
"""
try:
zone = ZoneInfo(tz)
return datetime.combine(target_date, target_time, tzinfo=zone)
except Exception as e:
logger.warning(f"Invalid timezone {tz}, using default", error=str(e))
zone = ZoneInfo(DEFAULT_TIMEZONE)
return datetime.combine(target_date, target_time, tzinfo=zone)
def convert_to_utc(dt: datetime) -> datetime:
"""
Convert datetime to UTC.
Args:
dt: Datetime to convert (must be timezone-aware)
Returns:
Datetime in UTC timezone
"""
if dt.tzinfo is None:
logger.warning("Converting naive datetime to UTC, assuming UTC")
return dt.replace(tzinfo=ZoneInfo("UTC"))
return dt.astimezone(ZoneInfo("UTC"))
def convert_from_utc(dt: datetime, target_timezone: str) -> datetime:
"""
Convert UTC datetime to target timezone.
Args:
dt: UTC datetime
target_timezone: Target IANA timezone string
Returns:
Datetime in target timezone
"""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=ZoneInfo("UTC"))
try:
zone = ZoneInfo(target_timezone)
return dt.astimezone(zone)
except Exception as e:
logger.warning(f"Invalid timezone {target_timezone}, using default", error=str(e))
zone = ZoneInfo(DEFAULT_TIMEZONE)
return dt.astimezone(zone)