"""Time and timezone utilities.""" from datetime import datetime, time, timedelta from zoneinfo import ZoneInfo from typing import Optional from bot.logging_config import get_logger logger = get_logger(__name__) class TimeService: """Service for time-related operations.""" def __init__(self, timezone: str = "Europe/Moscow"): """ Initialize TimeService. Args: timezone: Timezone name (e.g., "Europe/Moscow") """ self.timezone = ZoneInfo(timezone) logger.info(f"TimeService initialized with timezone: {timezone}") def get_now(self) -> datetime: """ Get current datetime in configured timezone. Returns: Current datetime """ return datetime.now(self.timezone) def combine_date_time(self, date: datetime, time_of_day: time) -> datetime: """ Combine date and time in configured timezone. Args: date: Date component time_of_day: Time component Returns: Combined datetime """ naive_dt = datetime.combine(date.date(), time_of_day) return naive_dt.replace(tzinfo=self.timezone) def calculate_next_run_time( self, time_of_day: time, days_interval: int, from_datetime: Optional[datetime] = None, ) -> datetime: """ Calculate next run datetime for a reminder. Args: time_of_day: Desired time of day days_interval: Days between reminders from_datetime: Base datetime (defaults to now) Returns: Next run datetime """ if from_datetime is None: from_datetime = self.get_now() # Start with today at the specified time next_run = self.combine_date_time(from_datetime, time_of_day) # If the time has already passed today, start from tomorrow if next_run <= from_datetime: next_run += timedelta(days=1) return next_run def calculate_next_occurrence( self, current_run: datetime, days_interval: int, ) -> datetime: """ Calculate next occurrence after a completed reminder. Args: current_run: Current run datetime days_interval: Days between reminders Returns: Next occurrence datetime """ return current_run + timedelta(days=days_interval) def add_hours(self, dt: datetime, hours: int) -> datetime: """ Add hours to a datetime. Args: dt: Base datetime hours: Hours to add Returns: New datetime """ return dt + timedelta(hours=hours) def format_next_run(self, next_run: datetime) -> str: """ Format next run datetime for display. Args: next_run: Next run datetime Returns: Formatted string """ now = self.get_now() delta = next_run - now # If it's today if next_run.date() == now.date(): return f"сегодня в {next_run.strftime('%H:%M')}" # If it's tomorrow tomorrow = (now + timedelta(days=1)).date() if next_run.date() == tomorrow: return f"завтра в {next_run.strftime('%H:%M')}" # Otherwise, show full date return next_run.strftime("%d.%m.%Y в %H:%M") # Global instance _time_service: Optional[TimeService] = None def get_time_service(timezone: Optional[str] = None) -> TimeService: """ Get global TimeService instance. Args: timezone: Timezone name (only used for first initialization) Returns: TimeService instance """ global _time_service if _time_service is None: if timezone is None: from bot.config import get_config timezone = get_config().timezone _time_service = TimeService(timezone) return _time_service