From 567666a494f6200dcaf4547175aac598f3c76dd6 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 16 Oct 2017 23:43:37 +0100 Subject: [PATCH 01/13] Support for colon when parsing time offsets This change adds support to _strptime to parse time offsets with a colon between the hour and the minutes. This unblocks the parsing of datetime string representation generated via isoformat together with all string that have the colon in its offset (which is quite common and part of the ISO8606) --- Lib/_strptime.py | 8 ++++++-- Lib/test/test_strptime.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 284175d0cfe22b..343868fa6392f4 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -210,7 +210,7 @@ def __init__(self, locale_time=None): #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d[0-5]\d)", + 'z': r"(?P[+-]\d\d:?[0-5]\d)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -455,7 +455,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): iso_week = int(found_dict['V']) elif group_key == 'z': z = found_dict['z'] - tzoffset = int(z[1:3]) * 60 + int(z[3:5]) + if z[3] == ':': + minute_start = 4 + else: + minute_start = 3 + tzoffset = int(z[1:3]) * 60 + int(z[minute_start:minute_start+2]) if z.startswith("-"): tzoffset = -tzoffset elif group_key == 'Z': diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 934318025753be..018fb446292d90 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -317,6 +317,16 @@ def test_julian(self): # Test julian directives self.helper('j', 7) + def test_offset(self): + (*_, offset), _ = _strptime._strptime("+0100", "%z") + self.assertEqual(offset, 60 * 60) + (*_, offset), _ = _strptime._strptime("-0100", "%z") + self.assertEqual(offset, -60 * 60) + (*_, offset), _ = _strptime._strptime("+01:00", "%z") + self.assertEqual(offset, 60 * 60) + (*_, offset), _ = _strptime._strptime("-01:00", "%z") + self.assertEqual(offset, -60 * 60) + def test_timezone(self): # Test timezone directives. # When gmtime() is used with %Z, entire result of strftime() is empty. From 0facd0810fefba31aeadb3aee6e97ee57d5c55cd Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 17 Oct 2017 20:08:42 +0100 Subject: [PATCH 02/13] Add news entry --- .../NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst diff --git a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst new file mode 100644 index 00000000000000..aadb3faaaea907 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst @@ -0,0 +1 @@ +Add support for parsing utc offsets with colon when using %z in strptime From af68297c09ceee9dcc2d03e302c541f5923d0df0 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 17 Oct 2017 20:21:55 +0100 Subject: [PATCH 03/13] Docs for parsing using %z and colons --- Doc/library/datetime.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 55be8694a067ba..43c2996ea5e77b 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2174,6 +2174,11 @@ Notes: .. versionchanged:: 3.7 The UTC offset is not restricted to a whole number of minutes. + .. versionchanged:: 3.7 + When ``%z`` directive is provided to the :meth:`strptime` method, offsets + with colons will be parsed as well. For example, offsets like ``'+01:00'`` + will be parsed as an offset of one hour. + ``%Z`` If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty string. Otherwise ``%Z`` is replaced by the returned value, which must From e0e3873dfcc5ab99c4f49d796b0578b340dfded6 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Thu, 19 Oct 2017 23:22:22 +0100 Subject: [PATCH 04/13] Add support for Z on %z directive --- Lib/_strptime.py | 17 ++++++++++------- Lib/test/test_strptime.py | 16 ++++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 343868fa6392f4..513feace95f12b 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -210,7 +210,7 @@ def __init__(self, locale_time=None): #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d:?[0-5]\d)", + 'z': r"(?P[+-]\d\d:?[0-5]\d|Z)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -455,13 +455,16 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): iso_week = int(found_dict['V']) elif group_key == 'z': z = found_dict['z'] - if z[3] == ':': - minute_start = 4 + if z == 'Z': + tzoffset = 0 else: - minute_start = 3 - tzoffset = int(z[1:3]) * 60 + int(z[minute_start:minute_start+2]) - if z.startswith("-"): - tzoffset = -tzoffset + if z[3] == ':': + minute_start = 4 + else: + minute_start = 3 + tzoffset = int(z[1:3]) * 60 + int(z[minute_start:minute_start+2]) + if z.startswith("-"): + tzoffset = -tzoffset elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if # it can be something other than -1. diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 018fb446292d90..dadfe17bf60ed2 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -318,14 +318,18 @@ def test_julian(self): self.helper('j', 7) def test_offset(self): - (*_, offset), _ = _strptime._strptime("+0100", "%z") - self.assertEqual(offset, 60 * 60) + one_hour = 60 * 60 + half_hour = 30 * 60 + (*_, offset), _ = _strptime._strptime("+0130", "%z") + self.assertEqual(offset, one_hour + half_hour) (*_, offset), _ = _strptime._strptime("-0100", "%z") - self.assertEqual(offset, -60 * 60) + self.assertEqual(offset, -one_hour) (*_, offset), _ = _strptime._strptime("+01:00", "%z") - self.assertEqual(offset, 60 * 60) - (*_, offset), _ = _strptime._strptime("-01:00", "%z") - self.assertEqual(offset, -60 * 60) + self.assertEqual(offset, one_hour) + (*_, offset), _ = _strptime._strptime("-01:30", "%z") + self.assertEqual(offset, -(one_hour + half_hour)) + (*_, offset), _ = _strptime._strptime("Z", "%z") + self.assertEqual(offset, 0) def test_timezone(self): # Test timezone directives. From 7c533072cfbaea789cc8e6bda29287c8c9bae99e Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Sat, 21 Oct 2017 14:56:30 +0100 Subject: [PATCH 05/13] Update docs to reflect the addition of parsing Z --- Doc/library/datetime.rst | 7 ++++--- .../next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 43c2996ea5e77b..b952921251cf19 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2175,9 +2175,10 @@ Notes: The UTC offset is not restricted to a whole number of minutes. .. versionchanged:: 3.7 - When ``%z`` directive is provided to the :meth:`strptime` method, offsets - with colons will be parsed as well. For example, offsets like ``'+01:00'`` - will be parsed as an offset of one hour. + When ``%z`` directive is provided to the :meth:`strptime` method, any valid + RFC-822/ISO 8601 standard utftime-offset can be parsed. For example, strings + like ``'+01:00'`` will be parsed as an offset of one hour and or ``'Z'`` will + be transofmred to a 0 utc offset. ``%Z`` If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty diff --git a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst index aadb3faaaea907..0044240a0c756f 100644 --- a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst +++ b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst @@ -1 +1 @@ -Add support for parsing utc offsets with colon when using %z in strptime +Add support for parsing RFC-822/ISO 8601 utf offsets From d184e7be0db49de19ae6fc7d093c5b0b207dc1f6 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 23 Oct 2017 22:14:12 +0100 Subject: [PATCH 06/13] Add support for parsing seconds --- Lib/_strptime.py | 25 +++++++++++++------------ Lib/test/test_strptime.py | 5 +++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 513feace95f12b..e8ac0245444d1b 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -210,7 +210,7 @@ def __init__(self, locale_time=None): #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d:?[0-5]\d|Z)", + 'z': r"(?P[+-]\d\d:?[0-5]\d(:?[0-5]\d)?|Z)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -365,7 +365,7 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): month = day = 1 hour = minute = second = fraction = 0 tz = -1 - tzoffset = None + gmtoff = None # Default to -1 to signify that values not known; not critical to have, # though iso_week = week_of_year = None @@ -456,15 +456,20 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): elif group_key == 'z': z = found_dict['z'] if z == 'Z': - tzoffset = 0 + gmtoff = 0 else: if z[3] == ':': - minute_start = 4 - else: - minute_start = 3 - tzoffset = int(z[1:3]) * 60 + int(z[minute_start:minute_start+2]) + z = z[:3] + z[4:] + if len(z) > 5: + if z[5] != ':': + raise ValueError(f"Unconsistent use of :") + z = z[:5] + z[6:] + hours = int(z[1:3]) + minutes = int(z[3:5]) + seconds = int(z[5:7] or 0) + gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds if z.startswith("-"): - tzoffset = -tzoffset + gmtoff = -gmtoff elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if # it can be something other than -1. @@ -542,10 +547,6 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): weekday = datetime_date(year, month, day).weekday() # Add timezone info tzname = found_dict.get("Z") - if tzoffset is not None: - gmtoff = tzoffset * 60 - else: - gmtoff = None if leap_year_fix: # the caller didn't supply a year but asked for Feb 29th. We couldn't diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index dadfe17bf60ed2..8f5b972ce9434e 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -320,14 +320,19 @@ def test_julian(self): def test_offset(self): one_hour = 60 * 60 half_hour = 30 * 60 + half_minute = 30 (*_, offset), _ = _strptime._strptime("+0130", "%z") self.assertEqual(offset, one_hour + half_hour) (*_, offset), _ = _strptime._strptime("-0100", "%z") self.assertEqual(offset, -one_hour) + (*_, offset), _ = _strptime._strptime("-013030", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute)) (*_, offset), _ = _strptime._strptime("+01:00", "%z") self.assertEqual(offset, one_hour) (*_, offset), _ = _strptime._strptime("-01:30", "%z") self.assertEqual(offset, -(one_hour + half_hour)) + (*_, offset), _ = _strptime._strptime("-01:30:30", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute)) (*_, offset), _ = _strptime._strptime("Z", "%z") self.assertEqual(offset, 0) From 380930321254b9edb1638046e48421c3b11beb3f Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 23 Oct 2017 23:04:58 +0100 Subject: [PATCH 07/13] Add support for microsecond on strptime --- Lib/_strptime.py | 4 +++- Lib/test/test_strptime.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index e8ac0245444d1b..e2d8ded3db84f4 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -210,7 +210,7 @@ def __init__(self, locale_time=None): #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d:?[0-5]\d(:?[0-5]\d)?|Z)", + 'z': r"(?P[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|Z)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -467,7 +467,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): hours = int(z[1:3]) minutes = int(z[3:5]) seconds = int(z[5:7] or 0) + microseconds = int(z[8:] or 0) gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds + gmtoff = gmtoff + (microseconds / (10**6)) if z.startswith("-"): gmtoff = -gmtoff elif group_key == 'Z': diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 8f5b972ce9434e..e7df5bc0c1af82 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -327,15 +327,31 @@ def test_offset(self): self.assertEqual(offset, -one_hour) (*_, offset), _ = _strptime._strptime("-013030", "%z") self.assertEqual(offset, -(one_hour + half_hour + half_minute)) + (*_, offset), _ = _strptime._strptime("-013030.000001", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute + 0.000001)) (*_, offset), _ = _strptime._strptime("+01:00", "%z") self.assertEqual(offset, one_hour) (*_, offset), _ = _strptime._strptime("-01:30", "%z") self.assertEqual(offset, -(one_hour + half_hour)) (*_, offset), _ = _strptime._strptime("-01:30:30", "%z") self.assertEqual(offset, -(one_hour + half_hour + half_minute)) + (*_, offset), _ = _strptime._strptime("-01:30:30.000001", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute + 0.000001)) (*_, offset), _ = _strptime._strptime("Z", "%z") self.assertEqual(offset, 0) + def test_bad_offset(self): + with self.assertRaises(ValueError): + _strptime._strptime("-01:30:30.", "%z") + with self.assertRaises(ValueError): + _strptime._strptime("-0130:30", "%z") + with self.assertRaises(ValueError): + _strptime._strptime("-01:30:30.1234567", "%z") + with self.assertRaises(ValueError): + _strptime._strptime("-01:30:30:123456", "%z") + with self.assertRaises(ValueError): + _strptime._strptime("-01:3030", "%z") + def test_timezone(self): # Test timezone directives. # When gmtime() is used with %Z, entire result of strftime() is empty. From 7548704725775339c8dbf7c557246686b7383a40 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 23 Oct 2017 23:09:38 +0100 Subject: [PATCH 08/13] Add test case with the new format on datetimetester --- Lib/test/datetimetester.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 4edfb42d35511e..e4a113c2bef0ac 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2147,6 +2147,10 @@ def test_strptime(self): strptime = self.theclass.strptime self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) + self.assertEqual( + strptime("-00:02:01.000003", "%z").utcoffset(), + timedelta(minutes=-2, seconds=-1, microseconds=-3) + ) # Only local timezone and UTC are supported for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), (-_time.timezone, _time.tzname[0])): From 69e655ce2faeb688aac5c3cfd4dfa31b9350e2b5 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 23 Oct 2017 23:26:30 +0100 Subject: [PATCH 09/13] Improve error message on : invalid use --- Lib/_strptime.py | 3 ++- Lib/test/test_strptime.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index e2d8ded3db84f4..cb76f02387f19b 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -462,7 +462,8 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): z = z[:3] + z[4:] if len(z) > 5: if z[5] != ':': - raise ValueError(f"Unconsistent use of :") + msg = f"Unconsistent use of : in {found_dict['z']}" + raise ValueError(msg) z = z[:5] + z[6:] hours = int(z[1:3]) minutes = int(z[3:5]) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index e7df5bc0c1af82..248f2d39294e18 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -349,8 +349,9 @@ def test_bad_offset(self): _strptime._strptime("-01:30:30.1234567", "%z") with self.assertRaises(ValueError): _strptime._strptime("-01:30:30:123456", "%z") - with self.assertRaises(ValueError): + with self.assertRaises(ValueError) as err: _strptime._strptime("-01:3030", "%z") + self.assertEqual("Unconsistent use of : in -01:3030", str(err.exception)) def test_timezone(self): # Test timezone directives. From f718b37e3ba4dc414dccfcc32954f7290e6332c6 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 23 Oct 2017 23:39:28 +0100 Subject: [PATCH 10/13] Typo: utc -> UTC --- Doc/library/datetime.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index b952921251cf19..60f81aea37f5c7 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2178,7 +2178,7 @@ Notes: When ``%z`` directive is provided to the :meth:`strptime` method, any valid RFC-822/ISO 8601 standard utftime-offset can be parsed. For example, strings like ``'+01:00'`` will be parsed as an offset of one hour and or ``'Z'`` will - be transofmred to a 0 utc offset. + be transofmred to a 0 UTC offset. ``%Z`` If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty From 5cfeeee558bbe0f652577ac18d9b5511e6fe2e5a Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 24 Oct 2017 09:36:44 +0100 Subject: [PATCH 11/13] Handle gmtoff miliseconds via a separate variable --- Lib/_strptime.py | 11 ++++++----- Lib/test/datetimetester.py | 2 +- Lib/test/test_strptime.py | 33 +++++++++++++++++++++------------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index cb76f02387f19b..f5195af90c8a4d 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -366,6 +366,7 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): hour = minute = second = fraction = 0 tz = -1 gmtoff = None + gmtoff_fraction = 0 # Default to -1 to signify that values not known; not critical to have, # though iso_week = week_of_year = None @@ -468,11 +469,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): hours = int(z[1:3]) minutes = int(z[3:5]) seconds = int(z[5:7] or 0) - microseconds = int(z[8:] or 0) gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds - gmtoff = gmtoff + (microseconds / (10**6)) + gmtoff_fraction = int(z[8:] or 0) if z.startswith("-"): gmtoff = -gmtoff + gmtoff_fraction = -gmtoff_fraction elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if # it can be something other than -1. @@ -559,7 +560,7 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): return (year, month, day, hour, minute, second, - weekday, julian, tz, tzname, gmtoff), fraction + weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"): """Return a time struct based on the input string and the @@ -570,11 +571,11 @@ def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"): def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"): """Return a class cls instance based on the input string and the format string.""" - tt, fraction = _strptime(data_string, format) + tt, fraction, gmtoff_fraction = _strptime(data_string, format) tzname, gmtoff = tt[-2:] args = tt[:6] + (fraction,) if gmtoff is not None: - tzdelta = datetime_timedelta(seconds=gmtoff) + tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction) if tzname: tz = datetime_timezone(tzdelta, tzname) else: diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index e4a113c2bef0ac..c5f91fbe1840ba 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2149,7 +2149,7 @@ def test_strptime(self): self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) self.assertEqual( strptime("-00:02:01.000003", "%z").utcoffset(), - timedelta(minutes=-2, seconds=-1, microseconds=-3) + -timedelta(minutes=2, seconds=1, microseconds=3) ) # Only local timezone and UTC are supported for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 248f2d39294e18..1251886779d207 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -305,7 +305,7 @@ def test_fraction(self): # Test microseconds import datetime d = datetime.datetime(2012, 12, 20, 12, 34, 56, 78987) - tup, frac = _strptime._strptime(str(d), format="%Y-%m-%d %H:%M:%S.%f") + tup, frac, _ = _strptime._strptime(str(d), format="%Y-%m-%d %H:%M:%S.%f") self.assertEqual(frac, d.microsecond) def test_weekday(self): @@ -321,24 +321,33 @@ def test_offset(self): one_hour = 60 * 60 half_hour = 30 * 60 half_minute = 30 - (*_, offset), _ = _strptime._strptime("+0130", "%z") + (*_, offset), _, offset_fraction = _strptime._strptime("+0130", "%z") self.assertEqual(offset, one_hour + half_hour) - (*_, offset), _ = _strptime._strptime("-0100", "%z") + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-0100", "%z") self.assertEqual(offset, -one_hour) - (*_, offset), _ = _strptime._strptime("-013030", "%z") + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-013030", "%z") self.assertEqual(offset, -(one_hour + half_hour + half_minute)) - (*_, offset), _ = _strptime._strptime("-013030.000001", "%z") - self.assertEqual(offset, -(one_hour + half_hour + half_minute + 0.000001)) - (*_, offset), _ = _strptime._strptime("+01:00", "%z") + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-013030.000001", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute)) + self.assertEqual(offset_fraction, -1) + (*_, offset), _, offset_fraction = _strptime._strptime("+01:00", "%z") self.assertEqual(offset, one_hour) - (*_, offset), _ = _strptime._strptime("-01:30", "%z") + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-01:30", "%z") self.assertEqual(offset, -(one_hour + half_hour)) - (*_, offset), _ = _strptime._strptime("-01:30:30", "%z") + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-01:30:30", "%z") + self.assertEqual(offset, -(one_hour + half_hour + half_minute)) + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-01:30:30.000001", "%z") self.assertEqual(offset, -(one_hour + half_hour + half_minute)) - (*_, offset), _ = _strptime._strptime("-01:30:30.000001", "%z") - self.assertEqual(offset, -(one_hour + half_hour + half_minute + 0.000001)) - (*_, offset), _ = _strptime._strptime("Z", "%z") + self.assertEqual(offset_fraction, -1) + (*_, offset), _, offset_fraction = _strptime._strptime("Z", "%z") self.assertEqual(offset, 0) + self.assertEqual(offset_fraction, 0) def test_bad_offset(self): with self.assertRaises(ValueError): From 546b7b835eec353137a097cb85493ea1dcfabf30 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Wed, 25 Oct 2017 09:21:07 +0100 Subject: [PATCH 12/13] Enrich NEWS entry with latest changes --- .../next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst index 0044240a0c756f..7586b913125bca 100644 --- a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst +++ b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst @@ -1 +1,3 @@ -Add support for parsing RFC-822/ISO 8601 utf offsets +Add support for parsing RFC-822/ISO 8601 UTC offsets. strptime '%z' can now +parse the output generated by datetime.isoformat, including seconds and +microseconds. From c20b57fcdda9f43f69ff46beddedd1231920dad3 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Wed, 25 Oct 2017 18:53:58 +0100 Subject: [PATCH 13/13] Update docs on new %z behaviour --- Doc/library/datetime.rst | 9 +++++---- .../Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 60f81aea37f5c7..d032d283eb9b53 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2175,10 +2175,11 @@ Notes: The UTC offset is not restricted to a whole number of minutes. .. versionchanged:: 3.7 - When ``%z`` directive is provided to the :meth:`strptime` method, any valid - RFC-822/ISO 8601 standard utftime-offset can be parsed. For example, strings - like ``'+01:00'`` will be parsed as an offset of one hour and or ``'Z'`` will - be transofmred to a 0 UTC offset. + When the ``%z`` directive is provided to the :meth:`strptime` method, + the UTC offsets can have a colon as a separator between hours, minutes + and seconds. + For example, ``'+01:00:00'`` will be parsed as an offset of one hour. + In addition, providing ``'Z'`` is identical to ``'+00:00'``. ``%Z`` If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty diff --git a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst index 7586b913125bca..1580440a595d5b 100644 --- a/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst +++ b/Misc/NEWS.d/next/Library/2017-10-17-20-08-19.bpo-31800.foOSCi.rst @@ -1,3 +1,3 @@ -Add support for parsing RFC-822/ISO 8601 UTC offsets. strptime '%z' can now +Extended support for parsing UTC offsets. strptime '%z' can now parse the output generated by datetime.isoformat, including seconds and microseconds.