reminder-bot/bot/services/time_service.py

154 lines
3.9 KiB
Python

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