Add Discord timestamp converter and transformer

This commit is contained in:
Alex Nørgaard
2026-02-23 03:13:10 +00:00
committed by GitHub
parent fd5a218d7c
commit 8bad09e1d8
6 changed files with 95 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
import datetime
import inspect import inspect
from dataclasses import dataclass from dataclasses import dataclass
@@ -52,7 +53,7 @@ from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel,
from ..abc import GuildChannel from ..abc import GuildChannel
from ..threads import Thread from ..threads import Thread
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale
from ..utils import MISSING, maybe_coroutine, _human_join from ..utils import MISSING, maybe_coroutine, _human_join, TIMESTAMP_PATTERN
from ..user import User from ..user import User
from ..role import Role from ..role import Role
from ..member import Member from ..member import Member
@@ -62,6 +63,7 @@ from .._types import ClientT
__all__ = ( __all__ = (
'Transformer', 'Transformer',
'Transform', 'Transform',
'Timestamp',
'Range', 'Range',
) )
@@ -681,6 +683,41 @@ class UnionChannelTransformer(BaseChannelTransformer[ClientT]):
return resolved return resolved
if TYPE_CHECKING:
Timestamp = datetime.datetime
else:
class Timestamp(Transformer[ClientT]):
"""A type annotation that can be applied to a parameter for transforming a :ddocs:`Discord style timestamp <reference#message-formatting>` input to a
:class:`datetime.datetime`.
.. versionadded:: 2.7
.. warning::
Due to a Discord limitation, no timezone is provided with the input. The UTC timezone has been supplanted instead.
Examples
---------
.. code-block:: python3
@app_commands.command()
async def datetime(interaction: discord.Interaction, value: app_commands.Timestamp):
await interaction.response.send_message(value.isoformat())
"""
@property
def type(self) -> AppCommandOptionType:
return AppCommandOptionType.string
async def transform(self, interaction: Interaction[ClientT], value: Any, /):
match = TIMESTAMP_PATTERN.match(value)
if not match:
raise TransformerError(value, AppCommandOptionType.string, self)
return datetime.datetime.fromtimestamp(int(match[1]), tz=datetime.timezone.utc)
CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = { CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = {
AppCommandChannel: [ AppCommandChannel: [
ChannelType.stage_voice, ChannelType.stage_voice,

View File

@@ -24,6 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime
import inspect import inspect
import re import re
from typing import ( from typing import (
@@ -86,6 +87,7 @@ __all__ = (
'clean_content', 'clean_content',
'Greedy', 'Greedy',
'Range', 'Range',
'Timestamp',
'run_converters', 'run_converters',
) )
@@ -893,6 +895,28 @@ class GuildStickerConverter(IDConverter[discord.GuildSticker]):
return result return result
if TYPE_CHECKING:
Timestamp = datetime.datetime
else:
class Timestamp(Converter[str]):
"""Converts to a :class:`datetime.datetime`.
Conversion is attempted based on the :ddocs:`Discord style timestamp <reference#message-formatting>` input format.
.. versionadded:: 2.7
.. warning::
Due to a Discord limitation, no timezone is provided with the input. The UTC timezone has been supplanted instead.
"""
async def convert(self, ctx: Context[BotT], argument: str) -> datetime.datetime:
match = discord.utils.TIMESTAMP_PATTERN.match(argument)
if not match:
raise BadTimestampArgument(argument)
return datetime.datetime.fromtimestamp(int(match[1]), tz=datetime.timezone.utc)
class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]): class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]):
"""Converts to a :class:`~discord.ScheduledEvent`. """Converts to a :class:`~discord.ScheduledEvent`.

View File

@@ -79,6 +79,7 @@ __all__ = (
'SoundboardSoundNotFound', 'SoundboardSoundNotFound',
'PartialEmojiConversionFailure', 'PartialEmojiConversionFailure',
'BadBoolArgument', 'BadBoolArgument',
'BadTimestampArgument',
'MissingRole', 'MissingRole',
'BotMissingRole', 'BotMissingRole',
'MissingAnyRole', 'MissingAnyRole',
@@ -602,6 +603,24 @@ class BadBoolArgument(BadArgument):
super().__init__(f'{argument} is not a recognised boolean option') super().__init__(f'{argument} is not a recognised boolean option')
class BadTimestampArgument(BadArgument):
"""Exception raised when a timestamp argument was not convertable.
This inherits from :exc:`BadArgument`
.. versionadded:: 2.7
Attributes
-----------
argument: :class:`str`
The datetime/timestamp argument supplied by the caller that was not a valid timestamp format.
"""
def __init__(self, argument: str) -> None:
self.argument: str = argument
super().__init__(f'{argument} is not a recognised datetime or timestamp option')
class RangeError(BadArgument): class RangeError(BadArgument):
"""Exception raised when an argument is out of range. """Exception raised when an argument is out of range.

View File

@@ -118,6 +118,7 @@ __all__ = (
DISCORD_EPOCH = 1420070400000 DISCORD_EPOCH = 1420070400000
DEFAULT_FILE_SIZE_LIMIT_BYTES = 10485760 DEFAULT_FILE_SIZE_LIMIT_BYTES = 10485760
TIMESTAMP_PATTERN: re.Pattern[str] = re.compile(r'<t:(-?\d+)(?::[tTdDfFsSR])?>')
class _MissingSentinel: class _MissingSentinel:

View File

@@ -536,6 +536,11 @@ Converters
.. autoclass:: discord.ext.commands.SoundboardSoundConverter .. autoclass:: discord.ext.commands.SoundboardSoundConverter
:members: :members:
.. attributetable:: discord.ext.commands.Timestamp
.. autoclass:: discord.ext.commands.Timestamp
:members:
.. attributetable:: discord.ext.commands.clean_content .. attributetable:: discord.ext.commands.clean_content
.. autoclass:: discord.ext.commands.clean_content .. autoclass:: discord.ext.commands.clean_content

View File

@@ -1169,6 +1169,14 @@ Range
.. autoclass:: discord.app_commands.Range .. autoclass:: discord.app_commands.Range
:members: :members:
Timestamp
++++++++++
.. attributetable:: discord.app_commands.Timestamp
.. autoclass:: discord.app_commands.Timestamp
:members:
Translations Translations
~~~~~~~~~~~~~ ~~~~~~~~~~~~~