diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/converters.py | 33 | ||||
| -rw-r--r-- | tests/test_converters.py | 55 | 
2 files changed, 88 insertions, 0 deletions
| diff --git a/bot/converters.py b/bot/converters.py index 339da7b60..49ac488f4 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -4,6 +4,7 @@ from datetime import datetime  from ssl import CertificateError  from typing import Union +import dateutil.parser  import discord  from aiohttp import ClientConnectorError  from dateutil.relativedelta import relativedelta @@ -215,3 +216,35 @@ class Duration(Converter):          now = datetime.utcnow()          return now + delta + + +class ISODateTime(Converter): +    """"Converts an ISO-8601 datetime string into a datetime.datetime.""" + +    async def convert(self, ctx: Context, datetime_string: str) -> datetime: +        """ +        Converts a ISO-8601 `datetime_string` into a `datetime.datetime` object. + +        The converter is flexible in the formats it accepts, as it uses the `isoparse` method of +        `dateutil.parser`. In general, it accepts datetime strings that start with a date, +        optionally followed by a time. + +        See: <https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.isoparse> + +        Formats that are guaranteed to be valid by our tests are: + +        - `YYYY-mm-ddTHH:MM:SS` | `YYYY-mm-dd HH:MM:SS` +        - `YYYY-mm-ddTHH:MM` | `YYYY-mm-dd HH:MM` +        - `YYYY-mm-dd` +        - `YYYY-mm` +        - `YYYY` + +        Note: ISO-8601 specifies a `T` as the separator between the date and the time part of the +        datetime string. The converter accepts both a `T` and a single space character. +        """ +        try: +            dt = dateutil.parser.isoparse(datetime_string) +        except ValueError: +            raise BadArgument(f"`{datetime_string}` is not a valid ISO-8601 datetime string") + +        return dt diff --git a/tests/test_converters.py b/tests/test_converters.py index 35fc5d88e..aa692f9f8 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -8,6 +8,7 @@ from discord.ext.commands import BadArgument  from bot.converters import (      Duration, +    ISODateTime,      TagContentConverter,      TagNameConverter,      ValidPythonIdentifier, @@ -184,3 +185,57 @@ def test_duration_converter_for_invalid(duration: str):      converter = Duration()      with pytest.raises(BadArgument, match=f'`{duration}` is not a valid duration string.'):          asyncio.run(converter.convert(None, duration)) + + +    ("datetime_string", "expected_dt"), +    ( +        # `YYYY-mm-ddTHH:MM:SS` | `YYYY-mm-dd HH:MM:SS` +        ('2019-09-02T02:03:05', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 02:03:05', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM` | `YYYY-mm-dd HH:MM` +        ('2019-11-12T09:15', datetime.datetime(2019, 11, 12, 9, 15)), +        ('2019-11-12 09:15', datetime.datetime(2019, 11, 12, 9, 15)), + +        # `YYYY-mm-dd` +        ('2019-04-01', datetime.datetime(2019, 4, 1)), + +        # `YYYY-mm` +        ('2019-02-01', datetime.datetime(2019, 2, 1)), + +        # `YYYY` +        ('2025', datetime.datetime(2025, 1, 1)), +    ), +) +def test_isodatetime_converter_for_valid(datetime_string: str, expected_dt: datetime.datetime): +    converter = ISODateTime() +    assert asyncio.run(converter.convert(None, datetime_string)) == expected_dt + + +    ("datetime_string"), +    ( +        # Make sure it doesn't interfere with the Duration converation +        ('1Y'), +        ('1d'), +        ('1H'), + +        # Check if it fails when only providing the optional time part +        ('10:10:10'), +        ('10:00'), + +        # Invalid date format +        ('19-01-01'), + +        # Other non-valid strings +        ('fisk the tag master'), +    ), +) +def test_isodatetime_converter_for_invalid(datetime_string: str): +    converter = ISODateTime() +    with pytest.raises( +        BadArgument, +        match=f"`{datetime_string}` is not a valid ISO-8601 datetime string", +    ): +        asyncio.run(converter.convert(None, datetime_string)) | 
