From 6da69b65a65d578992cd781dc3effe020b767cb7 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 00:04:09 -0700 Subject: [PATCH 01/11] %D support for strptime, including test and Doc update --- Doc/library/datetime.rst | 6 +++--- Lib/_strptime.py | 1 + Lib/test/datetimetester.py | 7 +++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index d861139cec6db5..b47329ecc59701 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2534,8 +2534,8 @@ requires, and these work on all supported platforms. | ``%d`` | Day of the month as a | 01, 02, ..., 31 | \(9) | | | zero-padded decimal number. | | | +-----------+--------------------------------+------------------------+-------+ -| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/10/2025 | \(9), | -| | | | \(0) | +| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/10/2025 | \(9) | +| | | | | +-----------+--------------------------------+------------------------+-------+ | ``%e`` | The day of the month as a | ␣1, ␣2, ..., 31 | | | | space-padded decimal number. | | | @@ -2676,7 +2676,7 @@ differences between platforms in handling of unsupported format specifiers. ``%:z`` was added for :meth:`~.datetime.strftime`. .. versionadded:: 3.15 - ``%:z`` and ``%F`` were added for :meth:`~.datetime.strptime`. + ``%:z``, ``%F``, and ``%D`` were added for :meth:`~.datetime.strptime`. Technical Detail ^^^^^^^^^^^^^^^^ diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 8b62ea734b7d11..df4a3a3c2f771f 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -425,6 +425,7 @@ def __init__(self, locale_time=None): base.__setitem__('X', self.pattern(self.locale_time.LC_time)) base.__setitem__('x', self.pattern(self.locale_time.LC_date)) base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) + base.__setitem__('D', self.pattern('%m/%d/%Y')) def __seqToRE(self, to_convert, directive, altregex=None): """Convert a list to a regex string for matching a directive. diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 3784909ee77839..173e4e1e4b50a4 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2200,6 +2200,13 @@ def test_strptime_F_format(self): self.theclass.strptime(test_date, "%Y-%m-%d") ) + def test_strptime_D_format(self): + test_date = "11/28/2025" + self.assertEqual( + self.theclass.strptime(test_date, "%D"), + self.theclass.strptime(test_date, "%m/%d/%Y") + ) + ############################################################################# # datetime tests From 402d2d9e41b45f74af63a274c11f8b7c557c9031 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 00:13:17 -0700 Subject: [PATCH 02/11] additional %D test --- Lib/test/test_strptime.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 0784ea6a4cf5d4..a37e40f4db95e3 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -663,6 +663,13 @@ def test_strptime_T_format(self): time.strptime(test_time, "%H:%M:%S") ) + def test_strptime_D_format(self): + test_date = "10/26/2025" + self.assertEqual( + time.strptime(test_date, "%D"), + time.strptime(test_date, "%m/%d/%Y") + ) + class Strptime12AMPMTests(unittest.TestCase): """Test a _strptime regression in '%I %p' at 12 noon (12 PM)""" From 5c1d957006f0da86f4cf93ef757d5183d4ae3e56 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 07:38:10 -0700 Subject: [PATCH 03/11] change documentation example date for %D so it is more legible to non-US readers --- 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 b47329ecc59701..bf24ff7c5e0140 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2534,7 +2534,7 @@ requires, and these work on all supported platforms. | ``%d`` | Day of the month as a | 01, 02, ..., 31 | \(9) | | | zero-padded decimal number. | | | +-----------+--------------------------------+------------------------+-------+ -| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/10/2025 | \(9) | +| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/28/2025 | \(9) | | | | | | +-----------+--------------------------------+------------------------+-------+ | ``%e`` | The day of the month as a | ␣1, ␣2, ..., 31 | | From 49c39f539715d2332b9f631218b7df62a988036c Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 07:39:18 -0700 Subject: [PATCH 04/11] change testing date for %D so it is more legible to non-US readers --- Lib/test/test_strptime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index a37e40f4db95e3..e7c40aa0069f1d 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -664,7 +664,7 @@ def test_strptime_T_format(self): ) def test_strptime_D_format(self): - test_date = "10/26/2025" + test_date = "11/28/2025" self.assertEqual( time.strptime(test_date, "%D"), time.strptime(test_date, "%m/%d/%Y") From 1fd6495081b06392e84b6b9bb90e14bcabb03230 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:56:46 +0000 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst new file mode 100644 index 00000000000000..f7782f2fa4f23b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst @@ -0,0 +1 @@ +Add ``'%D'`` support to :meth:`~datetime.datetime.strptime`. From c7f30c9e644c78ea55c31f810053c007d025fdd8 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 07:59:37 -0700 Subject: [PATCH 06/11] mv News blurb to Library, consistent with previous %F change --- .../2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core_and_Builtins => Library}/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst (100%) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst b/Misc/NEWS.d/next/Library/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst similarity index 100% rename from Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst rename to Misc/NEWS.d/next/Library/2026-02-14-14-56-44.gh-issue-140715.AbSheM.rst From 2c31abac8f2f33bfce5d6c295a58d8bc86f6bbd7 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 15:14:43 -0700 Subject: [PATCH 07/11] change invalid format code from %D to C-standard unused %! --- Lib/test/test_strptime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index e7c40aa0069f1d..ef3d921aceae24 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -286,7 +286,7 @@ def test_ValueError(self): def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: - _strptime._strptime_time('', '%D') + _strptime._strptime_time('', '%!') self.assertTrue(e.exception.__suppress_context__) # additional check for stray % branch with self.assertRaises(ValueError) as e: From f965f0c309612097a3dd83dfbcdf4fddbb14d8f8 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 15:20:09 -0700 Subject: [PATCH 08/11] change invalid format code from %D to C-standard unused %! --- Lib/test/test_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index da0cf494bfa8ad..1e9b718dd21aff 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -379,7 +379,7 @@ def test_strptime_bytes(self): def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: - time.strptime('', '%D') + time.strptime('', '%!') self.assertTrue(e.exception.__suppress_context__) # additional check for stray % branch with self.assertRaises(ValueError) as e: From b3f67ca886d7f0feee580c427ace83d46a2a8771 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 15:20:20 -0700 Subject: [PATCH 09/11] change invalid format code from %D to C-standard unused %! --- Lib/test/test_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 1e9b718dd21aff..da5fd16b8b6291 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -358,7 +358,7 @@ def test_strptime(self): # Should be able to go round-trip from strftime to strptime without # raising an exception. tt = time.gmtime(self.t) - for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'F', 'H', 'I', + for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'D', 'F', 'H', 'I', 'j', 'm', 'M', 'p', 'S', 'T', 'U', 'w', 'W', 'x', 'X', 'y', 'Y', 'Z', '%'): format = '%' + directive From dca9e511dd43423088835b33fd5cea4f3fcc7690 Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 15:32:37 -0700 Subject: [PATCH 10/11] Fix erroneous and misleading example Doc to %y from %Y, use correct C99+ definition for C99 %D; update additional tests --- Doc/library/datetime.rst | 2 +- Lib/_strptime.py | 2 +- Lib/test/datetimetester.py | 4 ++-- Lib/test/test_strptime.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index bf24ff7c5e0140..39a7a1530a95cc 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2534,7 +2534,7 @@ requires, and these work on all supported platforms. | ``%d`` | Day of the month as a | 01, 02, ..., 31 | \(9) | | | zero-padded decimal number. | | | +-----------+--------------------------------+------------------------+-------+ -| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/28/2025 | \(9) | +| ``%D`` | Equivalent to ``%m/%d/%y``. | 11/28/25 | \(9) | | | | | | +-----------+--------------------------------+------------------------+-------+ | ``%e`` | The day of the month as a | ␣1, ␣2, ..., 31 | | diff --git a/Lib/_strptime.py b/Lib/_strptime.py index df4a3a3c2f771f..f94f59716e6ab8 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -425,7 +425,7 @@ def __init__(self, locale_time=None): base.__setitem__('X', self.pattern(self.locale_time.LC_time)) base.__setitem__('x', self.pattern(self.locale_time.LC_date)) base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) - base.__setitem__('D', self.pattern('%m/%d/%Y')) + base.__setitem__('D', self.pattern('%m/%d/%y')) def __seqToRE(self, to_convert, directive, altregex=None): """Convert a list to a regex string for matching a directive. diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 173e4e1e4b50a4..97eec618932aa5 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2201,10 +2201,10 @@ def test_strptime_F_format(self): ) def test_strptime_D_format(self): - test_date = "11/28/2025" + test_date = "11/28/25" self.assertEqual( self.theclass.strptime(test_date, "%D"), - self.theclass.strptime(test_date, "%m/%d/%Y") + self.theclass.strptime(test_date, "%m/%d/%y") ) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index ef3d921aceae24..fd8525feb88d53 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -664,10 +664,10 @@ def test_strptime_T_format(self): ) def test_strptime_D_format(self): - test_date = "11/28/2025" + test_date = "11/28/25" self.assertEqual( time.strptime(test_date, "%D"), - time.strptime(test_date, "%m/%d/%Y") + time.strptime(test_date, "%m/%d/%y") ) class Strptime12AMPMTests(unittest.TestCase): From 888e7b0b070ea143119851adc8472d9883acab7b Mon Sep 17 00:00:00 2001 From: Jason Yalim Date: Sat, 14 Feb 2026 15:33:19 -0700 Subject: [PATCH 11/11] reorg %D token to before %F, as requested --- Lib/_strptime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index f94f59716e6ab8..fe34808d88769a 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -418,6 +418,7 @@ def __init__(self, locale_time=None): mapping['W'] = mapping['U'].replace('U', 'W') base.__init__(mapping) + base.__setitem__('D', self.pattern('%m/%d/%y')) base.__setitem__('F', self.pattern('%Y-%m-%d')) base.__setitem__('T', self.pattern('%H:%M:%S')) base.__setitem__('R', self.pattern('%H:%M')) @@ -425,7 +426,6 @@ def __init__(self, locale_time=None): base.__setitem__('X', self.pattern(self.locale_time.LC_time)) base.__setitem__('x', self.pattern(self.locale_time.LC_date)) base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) - base.__setitem__('D', self.pattern('%m/%d/%y')) def __seqToRE(self, to_convert, directive, altregex=None): """Convert a list to a regex string for matching a directive.