diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index f27844c98ccff6..50a2821530ddab 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2659,9 +2659,11 @@ requires, and these work on all supported platforms. | | number. | 2014, ..., 9998, 9999 | | +-----------+--------------------------------+------------------------+-------+ | ``%z`` | UTC offset in the form | (empty), +0000, | \(6) | -| | ``±HHMM[SS[.ffffff]]`` (empty | -0400, +1030, | | -| | string if the object is | +063415, | | -| | naive). | -030712.345216 | | +| | ``±HHMM[SS[.ffffff]]``. | -0400, +1030, | | +| | For :meth:`!strptime`, | +063415, +04, | | +| | ``±HH[MM[SS[.ffffff]]]`` | -030712.345216 | | +| | (empty string if the object is | | | +| | naive). | | | +-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | | | if the object is naive). | | | @@ -2684,9 +2686,11 @@ convenience. | | digits. | | | +-----------+--------------------------------+------------------------+-------+ | ``%: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, | | -| | naive). | -03:07:12.345216 | | +| | ``±HH:MM[:SS[.ffffff]]``. | -04:00, +10:30, | | +| | For :meth:`!strptime`, | +06:34:15, +04, | | +| | ``±HH[:MM[:SS[.ffffff]]]`` | -03:07:12.345216 | | +| | (empty string if the object is | | | +| | naive). | | | +-----------+--------------------------------+------------------------+-------+ The full set of format codes supported varies across platforms, because Python @@ -2822,7 +2826,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 @@ -2871,6 +2875,10 @@ Notes: aware :class:`.datetime` object will be produced. The ``tzinfo`` of the result will be set to a :class:`timezone` instance. + .. versionchanged:: next + The ``%z`` and ``%:z`` directives in :meth:`~.datetime.strptime` + now accept time zone offsets in ``±HH`` format (for example, ``+03``). + (7) When used with the :meth:`~.datetime.strptime` method, ``%U`` and ``%W`` are only used in calculations when the day of the week and the calendar year (``%Y``) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 0d81ff6765e1ed..fbea97ff31046d 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -372,8 +372,8 @@ def __init__(self, locale_time=None): 'y': r"(?P\d\d)", 'Y': r"(?P\d\d\d\d)", # See gh-121237: "z" must support colons for backwards compatibility. - '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))?", + '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(_fixmonths(self.locale_time.f_month[1:]), 'B'), @@ -682,7 +682,7 @@ def parse_int(s): 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] != ':': @@ -690,7 +690,7 @@ def parse_int(s): 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/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 97eec618932aa5..8b0502917c2976 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3016,6 +3016,10 @@ 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( @@ -3056,6 +3060,8 @@ def test_strptime(self): with self.assertRaises(ValueError): strptime("-2400", "%z") with self.assertRaises(ValueError): strptime("-000", "%z") with self.assertRaises(ValueError): strptime("z", "%z") + with self.assertRaises(ValueError): strptime("+3", "%z") + with self.assertRaises(ValueError): strptime("-7", "%z") def test_strptime_ampm(self): dt = datetime(1999, 3, 17, 0, 44, 55, 2) @@ -4185,6 +4191,10 @@ 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 fd8525feb88d53..3e4ced27954398 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -408,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 new file mode 100644 index 00000000000000..8016216949e6ff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-21-07-34-37.gh-issue-128074.qeCRrp.rst @@ -0,0 +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`, :meth:`datetime.time.strptime` and +:func:`time.strptime`. Patch by Semyon Moroz.