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\d{4})-(?P\d{2})-(?P\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\d{2}):(?P\d{2}):(?P\d{2})' + r'(?P\.\d+)?(?PZ|[+-]\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