From f35ab74752adb37138112657c1bc8b91f50e799b Mon Sep 17 00:00:00 2001 From: Keryn Knight Date: Thu, 1 Jul 2021 12:05:41 +0100 Subject: [PATCH] Fixed #32892 -- Optimized django.utils.dateparse functions by using fromisoformat(). --- django/utils/dateparse.py | 61 ++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py index 47791b9331..60840933d5 100644 --- a/django/utils/dateparse.py +++ b/django/utils/dateparse.py @@ -72,10 +72,12 @@ def parse_date(value): Raise ValueError if the input is well formatted but not a valid date. Return None if the input isn't well formatted. """ - match = date_re.match(value) - if match: - kw = {k: int(v) for k, v in match.groupdict().items()} - return datetime.date(**kw) + try: + return datetime.date.fromisoformat(value) + except ValueError: + if match := date_re.match(value): + kw = {k: int(v) for k, v in match.groupdict().items()} + return datetime.date(**kw) def parse_time(value): @@ -87,12 +89,18 @@ def parse_time(value): Return None if the input isn't well formatted, in particular if it contains an offset. """ - match = time_re.match(value) - if match: - kw = match.groupdict() - kw['microsecond'] = kw['microsecond'] and kw['microsecond'].ljust(6, '0') - kw = {k: int(v) for k, v in kw.items() if v is not None} - return datetime.time(**kw) + try: + # The fromisoformat() method takes time zone info into account and + # returns a time with a tzinfo component, if possible. However, there + # are no circumstances where aware datetime.time objects make sense, so + # remove the time zone offset. + return datetime.time.fromisoformat(value).replace(tzinfo=None) + except ValueError: + if match := time_re.match(value): + kw = match.groupdict() + kw['microsecond'] = kw['microsecond'] and kw['microsecond'].ljust(6, '0') + kw = {k: int(v) for k, v in kw.items() if v is not None} + return datetime.time(**kw) def parse_datetime(value): @@ -104,22 +112,23 @@ def parse_datetime(value): Raise ValueError if the input is well formatted but not a valid datetime. Return None if the input isn't well formatted. """ - match = datetime_re.match(value) - if match: - kw = match.groupdict() - kw['microsecond'] = kw['microsecond'] and kw['microsecond'].ljust(6, '0') - tzinfo = kw.pop('tzinfo') - if tzinfo == 'Z': - tzinfo = utc - elif tzinfo is not None: - offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 - offset = 60 * int(tzinfo[1:3]) + offset_mins - if tzinfo[0] == '-': - offset = -offset - tzinfo = get_fixed_timezone(offset) - kw = {k: int(v) for k, v in kw.items() if v is not None} - kw['tzinfo'] = tzinfo - return datetime.datetime(**kw) + try: + return datetime.datetime.fromisoformat(value) + except ValueError: + if match := datetime_re.match(value): + kw = match.groupdict() + kw['microsecond'] = kw['microsecond'] and kw['microsecond'].ljust(6, '0') + tzinfo = kw.pop('tzinfo') + if tzinfo == 'Z': + tzinfo = utc + elif tzinfo is not None: + offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 + offset = 60 * int(tzinfo[1:3]) + offset_mins + if tzinfo[0] == '-': + offset = -offset + tzinfo = get_fixed_timezone(offset) + kw = {k: int(v) for k, v in kw.items() if v is not None} + return datetime.datetime(**kw, tzinfo=tzinfo) def parse_duration(value):