8000 Localize Received `datetime` Objects According to `Defaults.tzinfo` (… · Devors/python-telegram-bot@7b116be · GitHub
[go: up one dir, main page]

Skip to content

Commit 7b116be

Browse files
authored
Localize Received datetime Objects According to Defaults.tzinfo (python-telegram-bot#3632)
1 parent 934e4c9 commit 7b116be

21 files changed

+376
-40
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ The following wonderful people contributed directly or indirectly to this projec
7272
- `Li-aung Yip <https://github.com/LiaungYip>`_
7373
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
7474
- `LRezende <https://github. 628C com/lrezende>`_
75+
- `Luca Bellanti <https://github.com/Trifase>`_
7576
- `macrojames <https://github.com/macrojames>`_
7677
- `Matheus Lemos <https://github.com/mlemosf>`_
7778
- `Michael Dix <https://github.com/Eisberge>`_

docs/substitutions/global.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,5 @@
5555
.. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list.
5656

5757
.. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``.
58+
59+
.. |datetime_localization| replace:: The default timezone of the bot is used for localization, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.

telegram/_chatinvitelink.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from telegram._telegramobject import TelegramObject
2424
from telegram._user import User
25-
from telegram._utils.datetime import from_timestamp
25+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
2626
from telegram._utils.types import JSONDict
2727

2828
if TYPE_CHECKING:
@@ -54,6 +54,9 @@ class ChatInviteLink(TelegramObject):
5454
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
5555
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
5656
has been expired.
57+
58+
.. versionchanged:: NEXT.VERSION
59+
|datetime_localization|
5760
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
5861
chat simultaneously after joining the chat via this invite link;
5962
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@@ -78,6 +81,9 @@ class ChatInviteLink(TelegramObject):
7881
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
7982
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
8083
has been expired.
84+
85+
.. versionchanged:: NEXT.VERSION
86+
|datetime_localization|
8187
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
8288
of the chat simultaneously after joining the chat via this invite link;
8389
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@@ -152,7 +158,10 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatInviteLi
152158
if not data:
153159
return None
154160

161+
# Get the local timezone from the bot if it has defaults
162+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
163+
155164
data["creator"] = User.de_json(data.get("creator"), bot)
156-
data["expire_date"] = from_timestamp(data.get("expire_date", None))
165+
data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
157166

158167
return super().de_json(data=data, bot=bot)

telegram/_chatjoinrequest.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from telegram._chatinvitelink import ChatInviteLink
2525
from telegram._telegramobject import TelegramObject
2626
from telegram._user import User
27-
from telegram._utils.datetime import from_timestamp
27+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
2828
from telegram._utils.defaultvalue import DEFAULT_NONE
2929
from telegram._utils.types import JSONDict, ODVInput
3030

@@ -56,6 +56,9 @@ class ChatJoinRequest(TelegramObject):
5656
chat (:class:`telegram.Chat`): Chat to which the request was sent.
5757
from_user (:class:`telegram.User`): User that sent the join request.
5858
date (:class:`datetime.datetime`): Date the request was sent.
59+
60+
.. versionchanged:: NEXT.VERSION
61+
|datetime_localization|
5962
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
6063
request. This number may have more than 32 significant bits and some programming
6164
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@@ -73,6 +76,9 @@ class ChatJoinRequest(TelegramObject):
7376
chat (:class:`telegram.Chat`): Chat to which the request was sent.
7477
from_user (:class:`telegram.User`): User that sent the join request.
7578
date (:class:`datetime.datetime`): Date the request was sent.
79+
80+
.. versionchanged:: NEXT.VERSION
81+
|datetime_localization|
7682
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
7783
request. This number may have more than 32 significant bits and some programming
7884
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@@ -124,9 +130,12 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatJoinRequ
124130
if not data:
125131
return None
126132

133+
# Get the local timezone from the bot if it has defaults
134+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
135+
127136
data["chat"] = Chat.de_json(data.get("chat"), bot)
128137
data["from_user"] = User.de_json(data.pop("from", None), bot)
129-
data["date"] = from_timestamp(data.get("date", None))
138+
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
130139
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
131140

132141
return super().de_json(data=data, bot=bot)

telegram/_chatmember.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from telegram import constants
2424
from telegram._telegramobject import TelegramObject
2525
from telegram._user import User
26-
from telegram._utils.datetime import from_timestamp
26+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
2727
from telegram._utils.types import JSONDict
2828

2929
if TYPE_CHECKING:
@@ -125,7 +125,10 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatMember"]
125125

126126
data["user"] = User.de_json(data.get("user"), bot)
127127
if "until_date" in data:
128-
data["until_date"] = from_timestamp(data["until_date"])
128+
# Get the local timezone from the bot if it has defaults
129+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
130+
131+
data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
129132

130133
return super().de_json(data=data, bot=bot)
131134

@@ -386,6 +389,9 @@ class ChatMemberRestricted(ChatMember):
386389
.. versionadded:: 20.0
387390
until_date (:class:`datetime.datetime`): Date when restrictions
388391
will be lifted for this user.
392+
393+
.. versionchanged:: NEXT.VERSION
394+
|datetime_localization|
389395
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
390396
391397
.. versionadded:: 20.1
@@ -438,6 +444,9 @@ class ChatMemberRestricted(ChatMember):
438444
.. versionadded:: 20.0
439445
until_date (:class:`datetime.datetime`): Date when restrictions
440446
will be lifted for this user.
447+
448+
.. versionchanged:: NEXT.VERSION
449+
|datetime_localization|
441450
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
442451
443452
.. versionadded:: 20.1
@@ -565,13 +574,19 @@ class ChatMemberBanned(ChatMember):
565574
until_date (:class:`datetime.datetime`): Date when restrictions
566575
will be lifted for this user.
567576
577+
.. versionchanged:: NEXT.VERSION
578+
|datetime_localization|
579+
568580
Attributes:
569581
status (:obj:`str`): The member's status in the chat,
570582
always :tg-const:`telegram.ChatMember.BANNED`.
571583
user (:class:`telegram.User`): Information about the user.
572584
until_date (:class:`datetime.datetime`): Date when restrictions
573585
will be lifted for this user.
574586
587+
.. versionchanged:: NEXT.VERSION
588+
|datetime_localization|
589+
575590
"""
576591

577592
__slots__ = ("until_date",)

telegram/_chatmemberupdated.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from telegram._chatmember import ChatMember
2626
from telegram._telegramobject import TelegramObject
2727
from telegram._user import User
28-
from telegram._utils.datetime import from_timestamp
28+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
2929
from telegram._utils.types import JSONDict
3030

3131
if TYPE_CHECKING:
@@ -52,6 +52,9 @@ class ChatMemberUpdated(TelegramObject):
5252
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
5353
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
5454
:class:`datetime.datetime`.
55+
56+
.. versionchanged:: NEXT.VERSION
57+
|datetime_localization|
5558
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
5659
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
5760
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
@@ -62,6 +65,9 @@ class ChatMemberUpdated(TelegramObject):
6265
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
6366
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
6467
:class:`datetime.datetime`.
68+
69+
.. versionchanged:: NEXT.VERSION
70+
|datetime_localization|
6571
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
6672
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
6773
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
@@ -118,9 +124,12 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatMemberUp
118124
if not data:
119125
return None
120126

127+
# Get the local timezone from the bot if it has defaults
128+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
129+
121130
data["chat"] = Chat.de_json(data.get("chat"), bot)
122131
data["from_user"] = User.de_json(data.pop("from", None), bot)
123-
data["date"] = from_timestamp(data.get("date"))
132+
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
124133
data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
125134
data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
126135
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)

telegram/_message.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
from telegram._telegramobject import TelegramObject
5757
from telegram._user import User
5858
from telegram._utils.argumentparsing import parse_sequence_arg
59-
from telegram._utils.datetime import from_timestamp
59+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
6060
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
6161
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
6262
from telegram._videochat import (
@@ -121,6 +121,9 @@ class Message(TelegramObject):
121121
sent on behalf of a chat.
122122
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
123123
:class:`datetime.datetime`.
124+
125+
.. versionchanged:: NEXT.VERSION
126+
|datetime_localization|
124127
chat (:class:`telegram.Chat`): Conversation the message belongs to.
125128
forward_from (:class:`telegram.User`, optional): For forwarded messages, sender of
126129
the original message.
@@ -132,6 +135,9 @@ class Message(TelegramObject):
132135
users who disallow adding a link to their account in forwarded messages.
133136
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
134137
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
138+
139+
.. versionchanged:: NEXT.VERSION
140+
|datetime_localization|
135141
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel
136142
post that was automatically forwarded to the connected discussion group.
137143
@@ -141,6 +147,9 @@ class Message(TelegramObject):
141147
``reply_to_message`` fields even if it itself is a reply.
142148
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
143149
time. Converted to :class:`datetime.datetime`.
150+
151+
.. versionchanged:: NEXT.VERSION
152+
|datetime_localization|
144153
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
145154
forwarded.
146155
@@ -338,6 +347,9 @@ class Message(TelegramObject):
338347
sent on behalf of a chat.
339348
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
340349
:class:`datetime.datetime`.
350+
351+
.. versionchanged:: NEXT.VERSION
352+
|datetime_localization|
341353
chat (:class:`telegram.Chat`): Conversation the message belongs to.
342354
forward_from (:class:`telegram.User`): Optional. For forwarded messages, sender of the
343355
original message.
@@ -347,6 +359,9 @@ class Message(TelegramObject):
347359
the original message in the channel.
348360
forward_date (:class:`datetime.datetime`): Optional. For forwarded messages, date the
349361
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
362+
363+
.. versionchanged:: NEXT.VERSION
364+
|datetime_localization|
350365
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel
351366
post that was automatically forwarded to the connected discussion group.
352367
@@ -356,6 +371,9 @@ class Message(TelegramObject):
356371
``reply_to_message`` fields even if it itself is a reply.
357372
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited in Unix
358373
time. Converted to :class:`datetime.datetime`.
374+
375+
.. versionchanged:: NEXT.VERSION
376+
|datetime_localization|
359377
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
360378
forwarded.
361379
@@ -850,17 +868,20 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Message"]:
850868
if not data:
851869
return None
852870

871+
# Get the local timezone from the bot if it has defaults
872+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
873+
853874
data["from_user"] = User.de_json(data.pop("from", None), bot)
854875
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
855-
data["date"] = from_timestamp(data["date"])
876+
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
856877
data["chat"] = Chat.de_json(data.get("chat"), bot)
857878
data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
858879
data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
859880
data["forward_from"] = User.de_json(data.get("forward_from"), bot)
860881
data["forward_from_chat"] = Chat.de_json(data.get("forward_from_chat"), bot)
861-
data["forward_date"] = from_timestamp(data.get("forward_date"))
882+
data["forward_date"] = from_timestamp(data.get("forward_date"), tzinfo=loc_tzinfo)
862883
data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
863-
data["edit_date"] = from_timestamp(data.get("edit_date"))
884+
data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
864885
data["audio"] = Audio.de_json(data.get("audio"), bot)
865886
data["document"] = Document.de_json(data.get("document"), bot)
866887
data["animation"] = Animation.de_json(data.get("animation"), bot)

telegram/_poll.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from telegram._user import User
2727
from telegram._utils import enum
2828
from telegram._utils.argumentparsing import parse_sequence_arg
29-
from telegram._utils.datetime import from_timestamp
29+
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
3030
from telegram._utils.types import JSONDict
3131

3232
if TYPE_CHECKING:
@@ -173,6 +173,9 @@ class Poll(TelegramObject):
173173
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
174174
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
175175
176+
.. versionchanged:: NEXT.VERSION
177+
|datetime_localization|
178+
176179
Attributes:
177180
id (:obj:`str`): Unique poll identifier.
178181
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
@@ -206,6 +209,9 @@ class Poll(TelegramObject):
206209
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
207210
automatically closed.
208211
212+
.. versionchanged:: NEXT.VERSION
213+
|datetime_localization|
214+
209215
"""
210216

211217
__slots__ = (
@@ -271,9 +277,12 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Poll"]:
271277
if not data:
272278
return None
273279

280+
# Get the local timezone from the bot if it has defaults
281+
loc_tzinfo = extract_tzinfo_from_defaults(bot)
282+
274283
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
275284
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
276-
data["close_date"] = from_timestamp(data.get("close_date"))
285+
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
277286

278287
return super().de_json(data=data, bot=bot)
279288

telegram/_utils/datetime.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
"""
3030
import datetime as dtm # skipcq: PYL-W0406
3131
import time
32-
from typing import Optional, Union
32+
from typing import TYPE_CHECKING, Optional, Union
33+
34+
if TYPE_CHECKING:
35+
from telegram import Bot
3336

3437
# pytz is only available if it was installed as dependency of APScheduler, so we make a little
3538
# workaround here
@@ -162,15 +165,19 @@ def to_timestamp(
162165
)
163166

164167

165-
def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optional[dtm.datetime]:
168+
def from_timestamp(
169+
unixtime: Optional[int],
170+
tzinfo: Optional[dtm.tzinfo] = None,
171+
) -> Optional[dtm.datetime]:
166172
"""
167173
Converts an (integer) unix timestamp to a timezone aware datetime object.
168174
:obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
169175
170176
Args:
171177
unixtime (:obj:`int`): Integer POSIX timestamp.
172178
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be
173-
converted to. Defaults to UTC.
179+
converted to. Defaults to :obj:`None`, in which case the returned datetime object will
180+
be timezone aware and in UTC.
174181
175182
Returns:
176183
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
179186
if unixtime is None:
180187
return None
181188

182-
if tzinfo is not None:
183-
return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo)
184-
return dtm.datetime.utcfromtimestamp(unixtime)
189+
return dtm.datetime.fromtimestamp(unixtime, tz=UTC if tzinfo is None else tzinfo)
190+
191+
192+
def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]:
193+
"""
194+
Extracts the timezone info from the default values of the bot.
195+
If the bot has no default values, :obj:`None` is returned.
196+
"""
197+
# We don't use `ininstance(bot, ExtBot)` here so that this works
198+
# in `python-telegram-bot-raw` as well
199+
if hasattr(bot, "defaults") and bot.defaults:
200+
return bot.defaults.tzinfo 4DDF
201+
return None
185202

186203

187204
def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:

0 commit comments

Comments
 (0)
0