diff -r 6c122e4e1cb2 Lib/datetime.py --- a/Lib/datetime.py Sun Feb 14 03:25:48 2016 +0000 +++ b/Lib/datetime.py Tue Feb 16 16:01:59 2016 +1030 @@ -6,6 +6,7 @@ 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 @@ -146,6 +147,12 @@ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] _DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +datetime_re = re.compile( + r'(?P\d{4})-(?P\d{2})-(?P\d{2})' + r'[T ](?P\d{2}):(?P\d{2}):' + r'(?P\d{2})(?:(?P\.\d{1,7})\d*)?' + r'(?PZ|[+-]\d{2}:\d{2})?$' +) def _build_struct_time(y, m, d, hh, mm, ss, dstflag): wday = (_ymd2ord(y, m, d) + 6) % 7 @@ -1386,7 +1393,7 @@ y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) ss = min(ss, 59) # clamp out leap seconds if the platform has them return cls(y, m, d, hh, mm, ss, us, tz) - + @classmethod def fromtimestamp(cls, t, tz=None): """Construct a datetime from a POSIX timestamp (like time.time()). @@ -1401,6 +1408,37 @@ return result @classmethod + def fromisoformat(cls, value): + """Parses a string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + Microseconds values after 6 digits are discared. + + Raises ValueError if the input is not well formatted or not a valid datetime. + """ + match = datetime_re.match(value) + if not match: + raise ValueError('invalid {} iso8601 string : {}'.format(cls.__name__, value)) + kw = match.groupdict() + tzinfo = kw.pop('tzinfo') + if tzinfo == 'Z': + tzinfo = timezone.utc + elif tzinfo is not None: + offset_hours, offset_mins = tzinfo[1:].split(':') + 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 v is not None} + if us: + us = round(float(us), 6) + kw['microsecond'] = int(us * 1e6) + kw['tzinfo'] = tzinfo + return cls(**kw) + + @classmethod def utcfromtimestamp(cls, t): """Construct a naive UTC datetime from a POSIX timestamp.""" return cls._fromtimestamp(t, True, None) @@ -2127,6 +2165,7 @@ # pretty bizarre, and a tzinfo subclass can override fromutc() if it is. try: + raise ImportError from _datetime import * except ImportError: pass diff -r 6c122e4e1cb2 Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py Sun Feb 14 03:25:48 2016 +0000 +++ b/Lib/test/datetimetester.py Tue Feb 16 16:01:59 2016 +1030 @@ -1847,6 +1847,32 @@ self.assertEqual(expected.tm_min, got.minute) self.assertEqual(expected.tm_sec, got.second) + def test_fromisoformat(self): + # Valid inputs + parse_datetime = self.theclass.fromisoformat + self.assertEqual(parse_datetime('2012-04-23T09:15:00'), + datetime(2012, 4, 23, 9, 15)) + self.assertEqual(parse_datetime('2012-04-23T09:15:00Z'), + datetime(2012, 4, 23, 9, 15, 0, 0, timezone(timedelta(minutes=0)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=2, minutes=30)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:00'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=2)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400-02:00'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=-2)))) + # rounding high + self.assertEqual(parse_datetime('2012-04-23T10:20:30.4000059'), + datetime(2012, 4, 23, 10, 20, 30, 400006)) + # rounding low + testing long usec decimal we don't care about + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400003434'), + datetime(2012, 4, 23, 10, 20, 30, 400003)) + # Invalid inputs + with self.assertRaises(ValueError): + parse_datetime('20120423091500') + parse_datetime('2012-04-56T09:15:90') + parse_datetime('2012-05-31T12:14') + + def test_fromtimestamp(self): import time