Refactor datetime and timezone utils
This commit is contained in:
160
shared/dt_utils/timezone.py
Normal file
160
shared/dt_utils/timezone.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user