From bbfb1b9d7c8f11c6cacc1683f05555f7201d82f4 Mon Sep 17 00:00:00 2001 From: donBarbos Date: Fri, 21 Feb 2025 07:43:14 +0400 Subject: [PATCH 1/7] Add support `+HH` format as time zone in `datetime.strptime` --- Doc/library/datetime.rst | 8 ++++---- Lib/_strptime.py | 6 +++--- .../2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 1af7d6be750102..c9bdd42adaac5f 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2527,8 +2527,8 @@ requires, and these work on all platforms with a standard C implementation. | | digits. | | | +-----------+--------------------------------+------------------------+-------+ | ``%z`` | UTC offset in the form | (empty), +0000, | \(6) | -| | ``±HHMM[SS[.ffffff]]`` (empty | -0400, +1030, | | -| | string if the object is | +063415, | | +| | ``±HH[MM[SS[.ffffff]]]`` | -0400, +1030, | | +| | (empty string if the object is | +063415, | | | | naive). | -030712.345216 | | +-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | @@ -2589,7 +2589,7 @@ convenience. These parameters all correspond to ISO 8601 date values. | | Jan 4. | | | +-----------+--------------------------------+------------------------+-------+ | ``%:z`` | UTC offset in the form | (empty), +00:00, | \(6) | -| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | +| | ``±HH[:MM[:SS[.ffffff]]]`` | -04:00, +10:30, | | | | (empty string if the object is | +06:34:15, | | | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ @@ -2687,7 +2687,7 @@ Notes: ``%z`` :meth:`~.datetime.utcoffset` is transformed into a string of the form - ``±HHMM[SS[.ffffff]]``, where ``HH`` is a 2-digit string giving the number + ``±HH[MM[SS[.ffffff]]]``, where ``HH`` is a 2-digit string giving the number of UTC offset hours, ``MM`` is a 2-digit string giving the number of UTC offset minutes, ``SS`` is a 2-digit string giving the number of UTC offset seconds and ``ffffff`` is a 6-digit string giving the number of UTC diff --git a/Lib/_strptime.py b/Lib/_strptime.py index e6e23596db6f99..d40c413c34b5ed 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -302,7 +302,7 @@ def __init__(self, locale_time=None): # W is set below by using 'U' 'y': r"(?P\d\d)", 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))", + 'z': r"(?P[+-]\d\d(:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)?|(?-i: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'), @@ -552,7 +552,7 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): if z == 'Z': gmtoff = 0 else: - if z[3] == ':': + if len(z) != 3 and z[3] == ':': z = z[:3] + z[4:] if len(z) > 5: if z[5] != ':': @@ -560,7 +560,7 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): raise ValueError(msg) z = z[:5] + z[6:] hours = int(z[1:3]) - minutes = int(z[3:5]) + minutes = int(z[3:5] or 0) seconds = int(z[5:7] or 0) gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds gmtoff_remainder = z[8:] diff --git a/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst new file mode 100644 index 00000000000000..603e036ee96d66 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst @@ -0,0 +1,3 @@ +Update input time zone format from ``±HHMM[SS[.ffffff]]`` to +``±HH[MM[SS[.ffffff]]]`` for :meth:`date.strptime`, :meth:`datetime.strptime` +and :meth:`time.strptime` methods. Patch by Semyon Moroz. From dde711fec03aaca22d172038e3b80068342d87db Mon Sep 17 00:00:00 2001 From: donBarbos Date: Fri, 21 Feb 2025 08:06:28 +0400 Subject: [PATCH 2/7] Add tests --- Lib/test/datetimetester.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index b670973a71c748..2bdbc3c4ac691e 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2860,6 +2860,8 @@ def test_strptime(self): strptime = self.theclass.strptime + self.assertEqual(strptime("+01", "%z").utcoffset(), 1 * HOUR) + self.assertEqual(strptime("-10", "%z").utcoffset(), -10 * HOUR) self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) self.assertEqual( @@ -3958,6 +3960,8 @@ def test_strptime(self): def test_strptime_tz(self): strptime = self.theclass.strptime + self.assertEqual(strptime("+01", "%z").utcoffset(), 1 * HOUR) + self.assertEqual(strptime("-10", "%z").utcoffset(), -10 * HOUR) self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) self.assertEqual( From 1e32c1d8ee6d9a7a054ed3eda1e40324e567cea5 Mon Sep 17 00:00:00 2001 From: donBarbos Date: Fri, 21 Feb 2025 08:12:06 +0400 Subject: [PATCH 3/7] Update News.d entry --- .../Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst index 603e036ee96d66..b6b74d746db4d8 100644 --- a/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst +++ b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst @@ -1,3 +1,4 @@ Update input time zone format from ``±HHMM[SS[.ffffff]]`` to -``±HH[MM[SS[.ffffff]]]`` for :meth:`date.strptime`, :meth:`datetime.strptime` -and :meth:`time.strptime` methods. Patch by Semyon Moroz. +``±HH[MM[SS[.ffffff]]]`` for :meth:`datetime.date.strptime`, +:meth:`datetime.datetime.strptime` and :meth:`datetime.time.strptime` methods. +Patch by Semyon Moroz. From f117b20e6f14da068ce6ed4975ab6ab08891ef61 Mon Sep 17 00:00:00 2001 From: donBarbos Date: Fri, 21 Feb 2025 16:43:58 +0400 Subject: [PATCH 4/7] Add test cases to test_strptime.py --- Lib/test/test_strptime.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 0d30a63ab0c140..d472df5fd22f04 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -383,6 +383,12 @@ def test_offset(self): one_hour = 60 * 60 half_hour = 30 * 60 half_minute = 30 + (*_, offset), _, offset_fraction = _strptime._strptime("+09", "%z") + self.assertEqual(offset, 9 * one_hour) + self.assertEqual(offset_fraction, 0) + (*_, offset), _, offset_fraction = _strptime._strptime("-01", "%z") + self.assertEqual(offset, -one_hour) + self.assertEqual(offset_fraction, 0) (*_, offset), _, offset_fraction = _strptime._strptime("+0130", "%z") self.assertEqual(offset, one_hour + half_hour) self.assertEqual(offset_fraction, 0) From 9428b8c498a262b1abdeafc601bde08efcb7cab2 Mon Sep 17 00:00:00 2001 From: donBarbos Date: Sun, 29 Jun 2025 17:01:14 +0400 Subject: [PATCH 5/7] solve conflicts --- Lib/_strptime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index d40c413c34b5ed..2da5c42500f82e 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -302,7 +302,7 @@ def __init__(self, locale_time=None): # W is set below by using 'U' 'y': r"(?P\d\d)", 'Y': r"(?P\d\d\d\d)", - 'z': r"(?P[+-]\d\d(:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)?|(?-i:Z))", + 'z': r"(?P([+-]\d\d(:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)?)|(?-i: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'), From 4135515bd3063ff1bd8a9015c96fa6dcba4ec40f Mon Sep 17 00:00:00 2001 From: donBarbos Date: Tue, 1 Jul 2025 17:40:52 +0400 Subject: [PATCH 6/7] Revert "changing `%:z` docs" --- 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 1b2c69615a80e2..9da5f01ba29217 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2609,7 +2609,7 @@ convenience. These parameters all correspond to ISO 8601 date values. | | Jan 4. | | | +-----------+--------------------------------+------------------------+-------+ | ``%:z`` | UTC offset in the form | (empty), +00:00, | \(6) | -| | ``±HH[:MM[:SS[.ffffff]]]`` | -04:00, +10:30, | | +| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | | | (empty string if the object is | +06:34:15, | | | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ From 19577fe626ccf72228c32cc2e450d0ba57199454 Mon Sep 17 00:00:00 2001 From: donBarbos Date: Fri, 19 Sep 2025 18:53:55 +0400 Subject: [PATCH 7/7] And for `%:z` directive --- Doc/library/datetime.rst | 6 +++--- Lib/test/datetimetester.py | 4 ++++ Lib/test/test_strptime.py | 8 ++------ .../2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 12916489beadbb..7843ad2c750934 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2548,7 +2548,7 @@ requires, and these work on all platforms with a standard C implementation. +-----------+--------------------------------+------------------------+-------+ | ``%z`` | UTC offset in the form | (empty), +0000, | \(6) | | | ``±HH[MM[SS[.ffffff]]]`` | -0400, +1030, | | -| | (empty string if the object is | +063415, | | +| | (empty string if the object is | +063415, +04, | | | | naive). | -030712.345216 | | +-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | @@ -2609,8 +2609,8 @@ convenience. These parameters all correspond to ISO 8601 date values. | | Jan 4. | | | +-----------+--------------------------------+------------------------+-------+ | ``%:z`` | UTC offset in the form | (empty), +00:00, | \(6) | -| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | -| | (empty string if the object is | +06:34:15, | | +| | ``±HH[:MM[:SS[.ffffff]]]`` | -04:00, +10:30, | | +| | (empty string if the object is | +06:34:15, +04, | | | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 23c8a947db2016..284c1b9a1c976e 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2902,7 +2902,9 @@ def test_strptime(self): strptime = self.theclass.strptime self.assertEqual(strptime("+01", "%z").utcoffset(), 1 * HOUR) + self.assertEqual(strptime("+01", "%:z").utcoffset(), 1 * HOUR) self.assertEqual(strptime("-10", "%z").utcoffset(), -10 * HOUR) + self.assertEqual(strptime("-10", "%:z").utcoffset(), -10 * HOUR) self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) self.assertEqual( @@ -4066,7 +4068,9 @@ def test_strptime(self): def test_strptime_tz(self): strptime = self.theclass.strptime self.assertEqual(strptime("+01", "%z").utcoffset(), 1 * HOUR) + self.assertEqual(strptime("+01", "%:z").utcoffset(), 1 * HOUR) self.assertEqual(strptime("-10", "%z").utcoffset(), -10 * HOUR) + self.assertEqual(strptime("-10", "%:z").utcoffset(), -10 * HOUR) self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE) self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE) self.assertEqual( diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index d6e9bfe09fdf1c..f0eceee0add62f 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -394,12 +394,6 @@ def test_offset(self): one_hour = 60 * 60 half_hour = 30 * 60 half_minute = 30 - (*_, offset), _, offset_fraction = _strptime._strptime("+09", "%z") - self.assertEqual(offset, 9 * one_hour) - self.assertEqual(offset_fraction, 0) - (*_, offset), _, offset_fraction = _strptime._strptime("-01", "%z") - self.assertEqual(offset, -one_hour) - self.assertEqual(offset_fraction, 0) (*_, offset), _, offset_fraction = _strptime._strptime("+0130", "%z") self.assertEqual(offset, one_hour + half_hour) self.assertEqual(offset_fraction, 0) @@ -414,6 +408,8 @@ def test_offset(self): self.assertEqual(offset_fraction, -1) cases = [ + ("-01", -one_hour, 0), + ("+09", 9 * one_hour, 0), ("+01:00", one_hour, 0), ("-01:30", -(one_hour + half_hour), 0), ("-01:30:30", -(one_hour + half_hour + half_minute), 0), diff --git a/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst index b6b74d746db4d8..8016216949e6ff 100644 --- a/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst +++ b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst @@ -1,4 +1,4 @@ Update input time zone format from ``±HHMM[SS[.ffffff]]`` to ``±HH[MM[SS[.ffffff]]]`` for :meth:`datetime.date.strptime`, -:meth:`datetime.datetime.strptime` and :meth:`datetime.time.strptime` methods. -Patch by Semyon Moroz. +:meth:`datetime.datetime.strptime`, :meth:`datetime.time.strptime` and +:func:`time.strptime`. Patch by Semyon Moroz.