aboutsummaryrefslogtreecommitdiff
path: root/dateutil
diff options
context:
space:
mode:
authorJay Weisskopf <jay@jayschwa.net>2018-07-03 15:08:32 -0400
committerJay Weisskopf <jay@jayschwa.net>2018-07-03 15:08:32 -0400
commit9d2edc0e17cc16eaea49dbea379b85ba4f1e610e (patch)
tree593e9f54b358dfc5feb05d045337dd8b9d31a897 /dateutil
parent48f3860b83373d1603c3d4c542562f09925303a9 (diff)
downloaddateutil-9d2edc0e17cc16eaea49dbea379b85ba4f1e610e.tar.gz
Accept more than 6 fractional digits in `isoparse`
RFC 3339 does not specify a limit to the number of fractional digits that can be provided. In the wild, languages like Go will provide up to 9 digits by default. Since the standard library's `datetime` only supports precision down to microseconds, any extra digits in the fractional component are truncated. Fixes #786
Diffstat (limited to 'dateutil')
-rw-r--r--dateutil/parser/isoparser.py17
-rw-r--r--dateutil/test/test_isoparser.py12
2 files changed, 17 insertions, 12 deletions
diff --git a/dateutil/parser/isoparser.py b/dateutil/parser/isoparser.py
index 7c755e5..172972b 100644
--- a/dateutil/parser/isoparser.py
+++ b/dateutil/parser/isoparser.py
@@ -88,7 +88,7 @@ class isoparser(object):
- ``hh``
- ``hh:mm`` or ``hhmm``
- ``hh:mm:ss`` or ``hhmmss``
- - ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
+ - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
Midnight is a special case for `hh`, as the standard supports both
00:00 and 24:00 as a representation. The decimal separator can be
@@ -199,10 +199,9 @@ class isoparser(object):
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
# Constants
- _MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
_DATE_SEP = b'-'
_TIME_SEP = b':'
- _MICRO_SEPS = b'.,'
+ _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
def _parse_isodate(self, dt_str):
try:
@@ -357,16 +356,14 @@ class isoparser(object):
pos += 1
if comp == 3:
- # Microsecond
- if timestr[pos:pos + 1] not in self._MICRO_SEPS:
+ # Fraction of a second
+ frac = self._FRACTION_REGEX.match(timestr[pos:])
+ if not frac:
continue
- pos += 1
- us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
- 1)[0]
-
+ us_str = frac.group(1)[:6] # Truncate to microseconds
components[comp] = int(us_str) * 10**(6 - len(us_str))
- pos += len(us_str)
+ pos += len(frac.group())
if pos < len_str:
raise ValueError('Unused components in ISO string')
diff --git a/dateutil/test/test_isoparser.py b/dateutil/test/test_isoparser.py
index f4fa943..e60b0b0 100644
--- a/dateutil/test/test_isoparser.py
+++ b/dateutil/test/test_isoparser.py
@@ -130,6 +130,15 @@ def test_ymd_hms_micro(dt, date_fmt, time_fmt, tzoffset, precision):
_isoparse_date_and_time(dt, date_fmt, time_fmt, tzoffset, precision)
+###
+# Truncation of extra digits beyond microsecond precision
+@pytest.mark.parametrize('dt_str', [
+ '2018-07-03T14:07:00.123456000001',
+ '2018-07-03T14:07:00.123456999999',
+])
+def test_extra_subsecond_digits(dt_str):
+ assert isoparse(dt_str) == datetime(2018, 7, 3, 14, 7, 0, 123456)
+
@pytest.mark.parametrize('tzoffset', FULL_TZOFFSETS)
def test_full_tzoffsets(tzoffset):
dt = datetime(2017, 11, 27, 6, 14, 30, 123456)
@@ -473,12 +482,11 @@ def test_isotime_midnight(isostr):
('14時30分15秒', ValueError), # Not ASCII
('14_30_15', ValueError), # Invalid separators
('1430:15', ValueError), # Inconsistent separator use
- ('14:30:15.3684000309', ValueError), # Too much us precision
('25', ValueError), # Invalid hours
('25:15', ValueError), # Invalid hours
('14:60', ValueError), # Invalid minutes
('14:59:61', ValueError), # Invalid seconds
- ('14:30:15.3446830500', ValueError), # No sign in time zone
+ ('14:30:15.34468305:00', ValueError), # No sign in time zone
('14:30:15+', ValueError), # Time zone too short
('14:30:15+1234567', ValueError), # Time zone invalid
('14:59:59+25:00', ValueError), # Invalid tz hours