diff -r 6c122e4e1cb2 Lib/datetime.py --- a/Lib/datetime.py Sun Feb 14 03:25:48 2016 +0000 +++ b/Lib/datetime.py Sun Feb 14 22:17:06 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 @@ -40,7 +41,7 @@ def _days_before_year(year): "year -> number of days before January 1st of year." y = year - 1 - return y*365 + y//4 - y//100 + y//400 + return y * 365 + y // 4 - y // 100 + y // 400 def _days_in_month(year, month): "year, month -> number of days in that month in that year." @@ -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{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'(?PZ|[+-]\d{2}(?::?\d{2})?)?$' +) def _build_struct_time(y, m, d, hh, mm, ss, dstflag): wday = (_ymd2ord(y, m, d) + 6) % 7 @@ -1401,6 +1408,34 @@ 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. + + Raises ValueError if the input is well formatted but not a valid datetime. + Returns None if the input isn't well formatted. + """ + match = datetime_re.match(value) + if match: + kw = match.groupdict() + if kw['microsecond']: + kw['microsecond'] = kw['microsecond'].ljust(6, '0') + tzinfo = kw.pop('tzinfo') + if tzinfo == 'Z': + tzinfo = timezone.utc + elif tzinfo is not None: + offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 + offset = 60 * int(tzinfo[1:3]) + offset_mins + if tzinfo[0] == '-': + offset = -offset + tzinfo = timezone(timedelta(minutes=offset)) + kw = {k: int(v) for k, v in kw.items() if v is not None} + 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) 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 Sun Feb 14 22:17:06 2016 +1030 @@ -1847,6 +1847,34 @@ self.assertEqual(expected.tm_min, got.minute) self.assertEqual(expected.tm_sec, got.second) + def test_fromisoformat(self): + t = self.theclass(2012, 12, 1, 23, 59, 0, 411, tzinfo=timezone.utc) + self.assertEqual(t, self.theclass.fromisoformat(t.isoformat())) + + def test_parse_datetime(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-4-9 4:8:16'), + datetime(2012, 4, 9, 4, 8, 16)) + 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-4-9 4:8:16-0320'), + datetime(2012, 4, 9, 4, 8, 16, 0, timezone(timedelta(minutes=-200)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(minutes=150)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(minutes=120)))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400-02'), + datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(minutes=-120)))) + # Invalid inputs + self.assertEqual(parse_datetime('20120423091500'), None) + with self.assertRaises(ValueError): + parse_datetime('2012-04-56T09:15:90') + + def test_fromtimestamp(self): import time