Attempt to work around timezones

This commit is contained in:
Profitroll 2023-09-24 23:47:09 +02:00
parent b0f76f4c49
commit 3fa2f5a800
Signed by: profitroll
GPG Key ID: FA35CAB49DACD3B2
13 changed files with 99 additions and 36 deletions

View File

@ -1,6 +1,9 @@
from dataclasses import dataclass
from typing import Union
from bson import ObjectId
from pytz import timezone as pytz_timezone
from pytz.tzinfo import BaseTzInfo, DstTzInfo
from classes.point import Point
from modules.database import col_locations
@ -22,7 +25,7 @@ class Location:
name: str
location: Point
country: int
timezone: str
timezone: Union[BaseTzInfo, DstTzInfo]
@classmethod
async def get(cls, id: int):
@ -32,6 +35,7 @@ class Location:
raise ValueError(f"No location with ID {id} found.")
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
db_entry["timezone"] = pytz_timezone(db_entry["timezone"]) # type: ignore
return cls(**db_entry)
@ -43,6 +47,7 @@ class Location:
raise ValueError(f"No location with name {name} found.")
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
db_entry["timezone"] = pytz_timezone(db_entry["timezone"]) # type: ignore
return cls(**db_entry)
@ -54,5 +59,6 @@ class Location:
raise ValueError(f"No location near {lat}, {lon} found.")
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
db_entry["timezone"] = pytz_timezone(db_entry["timezone"]) # type: ignore
return cls(**db_entry)

View File

@ -46,6 +46,7 @@
"import_invalid": "Dies ist kein gültiges Abfallterminen JSON.",
"import": "Okay. Senden Sie mir ein gültiges JSON.",
"locale_choice": "Prima. Bitte wählen Sie die Sprache mit der Tastatur unten.",
"location_empty": "Sie haben keinen Standort festgelegt. Verwenden Sie /setup, um Ihren Standort auszuwählen.",
"location_name_empty": "Es konnten keine Orte mit diesem Namen gefunden werden. Versuchen Sie, ihn umzuformulieren, oder stellen Sie sicher, dass Sie dieselbe Sprache und denselben Namen verwenden, wie er von Ihren örtlichen Behörden im Müllabfuhrplan angegeben ist.\n\n{cancel_notice}",
"location_name_invalid": "Bitte senden Sie den Namen des Ortes als Text. {cancel_notice}",
"location_name": "Bitte senden Sie mir einen Standortnamen. Es sollte der Name sein, der im Müllabfuhrplan Ihrer örtlichen Behörde verwendet wird. In der Regel ist dies der Name des Bezirks oder sogar der Stadt selbst.",
@ -72,7 +73,6 @@
"toggle_enabled_location": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time} am **{name}**.",
"toggle_enabled": "🔔 Benachrichtigungen wurden aktiviert {offset} T. vor der Sammlung um {time}. Verwenden Sie /setup, um Ihren Standort auszuwählen.",
"toggle": "Führen Sie /toggle aus, um Benachrichtigungen zu aktivieren.",
"upcoming_empty_location": "Sie haben keinen Standort festgelegt. Verwenden Sie /setup, um Ihren Standort auszuwählen.",
"upcoming_empty": "Keine Müllabfuhr-Einträge für die nächsten 30 Tage bei **{name}** gefunden",
"upcoming": "Bevorstehende Müllabfuhr:\n\n{entries}"
},
@ -95,4 +95,4 @@
"callbacks": {
"locale_set": "Ihre Sprache ist jetzt: {locale}"
}
}
}

View File

@ -46,6 +46,7 @@
"import_invalid": "This is not a valid garbage collection JSON.",
"import": "Alright. Send me a valid JSON.",
"locale_choice": "Alright. Please choose the language using keyboard below.",
"location_empty": "You have no location set. Use /setup to select your location first.",
"location_name_empty": "Could not find any locations by this name. Try rephrasing it or make sure you use the same location language and name itself as it in written by your local authorities in garbage collection schedule.\n\n{cancel_notice}",
"location_name_invalid": "Please, send the name of the location as a text. {cancel_notice}",
"location_name": "Please, send me a location name. It should be the name used in your local authorities' garbage collection schedule. This usually is a name of the district or even the town itself.",
@ -72,7 +73,6 @@
"toggle_enabled_location": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time} at the **{name}**.",
"toggle_enabled": "🔔 Notifications have been enabled {offset} d. before garbage collection at {time}. Use /setup to select your location.",
"toggle": "Execute /toggle to enable notifications.",
"upcoming_empty_location": "You have no location set. Use /setup to select your location.",
"upcoming_empty": "No garbage collection entries found for the next 30 days at **{name}**",
"upcoming": "Upcoming garbage collection:\n\n{entries}"
},

View File

@ -22,6 +22,7 @@
"import_invalid": "Це недійсний JSON даних збору сміття.",
"import": "Гаразд. Надішліть правильний JSON.",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
"location_empty": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"location_name_empty": "Не вдалося знайти жодного населеного пункту з такою назвою. Спробуйте перефразувати назву або переконайтеся, що Ви використовуєте ту саму мову та назву, що й місцева влада у графіку вивезення сміття.\n\n{cancel_notice}",
"location_name_invalid": "Будь ласка, надішліть назву місця у вигляді тексту. {cancel_notice}",
"location_name": "Будь ласка, надішліть мені назву населеного пункту. Це має бути назва, яка використовується у графіку вивезення сміття Вашою місцевою владою. Зазвичай це назва району або міста.",
@ -47,7 +48,6 @@
"toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.",
"toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.",
"toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.",
"upcoming_empty_location": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**",
"upcoming": "Найближчі вивози сміття:\n\n{entries}"
},
@ -95,4 +95,4 @@
"set_offset": "Кількість днів",
"set_time": "Час у вигляді ГГ:ХХ"
}
}
}

View File

@ -46,6 +46,7 @@
"import_invalid": "Це недійсний JSON даних збору сміття.",
"import": "Гаразд. Надішліть правильний JSON.",
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
"location_empty": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"location_name_empty": "Не вдалося знайти жодного населеного пункту з такою назвою. Спробуйте перефразувати назву або переконайтеся, що Ви використовуєте ту саму мову та назву, що й місцева влада у графіку вивезення сміття.\n\n{cancel_notice}",
"location_name_invalid": "Будь ласка, надішліть назву місця у вигляді тексту. {cancel_notice}",
"location_name": "Будь ласка, надішліть мені назву населеного пункту. Це має бути назва, яка використовується у графіку вивезення сміття Вашою місцевою владою. Зазвичай це назва району або міста.",
@ -72,7 +73,6 @@
"toggle_enabled_location": "🔔 Сповіщення увімкнено за {offset} д. до вивезення сміття о {time} для локації **{name}**.",
"toggle_enabled": "🔔 Сповіщення було увімкнено за {offset} д. до вивезення сміття о {time}. Оберіть своє розташування за допомогою /setup.",
"toggle": "Використовуйте /toggle, щоб увімкнути сповіщення.",
"upcoming_empty_location": "У Вас не встановлено локацію вивозу. Оберіть свою локацію за допомогою /setup.",
"upcoming_empty": "Не знайдено записів про вивезення сміття на найближчі 30 днів для **{name}**",
"upcoming": "Найближчі вивози сміття:\n\n{entries}"
},

View File

@ -2,25 +2,25 @@ import logging
from datetime import datetime, timedelta, timezone
from libbot.pyrogram.classes import PyroClient
from pytz import timezone as pytz_timezone
from classes.enums import GarbageType
from classes.location import Location
from classes.pyrouser import PyroUser
from modules.database import col_entries, col_users
from modules.utils import from_utc
logger = logging.getLogger(__name__)
async def remind(app: PyroClient) -> None:
now = datetime.now()
utcnow = datetime.utcnow()
users = await col_users.find(
{"time_hour": now.hour, "time_minute": now.minute}
{"time_hour": utcnow.hour, "time_minute": utcnow.minute}
).to_list()
for user_db in users:
user = PyroUser(**user_db)
user = await PyroUser.from_dict(**user_db)
if not user.enabled or user.location is None:
continue
@ -30,12 +30,10 @@ async def remind(app: PyroClient) -> None:
except ValueError:
continue
user_date = (
datetime.now(pytz_timezone(location.timezone)).replace(
second=0, microsecond=0
)
+ timedelta(days=user.offset)
).replace(tzinfo=timezone.utc)
user_date = from_utc(
datetime(1970, 1, 1, user.time_hour, user.time_minute),
user.location.timezone.zone,
)
entries = await col_entries.find(
{

View File

@ -0,0 +1,38 @@
from datetime import datetime
from typing import Union
from pytz import UTC
from pytz import timezone as pytz_timezone
def to_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
"""Move timezone unaware datetime object to UTC timezone and return it.
### Args:
* date (`datetime`): Datetime to be converted.
* timezone (`Union[str, None] = None`): Timezone name (must be pytz-compatible). Defaults to `None` (UTC).
### Returns:
* `datetime`: Timezone unaware datetime in UTC with timezone's offset applied to it.
"""
timezone = "UTC" if timezone is None else timezone
return pytz_timezone(timezone).localize(date).astimezone(UTC).replace(tzinfo=None)
def from_utc(date: datetime, timezone: Union[str, None] = None) -> datetime:
"""Move timezone unaware datetime object to the timezone specified and return it.
### Args:
* date (`datetime`): Datetime to be converted.
* timezone (`Union[str, None] = None`): Timezone name (must be pytz-compatible). Defaults to `None` (UTC).
### Returns:
* `datetime`: Timezone unaware datetime in timezone provided with offset from UTC applied to it.
"""
timezone = "UTC" if timezone is None else timezone
return (
pytz_timezone("UTC")
.localize(date)
.astimezone(pytz_timezone(timezone))
.replace(tzinfo=None)
)

View File

@ -7,6 +7,7 @@ from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.utils import from_utc
logger = logging.getLogger(__name__)
@ -17,6 +18,12 @@ logger = logging.getLogger(__name__)
async def command_set_offset(app: PyroClient, message: Message):
user = await app.find_user(message.from_user)
if user.location is None:
await message.reply_text(
app._("location_empty", "messages", locale=user.locale)
)
return
await message.reply_text(
app._("set_offset", "messages", locale=user.locale),
reply_markup=ForceReply(
@ -58,8 +65,9 @@ async def command_set_offset(app: PyroClient, message: Message):
logger.info("User %s has set offset to %s", user.id, offset)
garbage_time = datetime(
1970, 1, 1, hour=user.time_hour, minute=user.time_minute
garbage_time = from_utc(
datetime(1970, 1, 1, user.time_hour, user.time_minute),
None if user.location is None else user.location.timezone.zone,
).strftime(app._("time", "formats"))
await answer.reply_text(

View File

@ -7,6 +7,7 @@ from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.utils import to_utc
logger = logging.getLogger(__name__)
@ -17,6 +18,12 @@ logger = logging.getLogger(__name__)
async def command_set_time(app: PyroClient, message: Message):
user = await app.find_user(message.from_user)
if user.location is None:
await message.reply_text(
app._("location_empty", "messages", locale=user.locale)
)
return
await message.reply_text(
app._("set_time", "messages", locale=user.locale),
reply_markup=ForceReply(
@ -48,7 +55,10 @@ async def command_set_time(app: PyroClient, message: Message):
break
user_time = datetime.strptime(answer.text, "%H:%M")
parsed_time = datetime.strptime(answer.text, "%H:%M").replace(
year=1970, month=1, day=1, second=0, microsecond=0
)
user_time = to_utc(parsed_time, user.location.timezone.zone)
await user.update_time(hour=user_time.hour, minute=user_time.minute)
@ -58,7 +68,7 @@ async def command_set_time(app: PyroClient, message: Message):
user_time.strftime("%H:%M"),
)
garbage_time = user_time.strftime(app._("time", "formats"))
garbage_time = parsed_time.strftime(app._("time", "formats"))
await answer.reply_text(
app._("set_time_finished", "messages", locale=user.locale).format(

View File

@ -11,6 +11,7 @@ from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.search_name import search_name
from modules.search_nearby import search_nearby
from modules.utils import from_utc
logger = logging.getLogger(__name__)
@ -73,9 +74,10 @@ async def command_setup(app: PyroClient, message: Message):
await user.update_location(location.id)
user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime(
app._("time", "formats", locale=user.locale)
)
user_time = from_utc(
datetime(1970, 1, 1, user.time_hour, user.time_minute),
None if user.location is None else user.location.timezone.zone,
).strftime(app._("time", "formats", locale=user.locale))
await message.reply_text(
app._("setup_finished", "messages", locale=user.locale).format(

View File

@ -11,6 +11,7 @@ from pyrogram.types import (
from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.utils import from_utc
@PyroClient.on_message(
@ -85,9 +86,10 @@ async def command_start(app: PyroClient, message: Message):
await user.update_location(location.id)
user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime(
app._("time", "formats", locale=user.locale)
)
user_time = from_utc(
datetime(1970, 1, 1, user.time_hour, user.time_minute),
None if user.location is None else user.location.timezone.zone,
).strftime(app._("time", "formats", locale=user.locale))
await answer.reply_text(
app._("start_selection_yes", "messages", locale=user.locale).format(
name=location.name, offset=user.offset, time=user_time

View File

@ -5,6 +5,7 @@ from pyrogram.types import Message
from classes.pyroclient import PyroClient
from modules import custom_filters
from modules.utils import from_utc
@PyroClient.on_message(
@ -21,7 +22,10 @@ async def command_toggle(app: PyroClient, message: Message):
)
return
user_time = datetime(1970, 1, 1, user.time_hour, user.time_minute).strftime("%H:%M")
user_time = from_utc(
datetime(1970, 1, 1, user.time_hour, user.time_minute),
None if user.location is None else user.location.timezone.zone,
).strftime(app._("time", "formats"))
if user.location is None:
await message.reply_text(

View File

@ -2,7 +2,6 @@ from datetime import datetime, timedelta, timezone
from pyrogram import filters
from pyrogram.types import Message
from pytz import timezone as pytz_timezone
from classes.garbage_entry import GarbageEntry
from classes.pyroclient import PyroClient
@ -18,19 +17,15 @@ async def command_upcoming(app: PyroClient, message: Message):
if user.location is None:
await message.reply_text(
app._("upcoming_empty_location", "messages", locale=user.locale)
app._("location_empty", "messages", locale=user.locale)
)
return
date_min = (
datetime.now(pytz_timezone(user.location.timezone)).replace(
second=0, microsecond=0
)
datetime.now(user.location.timezone).replace(second=0, microsecond=0)
).replace(tzinfo=timezone.utc)
date_max = (
datetime.now(pytz_timezone(user.location.timezone)).replace(
second=0, microsecond=0
)
datetime.now(user.location.timezone).replace(second=0, microsecond=0)
+ timedelta(days=30)
).replace(tzinfo=timezone.utc)