diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py index 535655561c..0945c0c761 100644 --- a/django/utils/dateparse.py +++ b/django/utils/dateparse.py @@ -16,13 +16,13 @@ date_re = _lazy_re_compile( time_re = _lazy_re_compile( r'(?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + r'(?::(?P\d{1,2})(?:[\.,](?P\d{1,6})\d{0,6})?)?' ) datetime_re = _lazy_re_compile( r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' r'[T ](?P\d{1,2}):(?P\d{1,2})' - r'(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?' + r'(?::(?P\d{1,2})(?:[\.,](?P\d{1,6})\d{0,6})?)?' r'(?PZ|[+-]\d{2}(?::?\d{2})?)?$' ) @@ -33,7 +33,7 @@ standard_duration_re = _lazy_re_compile( r'((?:(?P\d+):)(?=\d+:\d+))?' r'(?:(?P\d+):)?' r'(?P\d+)' - r'(?:\.(?P\d{1,6})\d{0,6})?' + r'(?:[\.,](?P\d{1,6})\d{0,6})?' r'$' ) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 8aa8c3403d..33afbac36a 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -139,6 +139,10 @@ The functions defined in this module share the following properties: UTC offsets aren't supported; if ``value`` describes one, the result is ``None``. + .. versionchanged:: 3.1 + + Support for comma separators for milliseconds was added. + .. function:: parse_datetime(value) Parses a string and returns a :class:`datetime.datetime`. @@ -146,18 +150,23 @@ The functions defined in this module share the following properties: UTC offsets are supported; if ``value`` describes one, the result's ``tzinfo`` attribute is a :class:`datetime.timezone` instance. + .. versionchanged:: 3.1 + + Support for comma separators for milliseconds was added. + .. function:: parse_duration(value) Parses a string and returns a :class:`datetime.timedelta`. - Expects data in the format ``"DD HH:MM:SS.uuuuuu"`` or as specified by ISO - 8601 (e.g. ``P4DT1H15M20S`` which is equivalent to ``4 1:15:20``) or - PostgreSQL's day-time interval format (e.g. ``3 days 04:05:06``). + Expects data in the format ``"DD HH:MM:SS.uuuuuu"``, + ``"DD HH:MM:SS,uuuuuu"``, or as specified by ISO 8601 (e.g. + ``P4DT1H15M20S`` which is equivalent to ``4 1:15:20``) or PostgreSQL's + day-time interval format (e.g. ``3 days 04:05:06``). .. versionchanged:: 3.1 Support for comma separators for decimal fractions in the ISO 8601 - format was added. + format and for the format ``"DD HH:MM:SS,uuuuuu"`` was added. ``django.utils.decorators`` =========================== diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index ed841cc962..dc16b95f79 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -259,6 +259,11 @@ Utilities * :func:`~django.utils.dateparse.parse_duration` now supports comma separators for decimal fractions in the ISO 8601 format. +* :func:`~django.utils.dateparse.parse_datetime`, + :func:`~django.utils.dateparse.parse_duration`, and + :func:`~django.utils.dateparse.parse_time` now support comma separators for + milliseconds. + Validators ~~~~~~~~~~ diff --git a/tests/utils_tests/test_dateparse.py b/tests/utils_tests/test_dateparse.py index b1b591a7df..844b7e4ab7 100644 --- a/tests/utils_tests/test_dateparse.py +++ b/tests/utils_tests/test_dateparse.py @@ -23,6 +23,7 @@ class DateParseTests(unittest.TestCase): self.assertEqual(parse_time('09:15:00'), time(9, 15)) self.assertEqual(parse_time('10:10'), time(10, 10)) self.assertEqual(parse_time('10:20:30.400'), time(10, 20, 30, 400000)) + self.assertEqual(parse_time('10:20:30,400'), time(10, 20, 30, 400000)) self.assertEqual(parse_time('4:8:16'), time(4, 8, 16)) # Invalid inputs self.assertIsNone(parse_time('091500')) @@ -38,6 +39,7 @@ class DateParseTests(unittest.TestCase): ('2012-04-23T10:20:30.400+02:30', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(150))), ('2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(120))), ('2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(-120))), + ('2012-04-23T10:20:30,400-02', datetime(2012, 4, 23, 10, 20, 30, 400000, get_fixed_timezone(-120))), ) for source, expected in valid_inputs: with self.subTest(source=source): @@ -104,6 +106,7 @@ class DurationParseTests(unittest.TestCase): ('15:30.0001', timedelta(minutes=15, seconds=30, microseconds=100)), ('15:30.00001', timedelta(minutes=15, seconds=30, microseconds=10)), ('15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)), + ('15:30,000001', timedelta(minutes=15, seconds=30, microseconds=1)), ) for source, expected in test_values: with self.subTest(source=source): @@ -116,6 +119,7 @@ class DurationParseTests(unittest.TestCase): ('-15:30', timedelta(minutes=-15, seconds=-30)), ('-1:15:30', timedelta(hours=-1, minutes=-15, seconds=-30)), ('-30.1', timedelta(seconds=-30, milliseconds=-100)), + ('-30,1', timedelta(seconds=-30, milliseconds=-100)), ('-00:01:01', timedelta(minutes=-1, seconds=-1)), ('-01:01', timedelta(seconds=-61)), ('-01:-01', None),