aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2019-10-01 10:53:18 +0200
committerGravatar Sebastiaan Zeeff <[email protected]>2019-10-01 10:53:18 +0200
commit20da07562aab1d9041170f70e1a3dc086f5c1b90 (patch)
tree009657974cf59fd12c176af1b91bd8ad3b3512de
parentAdjust development workflow inline with new `site` changes. (#464) (diff)
Add converter for ISO-formatted datetime strings
Related to https://github.com/python-discord/bot/issues/458 This commit adds a converter that automatically parses ISO-formatted datetime strings and returns a `datetime.datetime` object. It uses `dateutil.parser.isoparse` to do the heavy lifting, so it supports the same formats as this method. In addition, I have added tests that ensure that it accepts certain formats and added a description of these 'guaranteed' formats to the `ISODate.convert` docstring. This commit should make it easy to implement #485
-rw-r--r--bot/converters.py33
-rw-r--r--tests/test_converters.py55
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))