| """Timezone utility functions for the scheduling system.""" |
|
|
| import logging |
| from datetime import datetime, time, timedelta |
| from typing import Optional, Tuple, Dict, Any |
| import pytz |
| from zoneinfo import ZoneInfo |
| from zoneinfo._common import ZoneInfoNotFoundError |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
| COMMON_TIMEZONES = [ |
| 'UTC', |
| 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', |
| 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Rome', |
| 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Dubai', 'Asia/Kolkata', |
| 'Australia/Sydney', 'Pacific/Auckland', |
| 'Africa/Porto-Novo', 'Africa/Cairo', 'Africa/Johannesburg', |
| 'America/Toronto', 'America/Mexico_City', 'America/Sao_Paulo', |
| 'Europe/Madrid', 'Europe/Amsterdam', 'Europe/Stockholm', |
| 'Asia/Seoul', 'Asia/Singapore', 'Asia/Hong_Kong', |
| 'Pacific/Honolulu' |
| ] |
|
|
| def validate_timezone(timezone_str: str) -> bool: |
| """ |
| Validate if a timezone string is valid. |
| |
| Args: |
| timezone_str (str): Timezone string to validate |
| |
| Returns: |
| bool: True if valid, False otherwise |
| """ |
| try: |
| ZoneInfo(timezone_str) |
| return True |
| except (pytz.UnknownTimeZoneError, ValueError, ZoneInfoNotFoundError): |
| return False |
|
|
| def get_user_timezone() -> str: |
| """ |
| Get the system's local timezone. |
| |
| Returns: |
| str: System timezone string |
| """ |
| try: |
| |
| import tzlocal |
| return str(tzlocal.get_localzone()) |
| except Exception: |
| |
| return 'UTC' |
|
|
| def parse_timezone_schedule(schedule_time: str) -> Tuple[str, Optional[str]]: |
| """ |
| Parse a timezone-prefixed schedule time string. |
| |
| Args: |
| schedule_time (str): Schedule time in format "Day HH:MM::::timezone" or "Day HH:MM" |
| |
| Returns: |
| Tuple[str, Optional[str]]: (time_part, timezone_part) |
| """ |
| if "::::" in schedule_time: |
| time_part, timezone_part = schedule_time.split("::::", 1) |
| return time_part.strip(), timezone_part.strip() |
| else: |
| |
| return schedule_time, None |
|
|
| def format_timezone_schedule(time_part: str, timezone: Optional[str]) -> str: |
| """ |
| Format a schedule time with timezone prefix. |
| |
| Args: |
| time_part (str): Time part in format "Day HH:MM" |
| timezone (Optional[str]): Timezone string or None |
| |
| Returns: |
| str: Formatted schedule time with timezone |
| """ |
| if timezone: |
| return f"{time_part}::::{timezone}" |
| else: |
| return time_part |
|
|
| def convert_time_to_timezone(time_str: str, from_timezone: str, to_timezone: str) -> str: |
| """ |
| Convert a time string from one timezone to another. |
| |
| Args: |
| time_str (str): Time string in format "Day HH:MM" |
| from_timezone (str): Source timezone |
| to_timezone (str): Target timezone |
| |
| Returns: |
| str: Converted time string in format "Day HH:MM" |
| """ |
| try: |
| |
| day_name, time_part = time_str.split() |
| hour, minute = map(int, time_part.split(':')) |
| |
| |
| from_tz = ZoneInfo(from_timezone) |
| to_tz = ZoneInfo(to_timezone) |
| |
| |
| now = datetime.now(from_tz) |
| |
| |
| scheduled_datetime = now.replace(hour=hour, minute=minute, second=0, microsecond=0) |
| |
| |
| target_datetime = scheduled_datetime.astimezone(to_tz) |
| |
| |
| return f"{day_name} {target_datetime.hour:02d}:{target_datetime.minute:02d}" |
| |
| except Exception as e: |
| logger.error(f"Error converting time {time_str} from {from_timezone} to {to_timezone}: {str(e)}") |
| |
| return time_str |
|
|
| def get_server_timezone() -> str: |
| """ |
| Get the server's timezone. |
| |
| Returns: |
| str: Server timezone string |
| """ |
| try: |
| |
| import os |
| server_tz = os.environ.get('TZ', 'UTC') |
| if validate_timezone(server_tz): |
| return server_tz |
| |
| |
| return get_user_timezone() |
| except Exception: |
| return 'UTC' |
|
|
| def calculate_adjusted_time_with_timezone(schedule_time: str, timezone: str) -> str: |
| """ |
| Calculate adjusted time for content generation (5 minutes before schedule) with timezone support. |
| |
| Args: |
| schedule_time (str): Schedule time in format "Day HH:MM::::timezone" or "Day HH:MM" |
| timezone (str): Timezone string |
| |
| Returns: |
| str: Adjusted time with timezone |
| """ |
| try: |
| |
| time_part, schedule_timezone = parse_timezone_schedule(schedule_time) |
| |
| |
| effective_timezone = schedule_timezone or timezone |
| |
| if not effective_timezone: |
| effective_timezone = get_server_timezone() |
| |
| |
| day, time_str = time_part.split() |
| hour, minute = map(int, time_str.split(':')) |
| |
| |
| tz = ZoneInfo(effective_timezone) |
| |
| |
| now = datetime.now(tz) |
| |
| |
| scheduled_datetime = now.replace(hour=hour, minute=minute, second=0, microsecond=0) |
| |
| |
| adjusted_datetime = scheduled_datetime - timedelta(minutes=5) |
| |
| |
| adjusted_time_str = f"{day} {adjusted_datetime.hour:02d}:{adjusted_datetime.minute:02d}" |
| |
| |
| if effective_timezone: |
| return format_timezone_schedule(adjusted_time_str, effective_timezone) |
| else: |
| return adjusted_time_str |
| |
| except Exception as e: |
| logger.error(f"Error calculating adjusted time for {schedule_time}: {str(e)}") |
| |
| return schedule_time |