aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2019-10-02 10:42:43 +0200
committerGravatar Sebastiaan Zeeff <[email protected]>2019-10-02 10:42:43 +0200
commita8b600217cb9ab4524bb307f0a6a922a0d8815be (patch)
tree2fe8881806e356b1eba3fe28044a49db2d52f942
parentRemove angle brackets from ISODateTime docstring (diff)
Make ISODateTime return tz-unaware datetime
The parser we use, `dateutil.parsers.isoparse` returns a timezone- aware or timezone-unaware `datetime` object depending on whether or not the datetime string included a timezone offset specification. Since we can't compare tz-aware objects to tz-unaware objects it's better to make sure our converter is consistent in the type it will return. For now, I've chosen to return tz-unaware datetime objects, since `discord.py` also returns tz-unaware datetime objects when accessing datetime-related attributes of objects. Since we're likely to compare "our" datetime objects to discord.py-provided datetime objects, I think that's the most parsimonious option for now. Note: It's probably a good idea to open a larger discussion about using timezone-aware datetime objects throughout the library to avoid a UTC-time being interpreted as localtime. This will require a broader discussion than this commit/PR allows, though.
-rw-r--r--bot/converters.py13
-rw-r--r--tests/test_converters.py21
2 files changed, 33 insertions, 1 deletions
diff --git a/bot/converters.py b/bot/converters.py
index 59a6f6b07..27223e632 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -5,6 +5,7 @@ from ssl import CertificateError
from typing import Union
import dateutil.parser
+import dateutil.tz
import discord
from aiohttp import ClientConnectorError
from dateutil.relativedelta import relativedelta
@@ -227,12 +228,18 @@ class ISODateTime(Converter):
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.
+ optionally followed by a time. Specifying a timezone offset in the datetime string is
+ supported, but the `datetime` object will be converted to UTC and will be returned without
+ `tzinfo` as a timezone-unaware `datetime` object.
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:SSZ` | `YYYY-mm-dd HH:MM:SSZ`
+ - `YYYY-mm-ddTHH:MM:SS±HH:MM` | `YYYY-mm-dd HH:MM:SS±HH:MM`
+ - `YYYY-mm-ddTHH:MM:SS±HHMM` | `YYYY-mm-dd HH:MM:SS±HHMM`
+ - `YYYY-mm-ddTHH:MM:SS±HH` | `YYYY-mm-dd HH:MM:SS±HH`
- `YYYY-mm-ddTHH:MM:SS` | `YYYY-mm-dd HH:MM:SS`
- `YYYY-mm-ddTHH:MM` | `YYYY-mm-dd HH:MM`
- `YYYY-mm-dd`
@@ -247,4 +254,8 @@ class ISODateTime(Converter):
except ValueError:
raise BadArgument(f"`{datetime_string}` is not a valid ISO-8601 datetime string")
+ if dt.tzinfo:
+ dt = dt.astimezone(dateutil.tz.UTC)
+ dt = dt.replace(tzinfo=None)
+
return dt
diff --git a/tests/test_converters.py b/tests/test_converters.py
index 8093f55ac..86e8f2249 100644
--- a/tests/test_converters.py
+++ b/tests/test_converters.py
@@ -190,6 +190,27 @@ def test_duration_converter_for_invalid(duration: str):
@pytest.mark.parametrize(
("datetime_string", "expected_dt"),
(
+
+ # `YYYY-mm-ddTHH:MM:SSZ` | `YYYY-mm-dd HH:MM:SSZ`
+ ('2019-09-02T02:03:05Z', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02 02:03:05Z', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+
+ # `YYYY-mm-ddTHH:MM:SS±HH:MM` | `YYYY-mm-dd HH:MM:SS±HH:MM`
+ ('2019-09-02T03:18:05+01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02 03:18:05+01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02T00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02 00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+
+ # `YYYY-mm-ddTHH:MM:SS±HHMM` | `YYYY-mm-dd HH:MM:SS±HHMM`
+ ('2019-09-02T03:18:05+0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02 03:18:05+0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02T00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02 00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+
+ # `YYYY-mm-ddTHH:MM:SS±HH` | `YYYY-mm-dd HH:MM:SS±HH`
+ ('2019-09-02 03:03:05+01', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('2019-09-02T01:03:05-01', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+
# `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)),