Localize Received datetime Objects According to Defaults.tzinfo (#3632)

This commit is contained in:
Luca Bellanti 2023-04-18 16:16:23 +02:00 committed by GitHub
parent 934e4c9bd4
commit 7b116be344
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 376 additions and 40 deletions

View file

@ -72,6 +72,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Li-aung Yip <https://github.com/LiaungYip>`_
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
- `LRezende <https://github.com/lrezende>`_
- `Luca Bellanti <https://github.com/Trifase>`_
- `macrojames <https://github.com/macrojames>`_
- `Matheus Lemos <https://github.com/mlemosf>`_
- `Michael Dix <https://github.com/Eisberge>`_

View file

@ -55,3 +55,5 @@
.. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list.
.. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``.
.. |datetime_localization| replace:: The default timezone of the bot is used for localization, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.

View file

@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -54,6 +54,9 @@ class ChatInviteLink(TelegramObject):
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
has been expired.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
chat simultaneously after joining the chat via this invite link;
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@ -78,6 +81,9 @@ class ChatInviteLink(TelegramObject):
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
has been expired.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
of the chat simultaneously after joining the chat via this invite link;
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@ -152,7 +158,10 @@ class ChatInviteLink(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["creator"] = User.de_json(data.get("creator"), bot)
data["expire_date"] = from_timestamp(data.get("expire_date", None))
data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)

View file

@ -24,7 +24,7 @@ from telegram._chat import Chat
from telegram._chatinvitelink import ChatInviteLink
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@ -56,6 +56,9 @@ class ChatJoinRequest(TelegramObject):
chat (:class:`telegram.Chat`): Chat to which the request was sent.
from_user (:class:`telegram.User`): User that sent the join request.
date (:class:`datetime.datetime`): Date the request was sent.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
request. This number may have more than 32 significant bits and some programming
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@ -73,6 +76,9 @@ class ChatJoinRequest(TelegramObject):
chat (:class:`telegram.Chat`): Chat to which the request was sent.
from_user (:class:`telegram.User`): User that sent the join request.
date (:class:`datetime.datetime`): Date the request was sent.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
request. This number may have more than 32 significant bits and some programming
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@ -124,9 +130,12 @@ class ChatJoinRequest(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["date"] = from_timestamp(data.get("date", None))
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
return super().de_json(data=data, bot=bot)

View file

@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -125,7 +125,10 @@ class ChatMember(TelegramObject):
data["user"] = User.de_json(data.get("user"), bot)
if "until_date" in data:
data["until_date"] = from_timestamp(data["until_date"])
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@ -386,6 +389,9 @@ class ChatMemberRestricted(ChatMember):
.. versionadded:: 20.0
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
.. versionadded:: 20.1
@ -438,6 +444,9 @@ class ChatMemberRestricted(ChatMember):
.. versionadded:: 20.0
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
.. versionadded:: 20.1
@ -565,6 +574,9 @@ class ChatMemberBanned(ChatMember):
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
Attributes:
status (:obj:`str`): The member's status in the chat,
always :tg-const:`telegram.ChatMember.BANNED`.
@ -572,6 +584,9 @@ class ChatMemberBanned(ChatMember):
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
"""
__slots__ = ("until_date",)

View file

@ -25,7 +25,7 @@ from telegram._chatinvitelink import ChatInviteLink
from telegram._chatmember import ChatMember
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -52,6 +52,9 @@ class ChatMemberUpdated(TelegramObject):
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
@ -62,6 +65,9 @@ class ChatMemberUpdated(TelegramObject):
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
@ -118,9 +124,12 @@ class ChatMemberUpdated(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["date"] = from_timestamp(data.get("date"))
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)

View file

@ -56,7 +56,7 @@ from telegram._shared import ChatShared, UserShared
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._videochat import (
@ -121,6 +121,9 @@ class Message(TelegramObject):
sent on behalf of a chat.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
chat (:class:`telegram.Chat`): Conversation the message belongs to.
forward_from (:class:`telegram.User`, optional): For forwarded messages, sender of
the original message.
@ -132,6 +135,9 @@ class Message(TelegramObject):
users who disallow adding a link to their account in forwarded messages.
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel
post that was automatically forwarded to the connected discussion group.
@ -141,6 +147,9 @@ class Message(TelegramObject):
``reply_to_message`` fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
time. Converted to :class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
forwarded.
@ -338,6 +347,9 @@ class Message(TelegramObject):
sent on behalf of a chat.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
chat (:class:`telegram.Chat`): Conversation the message belongs to.
forward_from (:class:`telegram.User`): Optional. For forwarded messages, sender of the
original message.
@ -347,6 +359,9 @@ class Message(TelegramObject):
the original message in the channel.
forward_date (:class:`datetime.datetime`): Optional. For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel
post that was automatically forwarded to the connected discussion group.
@ -356,6 +371,9 @@ class Message(TelegramObject):
``reply_to_message`` fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited in Unix
time. Converted to :class:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
forwarded.
@ -850,17 +868,20 @@ class Message(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
data["date"] = from_timestamp(data["date"])
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
data["forward_from"] = User.de_json(data.get("forward_from"), bot)
data["forward_from_chat"] = Chat.de_json(data.get("forward_from_chat"), bot)
data["forward_date"] = from_timestamp(data.get("forward_date"))
data["forward_date"] = from_timestamp(data.get("forward_date"), tzinfo=loc_tzinfo)
data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
data["edit_date"] = from_timestamp(data.get("edit_date"))
data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
data["audio"] = Audio.de_json(data.get("audio"), bot)
data["document"] = Document.de_json(data.get("document"), bot)
data["animation"] = Animation.de_json(data.get("animation"), bot)

View file

@ -26,7 +26,7 @@ from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -173,6 +173,9 @@ class Poll(TelegramObject):
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
@ -206,6 +209,9 @@ class Poll(TelegramObject):
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
automatically closed.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
"""
__slots__ = (
@ -271,9 +277,12 @@ class Poll(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
data["close_date"] = from_timestamp(data.get("close_date"))
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)

View file

@ -29,7 +29,10 @@ Warning:
"""
import datetime as dtm # skipcq: PYL-W0406
import time
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
from telegram import Bot
# pytz is only available if it was installed as dependency of APScheduler, so we make a little
# workaround here
@ -162,7 +165,10 @@ def to_timestamp(
)
def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optional[dtm.datetime]:
def from_timestamp(
unixtime: Optional[int],
tzinfo: Optional[dtm.tzinfo] = None,
) -> Optional[dtm.datetime]:
"""
Converts an (integer) unix timestamp to a timezone aware datetime object.
:obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
@ -170,7 +176,8 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
Args:
unixtime (:obj:`int`): Integer POSIX timestamp.
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be
converted to. Defaults to UTC.
converted to. Defaults to :obj:`None`, in which case the returned datetime object will
be timezone aware and in UTC.
Returns:
Timezone aware equivalent :obj:`datetime.datetime` value if :paramref:`unixtime` is not
@ -179,9 +186,19 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
if unixtime is None:
return None
if tzinfo is not None:
return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo)
return dtm.datetime.utcfromtimestamp(unixtime)
return dtm.datetime.fromtimestamp(unixtime, tz=UTC if tzinfo is None else tzinfo)
def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]:
"""
Extracts the timezone info from the default values of the bot.
If the bot has no default values, :obj:`None` is returned.
"""
# We don't use `ininstance(bot, ExtBot)` here so that this works
# in `python-telegram-bot-raw` as well
if hasattr(bot, "defaults") and bot.defaults:
return bot.defaults.tzinfo
return None
def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:

View file

@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -149,10 +149,16 @@ class VideoChatScheduled(TelegramObject):
Args:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: NEXT.VERSION
|datetime_localization|
Attributes:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: NEXT.VERSION
|datetime_localization|
"""
__slots__ = ("start_date",)
@ -178,6 +184,9 @@ class VideoChatScheduled(TelegramObject):
if not data:
return None
data["start_date"] = from_timestamp(data["start_date"])
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)

View file

@ -21,7 +21,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@ -49,8 +49,11 @@ class WebhookInfo(TelegramObject):
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`, optional): Currently used webhook IP address.
last_error_date (:obj:`int`, optional): Unix time for the most recent error that happened
when trying to deliver an update via webhook.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
last_error_message (:obj:`str`, optional): Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
@ -62,18 +65,25 @@ class WebhookInfo(TelegramObject):
.. versionchanged:: 20.0
|sequenceclassargs|
last_synchronization_error_date (:obj:`int`, optional): Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: NEXT.VERSION
|datetime_localization|
Attributes:
url (:obj:`str`): Webhook URL, may be empty if webhook is not set up.
has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`): Optional. Currently used webhook IP address.
last_error_date (:obj:`int`): Optional. Unix time for the most recent error that happened
when trying to deliver an update via webhook.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: NEXT.VERSION
|datetime_localization|
last_error_message (:obj:`str`): Optional. Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS
@ -86,10 +96,14 @@ class WebhookInfo(TelegramObject):
* |tupleclassattrs|
* |alwaystuple|
last_synchronization_error_date (:obj:`int`): Optional. Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: NEXT.VERSION
|datetime_localization|
"""
__slots__ = (
@ -154,9 +168,12 @@ class WebhookInfo(TelegramObject):
if not data:
return None
data["last_error_date"] = from_timestamp(data.get("last_error_date"))
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["last_error_date"] = from_timestamp(data.get("last_error_date"), tzinfo=loc_tzinfo)
data["last_synchronization_error_date"] = from_timestamp(
data.get("last_synchronization_error_date")
data.get("last_synchronization_error_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot)

View file

@ -162,7 +162,7 @@ class TestDatetime:
assert tg_dtm.from_timestamp(None) is None
def test_from_timestamp_naive(self):
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, tzinfo=None)
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, tzinfo=dtm.timezone.utc)
assert tg_dtm.from_timestamp(1573431976, tzinfo=None) == datetime
def test_from_timestamp_aware(self, timezone):
@ -174,3 +174,8 @@ class TestDatetime:
tg_dtm.from_timestamp(1573431976.1 - timezone.utcoffset(test_datetime).total_seconds())
== datetime
)
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

View file

@ -134,7 +134,7 @@ async def raw_bot(bot_info):
"""Makes an regular Bot instance with the given bot_info"""
async with PytestBot(
bot_info["token"],
private_key=PRIVATE_KEY,
private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None,
request=NonchalantHttpxRequest(8),
get_updates_request=NonchalantHttpxRequest(1),
) as _bot:

View file

@ -21,7 +21,7 @@ import datetime
import pytest
from telegram import ChatInviteLink, User
from telegram._utils.datetime import to_timestamp
from telegram._utils.datetime import UTC, to_timestamp
from tests.auxil.slots import mro_slots
@ -107,6 +107,33 @@ class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
assert invite_link.name == self.name
assert invite_link.pending_join_request_count == self.pending_join_request_count
def test_de_json_localization(self, tz_bot, bot, raw_bot, creator):
json_dict = {
"invite_link": self.link,
"creator": creator.to_dict(),
"creates_join_request": self.creates_join_request,
"is_primary": self.primary,
"is_revoked": self.revoked,
"expire_date": to_timestamp(self.expire_date),
"member_limit": self.member_limit,
"name": self.name,
"pending_join_request_count": str(self.pending_join_request_count),
}
invite_link_raw = ChatInviteLink.de_json(json_dict, raw_bot)
invite_link_bot = ChatInviteLink.de_json(json_dict, bot)
invite_link_tz = ChatInviteLink.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
invite_offset = invite_link_tz.expire_date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
invite_link_tz.expire_date.replace(tzinfo=None)
)
assert invite_link_raw.expire_date.tzinfo == UTC
assert invite_link_bot.expire_date.tzinfo == UTC
assert invite_offset == tz_bot_offset
def test_to_dict(self, invite_link):
invite_link_dict = invite_link.to_dict()
assert isinstance(invite_link_dict, dict)

View file

@ -98,6 +98,26 @@ class TestChatJoinRequestWithoutRequest(TestChatJoinRequestBase):
assert chat_join_request.bio == self.bio
assert chat_join_request.invite_link == self.invite_link
def test_de_json_localization(self, tz_bot, bot, raw_bot, time):
json_dict = {
"chat": self.chat.to_dict(),
"from": self.from_user.to_dict(),
"date": to_timestamp(time),
"user_chat_id": self.from_user.id,
}
chatjoin_req_raw = ChatJoinRequest.de_json(json_dict, raw_bot)
chatjoin_req_bot = ChatJoinRequest.de_json(json_dict, bot)
chatjoin_req_tz = ChatJoinRequest.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
chatjoin_req_offset = chatjoin_req_tz.date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(chatjoin_req_tz.date.replace(tzinfo=None))
assert chatjoin_req_raw.date.tzinfo == UTC
assert chatjoin_req_bot.date.tzinfo == UTC
assert chatjoin_req_offset == tz_bot_offset
def test_to_dict(self, chat_join_request, time):
chat_join_request_dict = chat_join_request.to_dict()

View file

@ -33,7 +33,7 @@ from telegram import (
Dice,
User,
)
from telegram._utils.datetime import to_timestamp
from telegram._utils.datetime import UTC, to_timestamp
from tests.auxil.slots import mro_slots
ignored = ["self", "api_kwargs"]
@ -218,6 +218,24 @@ class TestChatMemberTypesWithoutRequest:
for c_mem_type_at, const_c_mem_at in iter_args(chat_member_type, const_chat_member, True):
assert c_mem_type_at == const_c_mem_at
def test_de_json_chatmemberbanned_localization(self, chat_member_type, tz_bot, bot, raw_bot):
# We only test two classes because the other three don't have datetimes in them.
if isinstance(chat_member_type, (ChatMemberBanned, ChatMemberRestricted)):
json_dict = make_json_dict(chat_member_type, include_optional_args=True)
chatmember_raw = ChatMember.de_json(json_dict, raw_bot)
chatmember_bot = ChatMember.de_json(json_dict, bot)
chatmember_tz = ChatMember.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
chatmember_offset = chatmember_tz.until_date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
chatmember_tz.until_date.replace(tzinfo=None)
)
assert chatmember_raw.until_date.tzinfo == UTC
assert chatmember_bot.until_date.tzinfo == UTC
assert chatmember_offset == tz_bot_offset
def test_de_json_invalid_status(self, chat_member_type, bot):
json_dict = {"status": "invalid", "user": CMDefaults.user.to_dict()}
chat_member_type = ChatMember.de_json(json_dict, bot)

View file

@ -137,6 +137,32 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link == invite_link
def test_de_json_localization(
self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link
):
json_dict = {
"chat": chat.to_dict(),
"from": user.to_dict(),
"date": to_timestamp(time),
"old_chat_member": old_chat_member.to_dict(),
"new_chat_member": new_chat_member.to_dict(),
"invite_link": invite_link.to_dict(),
}
chat_member_updated_bot = ChatMemberUpdated.de_json(json_dict, bot)
chat_member_updated_raw = ChatMemberUpdated.de_json(json_dict, raw_bot)
chat_member_updated_tz = ChatMemberUpdated.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
message_offset = chat_member_updated_tz.date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
chat_member_updated_tz.date.replace(tzinfo=None)
)
assert chat_member_updated_raw.date.tzinfo == UTC
assert chat_member_updated_bot.date.tzinfo == UTC
assert message_offset == tz_bot_offset
def test_to_dict(self, chat_member_updated):
chat_member_updated_dict = chat_member_updated.to_dict()
assert isinstance(chat_member_updated_dict, dict)

View file

@ -56,6 +56,7 @@ from telegram import (
Voice,
WebAppData,
)
from telegram._utils.datetime import UTC
from telegram.constants import ChatAction, ParseMode
from telegram.ext import Defaults
from tests._passport.test_passport import RAW_PASSPORT_DATA
@ -365,6 +366,46 @@ class TestMessageWithoutRequest(TestMessageBase):
for slot in new.__slots__:
assert not isinstance(new[slot], dict)
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"message_id": 12,
"from_user": None,
"date": int(datetime.now().timestamp()),
"chat": None,
"edit_date": int(datetime.now().timestamp()),
"forward_date": int(datetime.now().timestamp()),
}
message_raw = Message.de_json(json_dict, raw_bot)
message_bot = Message.de_json(json_dict, bot)
message_tz = Message.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
date_offset = message_tz.date.utcoffset()
date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(message_tz.date.replace(tzinfo=None))
edit_date_offset = message_tz.edit_date.utcoffset()
edit_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
message_tz.edit_date.replace(tzinfo=None)
)
forward_date_offset = message_tz.forward_date.utcoffset()
forward_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
message_tz.forward_date.replace(tzinfo=None)
)
assert message_raw.date.tzinfo == UTC
assert message_bot.date.tzinfo == UTC
assert date_offset == date_tz_bot_offset
assert message_raw.edit_date.tzinfo == UTC
assert message_bot.edit_date.tzinfo == UTC
assert edit_date_offset == edit_date_tz_bot_offset
assert message_raw.forward_date.tzinfo == UTC
assert message_bot.forward_date.tzinfo == UTC
assert forward_date_offset == forward_date_tz_bot_offset
def test_equality(self):
id_ = 1
a = Message(id_, self.date, self.chat, from_user=self.from_user)

View file

@ -20,7 +20,7 @@ from datetime import datetime, timedelta, timezone
import pytest
from telegram import MessageEntity, Poll, PollAnswer, PollOption, User
from telegram._utils.datetime import to_timestamp
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import PollType
from tests.auxil.slots import mro_slots
@ -208,6 +208,36 @@ class TestPollWithoutRequest(TestPollBase):
assert abs(poll.close_date - self.close_date) < timedelta(seconds=1)
assert to_timestamp(poll.close_date) == to_timestamp(self.close_date)
def test_de_json_localization(self, tz_bot, bot, raw_bot):
json_dict = {
"id": self.id_,
"question": self.question,
"options": [o.to_dict() for o in self.options],
"total_voter_count": self.total_voter_count,
"is_closed": self.is_closed,
"is_anonymous": self.is_anonymous,
"type": self.type,
"allows_multiple_answers": self.allows_multiple_answers,
"explanation": self.explanation,
"explanation_entities": [self.explanation_entities[0].to_dict()],
"open_period": self.open_period,
"close_date": to_timestamp(self.close_date),
}
poll_raw = Poll.de_json(json_dict, raw_bot)
poll_bot = Poll.de_json(json_dict, bot)
poll_bot_tz = Poll.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
poll_bot_tz_offset = poll_bot_tz.close_date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
poll_bot_tz.close_date.replace(tzinfo=None)
)
assert poll_raw.close_date.tzinfo == UTC
assert poll_bot.close_date.tzinfo == UTC
assert poll_bot_tz_offset == tz_bot_offset
def test_to_dict(self, poll):
poll_dict = poll.to_dict()

View file

@ -27,7 +27,7 @@ from telegram import (
VideoChatScheduled,
VideoChatStarted,
)
from telegram._utils.datetime import to_timestamp
from telegram._utils.datetime import UTC, to_timestamp
from tests.auxil.slots import mro_slots
@ -170,6 +170,23 @@ class TestVideoChatScheduledWithoutRequest:
assert abs(video_chat_scheduled.start_date - self.start_date) < dtm.timedelta(seconds=1)
def test_de_json_localization(self, tz_bot, bot, raw_bot):
json_dict = {"start_date": to_timestamp(self.start_date)}
videochat_raw = VideoChatScheduled.de_json(json_dict, raw_bot)
videochat_bot = VideoChatScheduled.de_json(json_dict, bot)
videochat_tz = VideoChatScheduled.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
videochat_offset = videochat_tz.start_date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
videochat_tz.start_date.replace(tzinfo=None)
)
assert videochat_raw.start_date.tzinfo == UTC
assert videochat_bot.start_date.tzinfo == UTC
assert videochat_offset == tz_bot_offset
def test_to_dict(self):
video_chat_scheduled = VideoChatScheduled(self.start_date)
video_chat_scheduled_dict = video_chat_scheduled.to_dict()

View file

@ -22,7 +22,7 @@ from datetime import datetime
import pytest
from telegram import LoginUrl, WebhookInfo
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import UTC, from_timestamp
from tests.auxil.slots import mro_slots
@ -102,6 +102,40 @@ class TestWebhookInfoWithoutRequest(TestWebhookInfoBase):
none = WebhookInfo.de_json(None, bot)
assert none is None
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"url": self.url,
"has_custom_certificate": self.has_custom_certificate,
"pending_update_count": self.pending_update_count,
"last_error_date": self.last_error_date,
"max_connections": self.max_connections,
"allowed_updates": self.allowed_updates,
"ip_address": self.ip_address,
"last_synchronization_error_date": self.last_synchronization_error_date,
}
webhook_info_bot = WebhookInfo.de_json(json_dict, bot)
webhook_info_raw = WebhookInfo.de_json(json_dict, raw_bot)
webhook_info_tz = WebhookInfo.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
last_error_date_offset = webhook_info_tz.last_error_date.utcoffset()
last_error_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
webhook_info_tz.last_error_date.replace(tzinfo=None)
)
sync_error_date_offset = webhook_info_tz.last_synchronization_error_date.utcoffset()
sync_error_date_tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
webhook_info_tz.last_synchronization_error_date.replace(tzinfo=None)
)
assert webhook_info_raw.last_error_date.tzinfo == UTC
assert webhook_info_bot.last_error_date.tzinfo == UTC
assert last_error_date_offset == last_error_tz_bot_offset
assert webhook_info_raw.last_synchronization_error_date.tzinfo == UTC
assert webhook_info_bot.last_synchronization_error_date.tzinfo == UTC
assert sync_error_date_offset == sync_error_date_tz_bot_offset
def test_always_tuple_allowed_updates(self):
webhook_info = WebhookInfo(
self.url, self.has_custom_certificate, self.pending_update_count