This commit is contained in:
Hinrich Mahler 2024-12-31 10:14:24 +01:00
parent 1f8124f3cb
commit 9cabf0e03b
4 changed files with 71 additions and 49 deletions

View file

@ -126,6 +126,12 @@ def to_float_timestamp(
return reference_timestamp + time_object return reference_timestamp + time_object
if tzinfo is None: if tzinfo is None:
# We do this here rather than in the signature to ensure that we can make calls like
# to_float_timestamp(
# time, tzinfo=bot.defaults.tzinfo if bot.defaults else None
# )
# This ensures clean separation of concerns, i.e. the default timezone should not be
# the responsibility of the caller
tzinfo = UTC tzinfo = UTC
if isinstance(time_object, dtm.time): if isinstance(time_object, dtm.time):
@ -137,6 +143,8 @@ def to_float_timestamp(
aware_datetime = dtm.datetime.combine(reference_date, time_object) aware_datetime = dtm.datetime.combine(reference_date, time_object)
if aware_datetime.tzinfo is None: if aware_datetime.tzinfo is None:
# datetime.combine uses the tzinfo of `time_object`, which might be None
# so we still need to localize
aware_datetime = localize(aware_datetime, tzinfo) aware_datetime = localize(aware_datetime, tzinfo)
# if the time of day has passed today, use tomorrow # if the time of day has passed today, use tomorrow

View file

@ -197,6 +197,8 @@ class JobQueue(Generic[CCT]):
dtm.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time dtm.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time
) )
if date_time.tzinfo is None: if date_time.tzinfo is None:
# dtm.combine uses the tzinfo of `time`, which might be None, so we still have
# to localize it
date_time = localize(date_time, self.scheduler.timezone) date_time = localize(date_time, self.scheduler.timezone)
if shift_day and date_time <= dtm.datetime.now(UTC): if shift_day and date_time <= dtm.datetime.now(UTC):
date_time += dtm.timedelta(days=1) date_time += dtm.timedelta(days=1)

View file

@ -41,15 +41,11 @@ from telegram import (
Sticker, Sticker,
TelegramObject, TelegramObject,
) )
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram.constants import InputMediaType from telegram.constants import InputMediaType
from telegram.ext import Defaults, ExtBot from telegram.ext import Defaults, ExtBot
from telegram.request import RequestData from telegram.request import RequestData
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
if TEST_WITH_OPT_DEPS:
pass
FORWARD_REF_PATTERN = re.compile(r"ForwardRef\('(?P<class_name>\w+)'\)") FORWARD_REF_PATTERN = re.compile(r"ForwardRef\('(?P<class_name>\w+)'\)")
""" A pattern to find a class name in a ForwardRef typing annotation. """ A pattern to find a class name in a ForwardRef typing annotation.
@ -396,6 +392,15 @@ def make_assertion_for_link_preview_options(
) )
_EUROPE_BERLIN_TS = to_timestamp(
dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
)
_UTC_TS = to_timestamp(dtm.datetime(2000, 1, 1, 0), tzinfo=zoneinfo.ZoneInfo("UTC"))
_AMERICA_NEW_YORK_TS = to_timestamp(
dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("America/New_York"))
)
async def make_assertion( async def make_assertion(
url, url,
request_data: RequestData, request_data: RequestData,
@ -535,14 +540,11 @@ async def make_assertion(
for key in date_keys: for key in date_keys:
date_param = data.pop(key) date_param = data.pop(key)
if date_param: if date_param:
if manual_value_expected and date_param != 946681200: if manual_value_expected and date_param != _EUROPE_BERLIN_TS:
pytest.fail(f"Non-naive `{key}` should have been interpreted as Europe/Berlin.") pytest.fail(f"Non-naive `{key}` should have been interpreted as Europe/Berlin.")
if ( if not any((manually_passed_value, expected_defaults_value)) and date_param != _UTC_TS:
not any((manually_passed_value, expected_defaults_value))
and date_param != 946684800
):
pytest.fail(f"Naive `{key}` should have been interpreted as UTC") pytest.fail(f"Naive `{key}` should have been interpreted as UTC")
if default_value_expected and date_param != 946702800: if default_value_expected and date_param != _AMERICA_NEW_YORK_TS:
pytest.fail(f"Naive `{key}` should have been interpreted as America/New_York") pytest.fail(f"Naive `{key}` should have been interpreted as America/New_York")
if method_name in ["get_file", "get_small_file", "get_big_file"]: if method_name in ["get_file", "get_small_file", "get_big_file"]:
@ -641,35 +643,35 @@ async def check_defaults_handling(
request.post = assertion_callback request.post = assertion_callback
assert await method(**kwargs) in expected_return_values assert await method(**kwargs) in expected_return_values
# # 2: test that we get the manually passed non-None value # 2: test that we get the manually passed non-None value
# kwargs = build_kwargs( kwargs = build_kwargs(
# shortcut_signature, kwargs_need_default, manually_passed_value="non-None-value" shortcut_signature, kwargs_need_default, manually_passed_value="non-None-value"
# ) )
# assertion_callback = functools.partial( assertion_callback = functools.partial(
# make_assertion, make_assertion,
# manually_passed_value="non-None-value", manually_passed_value="non-None-value",
# kwargs_need_default=kwargs_need_default, kwargs_need_default=kwargs_need_default,
# method_name=method.__name__, method_name=method.__name__,
# return_value=return_value, return_value=return_value,
# expected_defaults_value=expected_defaults_value, expected_defaults_value=expected_defaults_value,
# ) )
# request.post = assertion_callback request.post = assertion_callback
# assert await method(**kwargs) in expected_return_values assert await method(**kwargs) in expected_return_values
#
# # 3: test that we get the manually passed None value # 3: test that we get the manually passed None value
# kwargs = build_kwargs( kwargs = build_kwargs(
# shortcut_signature, kwargs_need_default, manually_passed_value=None shortcut_signature, kwargs_need_default, manually_passed_value=None
# ) )
# assertion_callback = functools.partial( assertion_callback = functools.partial(
# make_assertion, make_assertion,
# manually_passed_value=None, manually_passed_value=None,
# kwargs_need_default=kwargs_need_default, kwargs_need_default=kwargs_need_default,
# method_name=method.__name__, method_name=method.__name__,
# return_value=return_value, return_value=return_value,
# expected_defaults_value=expected_defaults_value, expected_defaults_value=expected_defaults_value,
# ) )
# request.post = assertion_callback request.post = assertion_callback
# assert await method(**kwargs) in expected_return_values assert await method(**kwargs) in expected_return_values
except Exception as exc: except Exception as exc:
raise exc raise exc
finally: finally:

View file

@ -21,13 +21,12 @@ import calendar
import contextlib import contextlib
import datetime as dtm import datetime as dtm
import logging import logging
import platform
import time import time
import pytest import pytest
from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Defaults, Job, JobQueue from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Defaults, Job, JobQueue
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.pytest_classes import make_bot from tests.auxil.pytest_classes import make_bot
from tests.auxil.slots import mro_slots from tests.auxil.slots import mro_slots
@ -65,13 +64,13 @@ class TestNoJobQueue:
Job(None) Job(None)
@pytest.mark.skipif( # @pytest.mark.skipif(
not TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is installed" # not TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is installed"
) # )
@pytest.mark.skipif( # @pytest.mark.skipif(
bool(GITHUB_ACTION and platform.system() in ["Windows", "Darwin"]), # bool(GITHUB_ACTION and platform.system() in ["Windows", "Darwin"]),
reason="On Windows & MacOS precise timings are not accurate.", # reason="On Windows & MacOS precise timings are not accurate.",
) # )
@pytest.mark.flaky(10, 1) # Timings aren't quite perfect @pytest.mark.flaky(10, 1) # Timings aren't quite perfect
class TestJobQueue: class TestJobQueue:
result = 0 result = 0
@ -364,6 +363,17 @@ class TestJobQueue:
scheduled_time = job_queue.jobs()[0].next_t.timestamp() scheduled_time = job_queue.jobs()[0].next_t.timestamp()
assert scheduled_time == pytest.approx(expected_time) assert scheduled_time == pytest.approx(expected_time)
async def test_time_unit_dt_aware_time(self, job_queue, timezone):
# Testing running at a specific tz-aware time today
delta, now = 0.5, dtm.datetime.now(timezone)
expected_time = now + dtm.timedelta(seconds=delta)
when = expected_time.timetz()
expected_time = expected_time.timestamp()
job_queue.run_once(self.job_datetime_tests, when)
await asyncio.sleep(0.6)
assert self.job_time == pytest.approx(expected_time)
async def test_run_daily(self, job_queue): async def test_run_daily(self, job_queue):
delta, now = 1, dtm.datetime.now(UTC) delta, now = 1, dtm.datetime.now(UTC)
time_of_day = (now + dtm.timedelta(seconds=delta)).time() time_of_day = (now + dtm.timedelta(seconds=delta)).time()