Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(9)

Unified Diff: Lib/datetime.py

Issue 15873: "datetime" cannot parse ISO 8601 dates and times
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | Lib/test/datetimetester.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
index 9f942a2..336d0dd 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -6,6 +6,7 @@ time zone and DST data sources.
import time as _time
import math as _math
+import re
def _cmp(x, y):
return 0 if x == y else 1 if x > y else -1
@@ -331,6 +332,31 @@ def _divide_and_round(a, b):
return q
+def _parse_isotime(cls, isostring):
+ match = cls._isore.match(isostring)
+ if not match:
+ raise ValueError("invalid RFC 3339 %s string: %r"
+ % (cls.__name__, isostring))
+ kw = match.groupdict()
+ tzinfo = kw.pop('tzinfo', None)
+ if tzinfo == 'Z' or tzinfo == 'z':
+ tzinfo = timezone.utc
+ elif tzinfo is not None:
+ offset_hours, _, offset_mins = tzinfo[1:].partition(':')
+ offset = timedelta(hours=int(offset_hours), minutes=int(offset_mins))
+ if tzinfo[0] == '-':
+ offset = -offset
+ tzinfo = timezone(offset)
+ us = kw.pop('microsecond', None)
+ kw = {k: int(v) for k, v in kw.items()}
+ if us:
+ us = round(float(us), 6)
+ kw['microsecond'] = int(us * 1e6)
+ if tzinfo:
+ kw['tzinfo'] = tzinfo
+ return cls(**kw)
+
+
class timedelta:
"""Represent the difference between two datetime objects.
@@ -683,6 +709,8 @@ class date:
"""
__slots__ = '_year', '_month', '_day', '_hashcode'
+ _isore = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$', re.ASCII)
+
def __new__(cls, year, month=None, day=None):
"""Constructor.
@@ -729,6 +757,14 @@ class date:
y, m, d = _ord2ymd(n)
return cls(y, m, d)
+ @classmethod
+ def fromisoformat(cls, date_string):
+ """Constructs a date from an RFC 3339 string, a strict subset of ISO 8601
+
+ Raises ValueError in case of ill-formatted or invalid string.
+ """
+ return _parse_isotime(cls, date_string)
+
# Conversions to string
def __repr__(self):
@@ -1018,6 +1054,7 @@ class tzinfo:
_tzinfo_class = tzinfo
+
class time:
"""Time with time zone.
@@ -1043,6 +1080,11 @@ class time:
"""
__slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode', '_fold'
+ _isore = re.compile(r'(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})'
+ r'(?P<microsecond>\.\d+)?(?P<tzinfo>Z|[+-]\d{2}:\d{2})?$',
+ re.ASCII|re.IGNORECASE)
+
+
def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
"""Constructor.
@@ -1072,6 +1114,15 @@ class time:
self._fold = fold
return self
+ @classmethod
+ def fromisoformat(cls, time_string):
+ """Constructs a time from an RFC 3339 string, a strict subset of ISO 8601
+
+ Microseconds are rounded to 6 digits.
+ Raises ValueError in case of ill-formatted or invalid string.
+ """
+ return _parse_isotime(cls, time_string)
+
# Read-only field accessors
@property
def hour(self):
@@ -1360,6 +1411,9 @@ class datetime(date):
"""
__slots__ = date.__slots__ + time.__slots__
+ _isore = re.compile(date._isore.pattern[:-1] + r'[T ]' +
+ time._isore.pattern, re.ASCII|re.IGNORECASE)
+
def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
microsecond=0, tzinfo=None, *, fold=0):
if isinstance(year, bytes) and len(year) == 10 and 1 <= year[2]&0x7F <= 12:
@@ -2252,6 +2306,7 @@ _EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
# pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
try:
+ raise ImportError
from _datetime import *
except ImportError:
pass
« no previous file with comments | « no previous file | Lib/test/datetimetester.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+