From f3285f343bac43538dd3cb66265be0e7c16e3220 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 23 Mar 2025 16:56:58 +0000 Subject: [PATCH 1/2] Initial --- Doc/library/datetime.rst | 10 ++++++++++ Doc/library/time.rst | 15 +++++++++++++++ Lib/_strptime.py | 5 +++++ Lib/test/datetimetester.py | 19 +++++++++++++++++++ Lib/test/test_strptime.py | 10 ++++++++++ ...-03-23-16-17-00.gh-issue-100929.awbdyu.rst | 2 ++ 6 files changed, 61 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-23-16-17-00.gh-issue-100929.awbdyu.rst diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 1af7d6be750102..9bc1f014e1817c 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1155,6 +1155,9 @@ Other constructors, all class methods: >>> when.strftime("%B %d") # doctest: +SKIP 'February 29' + .. versionchanged:: + Added the ``%F`` directive. + Class attributes: @@ -2593,6 +2596,9 @@ convenience. These parameters all correspond to ISO 8601 date values. | | (empty string if the object is | +06:34:15, | | | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ +| ``%F`` | Optional microseconds as a | (empty), .000003, | \(11) | +| | decimal number. | .123456, .3 | | ++-----------+--------------------------------+------------------------+-------+ These may not be available on all platforms when used with the :meth:`~.datetime.strftime` method. The ISO 8601 year and ISO 8601 week directives are not interchangeable @@ -2765,6 +2771,10 @@ Notes: :exc:`DeprecationWarning`. In 3.15 or later we may change this into an error or change the default year to a leap year. See :gh:`70647`. +(11) + The ``%F`` directive can only be used with :meth:`~.datetime.strptime` + and :meth:`~.time.strptime`. + .. rubric:: Footnotes .. [#] If, that is, we ignore the effects of Relativity diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 542493a82af94d..bae8b47d5d217d 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -614,12 +614,27 @@ Functions except for recognizing UTC and GMT which are always known (and are considered to be non-daylight savings timezones). + The ``%F`` directive is exclusive to :func:`!time.strptime` and allows for + optional microseconds as a decimal.:: + + >>> import time + >>> time.strptime("09:10:13", "%H:%M:%S%F") + time.struct_time(tm_year=1900, tm_mon=1, tm_mday=1, tm_hour=9, tm_min=10, + tm_sec=13, tm_wday=0, tm_yday=1, tm_isdst=-1) + >>> time.strptime("09:10:13.22", "%H:%M:%S%F") + time.struct_time(tm_year=1900, tm_mon=1, tm_mday=1, tm_hour=9, tm_min=10, + tm_sec=13, tm_wday=0, tm_yday=1, tm_isdst=-1) + + Only the directives specified in the documentation are supported. Because ``strftime()`` is implemented per platform it can sometimes offer more directives than those listed. But ``strptime()`` is independent of any platform and thus does not necessarily support all directives available that are not documented as supported. + .. versionchanged:: + Added the ``%F`` directive. + .. class:: struct_time diff --git a/Lib/_strptime.py b/Lib/_strptime.py index e6e23596db6f99..d5916d74673251 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -288,6 +288,7 @@ def __init__(self, locale_time=None): # The " [1-9]" part of the regex is to make %c from ANSI C work 'd': r"(?P3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 'f': r"(?P[0-9]{1,6})", + 'F': r"(?:\.(?P[0-9]{1,6}))?", 'H': r"(?P2[0-3]|[0-1]\d|\d)", 'I': r"(?P1[0-2]|0[1-9]|[1-9]| [1-9])", 'G': r"(?P\d\d\d\d)", @@ -522,6 +523,10 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # Pad to always return microseconds. s += "0" * (6 - len(s)) fraction = int(s) + elif group_key == "F": + s = found_dict["F"] or "0" + s += "0" * (6 - len(s)) + fraction = int(s) elif group_key == 'A': weekday = locale_time.f_weekday.index(found_dict['A'].lower()) elif group_key == 'a': diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index f9d20ef9c626a9..783817060d4b23 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2890,6 +2890,16 @@ def test_strptime(self): strptime("-00:02:01.000003", "%z").utcoffset(), -timedelta(minutes=2, seconds=1, microseconds=3) ) + + # Test %F + inputs = [ + (self.theclass(2025, 3, 23, 13, 2, 47, 197000), "2025-03-23 13:02:47.197", "%Y-%m-%d %H:%M:%S%F"), + (self.theclass(2025, 3, 23, 13, 2, 47), "2025-03-23 13:02:47", "%Y-%m-%d %H:%M:%S%F"), + ] + for expected, string, format in inputs: + with self.subTest(expected=expected, string=string, format=format): + self.assertEqual(expected, self.theclass.strptime(string, format)) + # Only local timezone and UTC are supported for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'), (-_time.timezone, _time.tzname[0])): @@ -3858,6 +3868,15 @@ def test_strftime(self): # A naive object replaces %z, %:z and %Z with empty strings. self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''") + # Test %F + inputs = [ + (self.theclass(13, 2, 47, 197000), "13:02:47.197", "%H:%M:%S%F"), + (self.theclass(13, 2, 47), "13:02:47", "%H:%M:%S%F"), + ] + for expected, string, format in inputs: + with self.subTest(expected=expected, string=string, format=format): + self.assertEqual(expected, self.theclass.strptime(string, format)) + # bpo-34482: Check that surrogates don't cause a crash. try: t.strftime('%H\ud800%M') diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index fbc43829e22a96..845a3f039560c8 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -364,6 +364,16 @@ def test_fraction(self): tup, frac, _ = _strptime._strptime(str(d), format="%Y-%m-%d %H:%M:%S.%f") self.assertEqual(frac, d.microsecond) + def test_optional_fraction(self): + # Test optional 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") + self.assertEqual(frac, d.microsecond) + dn = datetime.datetime(2012, 12, 20, 12, 34, 56) + tup, frac, _ = _strptime._strptime(str(dn), format="%Y-%m-%d %H:%M:%S%F") + self.assertEqual(frac, dn.microsecond) + def test_weekday(self): # Test weekday directives self.roundtrip('%w', 6) diff --git a/Misc/NEWS.d/next/Library/2025-03-23-16-17-00.gh-issue-100929.awbdyu.rst b/Misc/NEWS.d/next/Library/2025-03-23-16-17-00.gh-issue-100929.awbdyu.rst new file mode 100644 index 00000000000000..233e83230a76a3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-23-16-17-00.gh-issue-100929.awbdyu.rst @@ -0,0 +1,2 @@ +Add ``%F`` format code to :func:`time.strptime` and :func:`datetime.datetime.strptime` +which allows for optional microseconds. From 094b42c072b7548e673a7a28e85e73646296c22b Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 23 Mar 2025 17:48:39 +0000 Subject: [PATCH 2/2] Fix docs --- Doc/library/time.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index bae8b47d5d217d..f685ae24de46ec 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -633,7 +633,7 @@ Functions documented as supported. .. versionchanged:: - Added the ``%F`` directive. + Added the ``%F`` directive. .. class:: struct_time