From 684a9521589ecde478cb14e2eb681ea1dbaa8a2a Mon Sep 17 00:00:00 2001 From: cisterna Date: Thu, 2 Apr 2026 09:53:14 +0300 Subject: [PATCH] timezone fix --- bot.py | 36 +++++++++++++++++++++++++----------- database.py | 16 +++++++++------- weather.py | 45 +++++++++++++++++++++++++++++++++------------ 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/bot.py b/bot.py index 6db5c2d..b124f72 100644 --- a/bot.py +++ b/bot.py @@ -17,7 +17,8 @@ from weather import ( get_daily_weather_summary, get_coords_by_city, get_tomorrow_weather_summary, - get_weekly_weather_summary + get_weekly_weather_summary, + get_timezone_by_coords ) import database as db # Импортируем нашу базу данных @@ -90,22 +91,29 @@ async def cmd_change_city(message: types.Message, state: FSMContext): async def process_location_input(message: types.Message, state: FSMContext): user_id = message.from_user.id city_name_display = "Выбранная локация" + timezone = None if message.location: lat = message.location.latitude lon = message.location.longitude + # Для координат нужно определить часовой пояс отдельным запросом + timezone = await get_timezone_by_coords(lat, lon) + if not timezone: + await message.answer("Не удалось определить часовой пояс для вашей геопозиции. Попробуйте ввести название города.") + return elif message.text: - lat, lon, found_name = await get_coords_by_city(message.text) + lat, lon, found_name, timezone = await get_coords_by_city(message.text) if not lat: await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название.") return city_name_display = found_name else: return - - # Сохраняем локацию в БД - await db.save_user_location(user_id, lat, lon) - + + # Сохраняем локацию и часовой пояс в БД + await db.save_user_location(user_id, lat, lon, timezone) + logging.info(f"Сохранена локация для {user_id}: lat={lat}, lon={lon}, timezone={timezone}") + # Проверяем, есть ли уже у пользователя настроенное время user = await db.get_user(user_id) if user and user["time"]: @@ -114,7 +122,7 @@ async def process_location_input(message: types.Message, state: FSMContext): else: await state.set_state(WeatherSetup.waiting_for_time) await message.answer( - f"Отлично! Локация **{city_name_display}** найдена.\n\nТеперь напиши время (в формате ЧЧ:ММ):", + f"Отлично! Локация **{city_name_display}** найдена (часовой пояс: {timezone}).\n\nТеперь напиши время для ежедневной рассылки (в формате ЧЧ:ММ):", reply_markup=ReplyKeyboardRemove() ) @@ -136,6 +144,12 @@ async def process_time(message: types.Message, state: FSMContext): try: dt = datetime.strptime(time_text, "%H:%M") + # Получаем пользователя, чтобы узнать его часовой пояс + user = await db.get_user(user_id) + if not user or not user["timezone"]: + await message.answer("Не удалось найти ваш часовой пояс. Пожалуйста, установите город заново (команда /start).") + return + # Сохраняем время в БД await db.update_user_time(user_id, time_text) @@ -144,8 +158,8 @@ async def process_time(message: types.Message, state: FSMContext): scheduler.add_job( send_weather_update, trigger='cron', - hour=dt.hour, - minute=dt.minute, + hour=dt.hour, minute=dt.minute, + timezone=user["timezone"], args=[user_id], id=str(user_id), replace_existing=True @@ -204,8 +218,8 @@ async def restore_jobs(): scheduler.add_job( send_weather_update, trigger='cron', - hour=dt.hour, - minute=dt.minute, + hour=dt.hour, minute=dt.minute, + timezone=user["timezone"], args=[user["user_id"]], id=str(user["user_id"]), replace_existing=True diff --git a/database.py b/database.py index a61e82b..a45c1a4 100644 --- a/database.py +++ b/database.py @@ -13,7 +13,8 @@ async def init_db(): user_id INTEGER PRIMARY KEY, lat REAL, lon REAL, - time TEXT + time TEXT, + timezone TEXT ) ''') await db.commit() @@ -25,16 +26,17 @@ async def get_user(user_id: int): async with db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) as cursor: return await cursor.fetchone() -async def save_user_location(user_id: int, lat: float, lon: float): - """Сохраняет или обновляет только координаты пользователя.""" +async def save_user_location(user_id: int, lat: float, lon: float, timezone: str): + """Сохраняет или обновляет координаты и часовой пояс пользователя.""" async with aiosqlite.connect(DB_PATH) as db: await db.execute(''' - INSERT INTO users (user_id, lat, lon) - VALUES (?, ?, ?) + INSERT INTO users (user_id, lat, lon, timezone) + VALUES (?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET lat=excluded.lat, - lon=excluded.lon - ''', (user_id, lat, lon)) + lon=excluded.lon, + timezone=excluded.timezone + ''', (user_id, lat, lon, timezone)) await db.commit() async def update_user_time(user_id: int, time: str): diff --git a/weather.py b/weather.py index 51a19c2..3510369 100644 --- a/weather.py +++ b/weather.py @@ -1,5 +1,7 @@ import aiohttp from datetime import datetime +import asyncio +import logging async def get_coords_by_city(city_name: str): """Ищет координаты по названию города.""" @@ -9,18 +11,31 @@ async def get_coords_by_city(city_name: str): try: async with session.get(url, timeout=5) as response: if response.status != 200: - return None, None, None + return None, None, None, None data = await response.json() results = data.get("results") if not results: - return None, None, None + return None, None, None, None city_data = results[0] - return city_data.get("latitude"), city_data.get("longitude"), city_data.get("name", city_name) + return ( + city_data.get("latitude"), + city_data.get("longitude"), + city_data.get("name", city_name), + city_data.get("timezone") + ) except Exception: - return None, None, None + return None, None, None, None + +async def get_timezone_by_coords(lat: float, lon: float) -> str | None: + """Определяет часовой пояс по координатам, делая запрос к API погоды.""" + # Делаем минимальный запрос к API, чтобы получить часовой пояс + data = await fetch_weather_data(lat, lon, 1) + if data and "timezone" in data: + return data["timezone"] + return None async def fetch_weather_data(lat: float, lon: float, days: int): """Базовая функция для запроса данных о погоде на N дней.""" @@ -30,14 +45,20 @@ async def fetch_weather_data(lat: float, lon: float, days: int): f"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max" f"&timezone=auto&forecast_days={days}" ) - async with aiohttp.ClientSession() as session: - try: - async with session.get(url, timeout=5) as response: - if response.status != 200: - return None - return await response.json() - except Exception: - return None + # Логика с повторными попытками для надежности + for attempt in range(3): + async with aiohttp.ClientSession() as session: + try: + async with session.get(url, timeout=10) as response: + if response.status == 200: + return await response.json() + logging.warning(f"Попытка {attempt+1}: API погоды вернуло статус {response.status}") + except Exception as e: + logging.warning(f"Попытка {attempt+1}: Ошибка при запросе к API погоды: {e}") + + if attempt < 2: # Не спим после последней попытки + await asyncio.sleep(attempt + 2) # Спим 2, 3 секунды + return None async def get_daily_weather_summary(lat: float, lon: float) -> str: """Прогноз на сегодня."""