Skip to content

Convenience functionalty boh #4865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changes/unreleased/4861.HEoGVs2mYXWzqMahi6SEhV.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
features = "Added a two methods for BusinessOpeningHours"
[[pull_requests]]
uid = "4326"
author_uid = "Aweryc"
closes_threads = ["4194"]
4 changes: 2 additions & 2 deletions src/telegram/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=missing-module-docstring
# ruff: noqa: T201, D100, S603, S607
# ruff: noqa: T201, D100, S607
import subprocess
import sys
from typing import Optional
Expand All @@ -28,7 +28,7 @@

def _git_revision() -> Optional[str]:
try:
output = subprocess.check_output(
output = subprocess.check_output( # noqa: S603
["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT
)
except (subprocess.SubprocessError, OSError):
Expand Down
89 changes: 87 additions & 2 deletions src/telegram/_business.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from zoneinfo import ZoneInfo

from telegram._chat import Chat
from telegram._files.location import Location
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp, verify_timezone
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import (
Expand Down Expand Up @@ -494,7 +495,7 @@ class BusinessOpeningHoursInterval(TelegramObject):

Examples:
A day has (24 * 60 =) 1440 minutes, a week has (7 * 1440 =) 10080 minutes.
Starting the the minute's sequence from Monday, example values of
Starting the minute's sequence from Monday, example values of
:attr:`opening_minute`, :attr:`closing_minute` will map to the following day times:

* Monday - 8am to 8:30pm:
Expand Down Expand Up @@ -616,6 +617,90 @@ def __init__(

self._freeze()

def get_opening_hours_for_day(
self, date: dtm.date, time_zone: Optional[ZoneInfo] = None
) -> tuple[tuple[dtm.datetime, dtm.datetime], ...]:
"""Returns the opening hours intervals for a specific day as datetime objects.

.. versionadded:: NEXT.VERSION


Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`time_zone_name` and :attr:`opening_hours` are equal.
Args:
date (:obj:`datetime.date`): The date to get opening hours for.
Only the weekday component
is used to determine matching opening intervals.
time_zone (:obj:`zoneinfo.ZoneInfo`, optional): Timezone to use for the returned
datetime objects. If not specified, the returned datetime objects
will be timezone-naive.

Returns:
tuple[tuple[:obj:`datetime.datetime`, :obj:`datetime.datetime`], ...]:
A tuple of datetime pairs representing opening and closing times for the specified day.
Each pair consists of (opening_time, closing_time). Returns an empty tuple if there are
no opening hours for the given day.
"""

week_day = date.weekday()
res = []

for interval in self.opening_hours:
int_open = interval.opening_time
int_close = interval.closing_time
if int_open[0] == week_day:
res.append(
(
dtm.datetime(
year=date.year,
month=date.month,
day=date.day,
hour=int_open[1],
minute=int_open[2],
tzinfo=verify_timezone(time_zone),
),
dtm.datetime(
year=date.year,
month=date.month,
day=date.day,
hour=int_close[1],
minute=int_close[2],
tzinfo=verify_timezone(time_zone),
),
)
)

return tuple(res)

def is_open(self, dt: dtm.datetime) -> bool:
"""Check if the business is open at the specified datetime.

.. versionadded:: NEXT.VERSION

Args:
dt (:obj:`datetime.datetime`): The datetime to check.
If timezone-aware, the check will be performed in that timezone.
If timezone-naive, the check will be performed in the
timezone specified by :attr:`time_zone_name`.
Returns:
:obj:`bool`: True if the business is open at the specified time, False otherwise.
"""

if dt.tzinfo is None:
dt_utc = dt
else:
dt_utc = dt.astimezone(verify_timezone(ZoneInfo(self.time_zone_name)))

weekday = dt_utc.weekday()
minute_of_week = weekday * 1440 + dt_utc.hour * 60 + dt_utc.minute

for interval in self.opening_hours:
if interval.opening_minute <= minute_of_week < interval.closing_minute:
return True

return False

@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessOpeningHours":
"""See :meth:`telegram.TelegramObject.de_json`."""
Expand Down
1 change: 0 additions & 1 deletion src/telegram/_passport/encryptedpassportelement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
# flake8: noqa: E501
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
Expand Down
1 change: 0 additions & 1 deletion src/telegram/_payment/stars/staramount.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains an object that represents a Telegram StarAmount."""


Expand Down
23 changes: 23 additions & 0 deletions src/telegram/_utils/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import contextlib
import datetime as dtm
import time
import zoneinfo
from typing import TYPE_CHECKING, Optional, Union

if TYPE_CHECKING:
Expand Down Expand Up @@ -224,3 +225,25 @@ def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:
if dt_obj.tzinfo is None:
dt_obj = dt_obj.replace(tzinfo=dtm.timezone.utc)
return dt_obj.timestamp()


def verify_timezone(
tz: Optional[Union[dtm.tzinfo, zoneinfo.ZoneInfo]],
) -> Optional[Union[zoneinfo.ZoneInfo, dtm.tzinfo]]:
"""
Verifies that the given timezone is a valid timezone.
"""

if tz is None:
return None
if isinstance(tz, (dtm.tzinfo, zoneinfo.ZoneInfo)):
return tz

try:
return zoneinfo.ZoneInfo(tz)
except zoneinfo.ZoneInfoNotFoundError as err:
raise zoneinfo.ZoneInfoNotFoundError(
f"No time zone found with key {tz}. "
f"Make sure to use a valid time zone name and "
f"correct install tzdata (https://pypi.org/project/tzdata/)"
) from err
54 changes: 53 additions & 1 deletion tests/_utils/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import pytest

from telegram._utils import datetime as tg_dtm
from telegram._utils.datetime import verify_timezone
from telegram.error import TelegramError
from telegram.ext import Defaults

# sample time specification values categorised into absolute / delta / time-of-day
Expand Down Expand Up @@ -168,7 +170,7 @@ def test_to_timestamp(self):
assert tg_dtm.to_timestamp(i) == int(tg_dtm.to_float_timestamp(i)), f"Failed for {i}"

def test_to_timestamp_none(self):
# this 'convenience' behaviour has been left left for backwards compatibility
# this 'convenience' behaviour has been left for backwards compatibility
assert tg_dtm.to_timestamp(None) is None

def test_from_timestamp_none(self):
Expand All @@ -192,3 +194,53 @@ def test_extract_tzinfo_from_defaults(self, tz_bot, bot, raw_bot):
assert tg_dtm.extract_tzinfo_from_defaults(tz_bot) == tz_bot.defaults.tzinfo
assert tg_dtm.extract_tzinfo_from_defaults(bot) is None
assert tg_dtm.extract_tzinfo_from_defaults(raw_bot) is None

def test_with_zoneinfo_object(self):
"""Test with a valid zoneinfo.ZoneInfo object."""
tz = zoneinfo.ZoneInfo("Europe/Paris")
result = verify_timezone(tz)
assert result == tz

def test_with_datetime_tzinfo(self):
"""Test with a datetime.tzinfo object."""

class CustomTZ(dtm.tzinfo):
def utcoffset(self, dt):
return dtm.timedelta(hours=2)

def dst(self, dt):
return dtm.timedelta(0)

tz = CustomTZ()
result = verify_timezone(tz)
assert result == tz

def test_with_valid_timezone_string(self):
"""Test with a valid timezone string."""
tz = "Asia/Tokyo"
result = verify_timezone(tz)
assert isinstance(result, zoneinfo.ZoneInfo)
assert str(result) == "Asia/Tokyo"

def test_with_none(self):
"""Test with None input."""
assert verify_timezone(None) is None

def test_with_invalid_timezone_string(self):
"""Test with an invalid timezone string."""
with pytest.raises(TelegramError, match="No time zone found"):
verify_timezone("Invalid/Timezone")

def test_with_empty_string(self):
"""Test with empty string input."""
with pytest.raises(TelegramError, match="No time zone found"):
verify_timezone("")

def test_with_non_timezone_object(self):
"""Test with an object that isn't a timezone."""
with pytest.raises(TelegramError, match="No time zone found"):
verify_timezone(123) # integer
with pytest.raises(TelegramError, match="No time zone found"):
verify_timezone({"key": "value"}) # dict
with pytest.raises(TelegramError, match="No time zone found"):
verify_timezone([]) # empty list
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy