""" 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)