mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-03 10:32:42 +00:00
Convert datetimes to aware datetimes with UTC.
Naive datetimes will now be interpreted as local time throughout the library.
This commit is contained in:
parent
fd58371f53
commit
ff7094ce96
@ -75,7 +75,7 @@ class Snowflake(Protocol):
|
||||
|
||||
@property
|
||||
def created_at(self) -> datetime:
|
||||
""":class:`datetime.datetime`: Returns the model's creation time as a naive datetime in UTC."""
|
||||
""":class:`datetime.datetime`: Returns the model's creation time as an aware datetime in UTC."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@ -1176,13 +1176,16 @@ class Messageable(Protocol):
|
||||
that this would make it a slow operation.
|
||||
before: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages before this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages after this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
around: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages around this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
When using this argument, the maximum limit is 101. Note that if the limit is an
|
||||
even number then this will return at most limit + 1 messages.
|
||||
oldest_first: Optional[:class:`bool`]
|
||||
|
@ -225,17 +225,21 @@ class Activity(BaseActivity):
|
||||
def start(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
|
||||
try:
|
||||
return datetime.datetime.utcfromtimestamp(self.timestamps['start'] / 1000)
|
||||
timestamp = self.timestamps['start'] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
|
||||
try:
|
||||
return datetime.datetime.utcfromtimestamp(self.timestamps['end'] / 1000)
|
||||
timestamp = self.timestamps['end'] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
@property
|
||||
def large_image_url(self):
|
||||
@ -300,10 +304,6 @@ class Game(BaseActivity):
|
||||
-----------
|
||||
name: :class:`str`
|
||||
The game's name.
|
||||
start: Optional[:class:`datetime.datetime`]
|
||||
A naive UTC timestamp representing when the game started. Keyword-only parameter. Ignored for bots.
|
||||
end: Optional[:class:`datetime.datetime`]
|
||||
A naive UTC timestamp representing when the game ends. Keyword-only parameter. Ignored for bots.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
@ -320,20 +320,12 @@ class Game(BaseActivity):
|
||||
try:
|
||||
timestamps = extra['timestamps']
|
||||
except KeyError:
|
||||
self._extract_timestamp(extra, 'start')
|
||||
self._extract_timestamp(extra, 'end')
|
||||
self._start = 0
|
||||
self._end = 0
|
||||
else:
|
||||
self._start = timestamps.get('start', 0)
|
||||
self._end = timestamps.get('end', 0)
|
||||
|
||||
def _extract_timestamp(self, data, key):
|
||||
try:
|
||||
dt = data[key]
|
||||
except KeyError:
|
||||
setattr(self, '_' + key, 0)
|
||||
else:
|
||||
setattr(self, '_' + key, dt.timestamp() * 1000.0)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||
@ -346,14 +338,14 @@ class Game(BaseActivity):
|
||||
def start(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable."""
|
||||
if self._start:
|
||||
return datetime.datetime.utcfromtimestamp(self._start / 1000)
|
||||
return datetime.datetime.utcfromtimestamp(self._start / 1000).replace(tzinfo=datetime.timezone.utc)
|
||||
return None
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable."""
|
||||
if self._end:
|
||||
return datetime.datetime.utcfromtimestamp(self._end / 1000)
|
||||
return datetime.datetime.utcfromtimestamp(self._end / 1000).replace(tzinfo=datetime.timezone.utc)
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
|
@ -1076,10 +1076,12 @@ class Client:
|
||||
Defaults to ``100``.
|
||||
before: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieves guilds before this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve guilds after this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
|
||||
Raises
|
||||
------
|
||||
|
@ -87,7 +87,9 @@ class Embed:
|
||||
The URL of the embed.
|
||||
This can be set during initialisation.
|
||||
timestamp: :class:`datetime.datetime`
|
||||
The timestamp of the embed content. This could be a naive or aware datetime.
|
||||
The timestamp of the embed content. This is an aware datetime.
|
||||
If a naive datetime is passed, it is converted to an aware
|
||||
datetime with the local timezone.
|
||||
colour: Union[:class:`Colour`, :class:`int`]
|
||||
The colour code of the embed. Aliased to ``color`` as well.
|
||||
This can be set during initialisation.
|
||||
@ -129,6 +131,8 @@ class Embed:
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if timestamp.tzinfo is None:
|
||||
timestamp = timestamp.astimezone()
|
||||
self.timestamp = timestamp
|
||||
|
||||
@classmethod
|
||||
|
@ -1336,7 +1336,8 @@ class Guild(Hashable):
|
||||
Pass ``None`` to fetch all members. Note that this is potentially slow.
|
||||
after: Optional[Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve members after this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -2119,10 +2120,12 @@ class Guild(Hashable):
|
||||
The number of entries to retrieve. If ``None`` retrieve all entries.
|
||||
before: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve entries before this date or entry.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve entries after this date or entry.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
oldest_first: :class:`bool`
|
||||
If set to ``True``, return entries in oldest->newest order. Defaults to ``True`` if
|
||||
``after`` is specified, otherwise ``False``.
|
||||
|
@ -82,7 +82,7 @@ class Integration:
|
||||
account: :class:`IntegrationAccount`
|
||||
The integration account information.
|
||||
synced_at: :class:`datetime.datetime`
|
||||
When the integration was last synced.
|
||||
An aware UTC datetime representing when the integration was last synced.
|
||||
"""
|
||||
|
||||
__slots__ = ('id', '_state', 'guild', 'name', 'enabled', 'type',
|
||||
@ -184,7 +184,7 @@ class Integration:
|
||||
Syncing the integration failed.
|
||||
"""
|
||||
await self._state.http.sync_integration(self.guild.id, self.id)
|
||||
self.synced_at = datetime.datetime.utcnow()
|
||||
self.synced_at = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
@ -265,7 +265,7 @@ class Invite(Hashable):
|
||||
revoked: :class:`bool`
|
||||
Indicates if the invite has been revoked.
|
||||
created_at: :class:`datetime.datetime`
|
||||
A datetime object denoting the time the invite was created.
|
||||
An aware UTC datetime object denoting the time the invite was created.
|
||||
temporary: :class:`bool`
|
||||
Indicates that the invite grants temporary membership.
|
||||
If ``True``, members who joined via this invite will be kicked upon disconnect.
|
||||
|
@ -67,7 +67,7 @@ class VoiceState:
|
||||
.. versionadded:: 1.7
|
||||
|
||||
requested_to_speak_at: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC that the member
|
||||
An aware datetime object that specifies the date and time in UTC that the member
|
||||
requested to speak. It will be ``None`` if they are not requesting to speak
|
||||
anymore or have been accepted to speak.
|
||||
|
||||
@ -183,7 +183,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
Attributes
|
||||
----------
|
||||
joined_at: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC that the member joined the guild.
|
||||
An aware datetime object that specifies the date and time in UTC that the member joined the guild.
|
||||
If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``.
|
||||
activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]]
|
||||
The activities that the user is currently doing.
|
||||
@ -196,7 +196,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
.. versionadded:: 1.6
|
||||
premium_since: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC when the member used their
|
||||
An aware datetime object that specifies the date and time in UTC when the member used their
|
||||
Nitro boost on the guild, if available. This could be ``None``.
|
||||
"""
|
||||
|
||||
|
@ -842,7 +842,7 @@ class Message(Hashable):
|
||||
|
||||
@property
|
||||
def edited_at(self):
|
||||
"""Optional[:class:`datetime.datetime`]: A naive UTC datetime object containing the edited time of the message."""
|
||||
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the message."""
|
||||
return self._edited_timestamp
|
||||
|
||||
@property
|
||||
@ -903,10 +903,7 @@ class Message(Hashable):
|
||||
"Yay you made it, {0}!",
|
||||
]
|
||||
|
||||
# manually reconstruct the epoch with millisecond precision, because
|
||||
# datetime.datetime.timestamp() doesn't return the exact posix
|
||||
# timestamp with the precision that we need
|
||||
created_at_ms = int((self.created_at - datetime.datetime(1970, 1, 1)).total_seconds() * 1000)
|
||||
created_at_ms = int(self.created_at.timestamp() * 1000)
|
||||
return formats[created_at_ms % len(formats)].format(self.author.name)
|
||||
|
||||
if self.type is MessageType.premium_guild_subscription:
|
||||
|
@ -1013,6 +1013,7 @@ class ConnectionState:
|
||||
|
||||
if member is not None:
|
||||
timestamp = datetime.datetime.utcfromtimestamp(data.get('timestamp'))
|
||||
timestamp = timestamp.replace(tzinfo=datetime.timezone.utc)
|
||||
self.dispatch('typing', channel, member, timestamp)
|
||||
|
||||
def _get_reaction_user(self, channel, user_id):
|
||||
|
@ -91,7 +91,7 @@ class Sticker(Hashable):
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC as a naive datetime."""
|
||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC."""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
|
@ -90,9 +90,10 @@ class Template:
|
||||
creator: :class:`User`
|
||||
The creator of the template.
|
||||
created_at: :class:`datetime.datetime`
|
||||
When the template was created.
|
||||
An aware datetime in UTC representing when the template was created.
|
||||
updated_at: :class:`datetime.datetime`
|
||||
When the template was last updated (referred to as "last synced" in the client).
|
||||
An aware datetime in UTC representing when the template was last updated.
|
||||
This is referred to as "last synced" in the official Discord client.
|
||||
source_guild: :class:`Guild`
|
||||
The source guild.
|
||||
"""
|
||||
|
@ -192,7 +192,8 @@ class BaseUser(_BaseUser):
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the user's creation time in UTC.
|
||||
|
||||
This is when the user's Discord account was created."""
|
||||
This is when the user's Discord account was created.
|
||||
"""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
|
@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
import array
|
||||
import asyncio
|
||||
import collections.abc
|
||||
from typing import Optional, overload
|
||||
import unicodedata
|
||||
from base64 import b64encode
|
||||
from bisect import bisect_left
|
||||
@ -103,9 +104,17 @@ class SequenceProxy(collections.abc.Sequence):
|
||||
def count(self, value):
|
||||
return self.__proxied.count(value)
|
||||
|
||||
def parse_time(timestamp):
|
||||
@overload
|
||||
def parse_time(timestamp: None) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_time(timestamp: str) -> datetime.datetime:
|
||||
...
|
||||
|
||||
def parse_time(timestamp: Optional[str]) -> Optional[datetime.datetime]:
|
||||
if timestamp:
|
||||
return datetime.datetime(*map(int, re.split(r'[^\d]', timestamp.replace('+00:00', ''))))
|
||||
return datetime.datetime.fromisoformat(timestamp)
|
||||
return None
|
||||
|
||||
def copy_doc(original):
|
||||
@ -168,7 +177,7 @@ def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None, scopes
|
||||
return url
|
||||
|
||||
|
||||
def snowflake_time(id):
|
||||
def snowflake_time(id: int) -> datetime.datetime:
|
||||
"""
|
||||
Parameters
|
||||
-----------
|
||||
@ -178,25 +187,34 @@ def snowflake_time(id):
|
||||
Returns
|
||||
--------
|
||||
:class:`datetime.datetime`
|
||||
The creation date in UTC of a Discord snowflake ID."""
|
||||
return datetime.datetime.utcfromtimestamp(((id >> 22) + DISCORD_EPOCH) / 1000)
|
||||
An aware datetime in UTC representing the creation time of the snowflake.
|
||||
"""
|
||||
timestamp = ((id >> 22) + DISCORD_EPOCH) / 1000
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
def time_snowflake(datetime_obj, high=False):
|
||||
def time_snowflake(dt: datetime.datetime, high: bool = False) -> int:
|
||||
"""Returns a numeric snowflake pretending to be created at the given date.
|
||||
|
||||
When using as the lower end of a range, use ``time_snowflake(high=False) - 1`` to be inclusive, ``high=True`` to be exclusive
|
||||
When using as the higher end of a range, use ``time_snowflake(high=True)`` + 1 to be inclusive, ``high=False`` to be exclusive
|
||||
When using as the lower end of a range, use ``time_snowflake(high=False) - 1``
|
||||
to be inclusive, ``high=True`` to be exclusive.
|
||||
|
||||
When using as the higher end of a range, use ``time_snowflake(high=True) + 1``
|
||||
to be inclusive, ``high=False`` to be exclusive
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
datetime_obj: :class:`datetime.datetime`
|
||||
A timezone-naive datetime object representing UTC time.
|
||||
dt: :class:`datetime.datetime`
|
||||
A datetime object to convert to a snowflake.
|
||||
If naive, the timezone is assumed to be local time.
|
||||
high: :class:`bool`
|
||||
Whether or not to set the lower 22 bit to high or low.
|
||||
"""
|
||||
unix_seconds = (datetime_obj - type(datetime_obj)(1970, 1, 1)).total_seconds()
|
||||
discord_millis = int(unix_seconds * 1000 - DISCORD_EPOCH)
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`int`
|
||||
The snowflake representing the time given.
|
||||
"""
|
||||
discord_millis = int(dt.timestamp() * 1000 - DISCORD_EPOCH)
|
||||
return (discord_millis << 22) + (2**22-1 if high else 0)
|
||||
|
||||
def find(predicate, seq):
|
||||
@ -374,12 +392,12 @@ async def sleep_until(when, result=None):
|
||||
-----------
|
||||
when: :class:`datetime.datetime`
|
||||
The timestamp in which to sleep until. If the datetime is naive then
|
||||
it is assumed to be in UTC.
|
||||
it is assumed to be local time.
|
||||
result: Any
|
||||
If provided is returned to the caller when the coroutine completes.
|
||||
"""
|
||||
if when.tzinfo is None:
|
||||
when = when.replace(tzinfo=datetime.timezone.utc)
|
||||
when = when.astimezone()
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
delta = (when - now).total_seconds()
|
||||
while delta > MAX_ASYNCIO_SECONDS:
|
||||
|
@ -340,7 +340,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
:type channel: :class:`abc.Messageable`
|
||||
:param user: The user that started typing.
|
||||
:type user: Union[:class:`User`, :class:`Member`]
|
||||
:param when: When the typing started as a naive datetime in UTC.
|
||||
:param when: When the typing started as an aware datetime in UTC.
|
||||
:type when: :class:`datetime.datetime`
|
||||
|
||||
.. function:: on_message(message)
|
||||
@ -612,7 +612,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
|
||||
:param channel: The private channel that had its pins updated.
|
||||
:type channel: :class:`abc.PrivateChannel`
|
||||
:param last_pin: The latest message that was pinned as a naive datetime in UTC. Could be ``None``.
|
||||
:param last_pin: The latest message that was pinned as an aware datetime in UTC. Could be ``None``.
|
||||
:type last_pin: Optional[:class:`datetime.datetime`]
|
||||
|
||||
.. function:: on_guild_channel_delete(channel)
|
||||
@ -646,7 +646,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
|
||||
:param channel: The guild channel that had its pins updated.
|
||||
:type channel: :class:`abc.GuildChannel`
|
||||
:param last_pin: The latest message that was pinned as a naive datetime in UTC. Could be ``None``.
|
||||
:param last_pin: The latest message that was pinned as an aware datetime in UTC. Could be ``None``.
|
||||
:type last_pin: Optional[:class:`datetime.datetime`]
|
||||
|
||||
.. function:: on_guild_integrations_update(guild)
|
||||
|
Loading…
x
Reference in New Issue
Block a user