timezone fix

This commit is contained in:
2026-04-02 09:53:14 +03:00
parent e8544ebb38
commit 684a952158
3 changed files with 67 additions and 30 deletions

36
bot.py
View File

@@ -17,7 +17,8 @@ from weather import (
get_daily_weather_summary, get_daily_weather_summary,
get_coords_by_city, get_coords_by_city,
get_tomorrow_weather_summary, get_tomorrow_weather_summary,
get_weekly_weather_summary get_weekly_weather_summary,
get_timezone_by_coords
) )
import database as db # Импортируем нашу базу данных 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): async def process_location_input(message: types.Message, state: FSMContext):
user_id = message.from_user.id user_id = message.from_user.id
city_name_display = "Выбранная локация" city_name_display = "Выбранная локация"
timezone = None
if message.location: if message.location:
lat = message.location.latitude lat = message.location.latitude
lon = message.location.longitude lon = message.location.longitude
# Для координат нужно определить часовой пояс отдельным запросом
timezone = await get_timezone_by_coords(lat, lon)
if not timezone:
await message.answer("Не удалось определить часовой пояс для вашей геопозиции. Попробуйте ввести название города.")
return
elif message.text: 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: if not lat:
await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название.") await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название.")
return return
city_name_display = found_name city_name_display = found_name
else: else:
return 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) user = await db.get_user(user_id)
if user and user["time"]: if user and user["time"]:
@@ -114,7 +122,7 @@ async def process_location_input(message: types.Message, state: FSMContext):
else: else:
await state.set_state(WeatherSetup.waiting_for_time) await state.set_state(WeatherSetup.waiting_for_time)
await message.answer( await message.answer(
f"Отлично! Локация **{city_name_display}** найдена.\n\nТеперь напиши время (в формате ЧЧ:ММ):", f"Отлично! Локация **{city_name_display}** найдена (часовой пояс: {timezone}).\n\nТеперь напиши время для ежедневной рассылки (в формате ЧЧ:ММ):",
reply_markup=ReplyKeyboardRemove() reply_markup=ReplyKeyboardRemove()
) )
@@ -136,6 +144,12 @@ async def process_time(message: types.Message, state: FSMContext):
try: try:
dt = datetime.strptime(time_text, "%H:%M") 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) 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( scheduler.add_job(
send_weather_update, send_weather_update,
trigger='cron', trigger='cron',
hour=dt.hour, hour=dt.hour, minute=dt.minute,
minute=dt.minute, timezone=user["timezone"],
args=[user_id], args=[user_id],
id=str(user_id), id=str(user_id),
replace_existing=True replace_existing=True
@@ -204,8 +218,8 @@ async def restore_jobs():
scheduler.add_job( scheduler.add_job(
send_weather_update, send_weather_update,
trigger='cron', trigger='cron',
hour=dt.hour, hour=dt.hour, minute=dt.minute,
minute=dt.minute, timezone=user["timezone"],
args=[user["user_id"]], args=[user["user_id"]],
id=str(user["user_id"]), id=str(user["user_id"]),
replace_existing=True replace_existing=True

View File

@@ -13,7 +13,8 @@ async def init_db():
user_id INTEGER PRIMARY KEY, user_id INTEGER PRIMARY KEY,
lat REAL, lat REAL,
lon REAL, lon REAL,
time TEXT time TEXT,
timezone TEXT
) )
''') ''')
await db.commit() 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: async with db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) as cursor:
return await cursor.fetchone() 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: async with aiosqlite.connect(DB_PATH) as db:
await db.execute(''' await db.execute('''
INSERT INTO users (user_id, lat, lon) INSERT INTO users (user_id, lat, lon, timezone)
VALUES (?, ?, ?) VALUES (?, ?, ?, ?)
ON CONFLICT(user_id) DO UPDATE SET ON CONFLICT(user_id) DO UPDATE SET
lat=excluded.lat, lat=excluded.lat,
lon=excluded.lon lon=excluded.lon,
''', (user_id, lat, lon)) timezone=excluded.timezone
''', (user_id, lat, lon, timezone))
await db.commit() await db.commit()
async def update_user_time(user_id: int, time: str): async def update_user_time(user_id: int, time: str):

View File

@@ -1,5 +1,7 @@
import aiohttp import aiohttp
from datetime import datetime from datetime import datetime
import asyncio
import logging
async def get_coords_by_city(city_name: str): async def get_coords_by_city(city_name: str):
"""Ищет координаты по названию города.""" """Ищет координаты по названию города."""
@@ -9,18 +11,31 @@ async def get_coords_by_city(city_name: str):
try: try:
async with session.get(url, timeout=5) as response: async with session.get(url, timeout=5) as response:
if response.status != 200: if response.status != 200:
return None, None, None return None, None, None, None
data = await response.json() data = await response.json()
results = data.get("results") results = data.get("results")
if not results: if not results:
return None, None, None return None, None, None, None
city_data = results[0] 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: 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): async def fetch_weather_data(lat: float, lon: float, days: int):
"""Базовая функция для запроса данных о погоде на N дней.""" """Базовая функция для запроса данных о погоде на 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"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"
f"&timezone=auto&forecast_days={days}" f"&timezone=auto&forecast_days={days}"
) )
async with aiohttp.ClientSession() as session: # Логика с повторными попытками для надежности
try: for attempt in range(3):
async with session.get(url, timeout=5) as response: async with aiohttp.ClientSession() as session:
if response.status != 200: try:
return None async with session.get(url, timeout=10) as response:
return await response.json() if response.status == 200:
except Exception: return await response.json()
return None 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: async def get_daily_weather_summary(lat: float, lon: float) -> str:
"""Прогноз на сегодня.""" """Прогноз на сегодня."""