From d87b20a3cb277f549f0ba96f12df57183cf8de9d Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Fri, 1 Mar 2024 07:58:14 +0000 Subject: [PATCH 1/5] gh-70647: Better promote how to safely parse yearless dates in datetime. Every four years people encounter this because it just isn't obvious. This moves the footnote up to a note with a code example. We'd love to change the default year value for datetime but doing that could have other consequences for existing code. This documented workaround *always* works. --- Doc/library/datetime.rst | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 4602132f37f733..14b9ea70ff5f10 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2525,7 +2525,24 @@ Broadly speaking, ``d.strftime(fmt)`` acts like the :mod:`time` module's For the :meth:`.datetime.strptime` class method, the default value is ``1900-01-01T00:00:00.000``: any components not specified in the format string -will be pulled from the default value. [#]_ +will be pulled from the default value. + +.. note:: + When used to parse partial dates lacking a year, :meth:`~.datetime.strptime` + will raise when encountering February 29 because its default year of 1900 is + *not* a leap year. Always add a default leap year to partial date strings + before parsing:: + + .. doctest:: + + >>> from datetime import datetime + >>> value = "2/29" + >>> datetime.strptime(value, "%m/%d") + Traceback (most recent call last): + ... + ValueError: day is out of range for month + >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") + datetime.datetime(1904, 2, 29, 0, 0) Using ``datetime.strptime(date_string, format)`` is equivalent to:: @@ -2651,6 +2668,11 @@ Notes: for formats ``%d``, ``%m``, ``%H``, ``%I``, ``%M``, ``%S``, ``%j``, ``%U``, ``%W``, and ``%V``. Format ``%y`` does require a leading zero. +(10) + Parsing dates without a year using :meth:`~.datetime.strptime` will fail on + representations of February 29 as that date does not exist in the default + year of 1900. + .. rubric:: Footnotes .. [#] If, that is, we ignore the effects of Relativity @@ -2664,5 +2686,3 @@ Notes: .. [#] See R. H. van Gent's `guide to the mathematics of the ISO 8601 calendar `_ for a good explanation. - -.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since ``1900`` is not a leap year. From 11f01a313a2d2f0ff09546dae87ffb2a5323fc28 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Fri, 1 Mar 2024 08:25:25 +0000 Subject: [PATCH 2/5] doctest code within note is bad, dedent. --- Doc/library/datetime.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 14b9ea70ff5f10..81850f3e288187 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2531,18 +2531,18 @@ will be pulled from the default value. When used to parse partial dates lacking a year, :meth:`~.datetime.strptime` will raise when encountering February 29 because its default year of 1900 is *not* a leap year. Always add a default leap year to partial date strings - before parsing:: + before parsing. - .. doctest:: +.. doctest:: - >>> from datetime import datetime - >>> value = "2/29" - >>> datetime.strptime(value, "%m/%d") - Traceback (most recent call last): - ... - ValueError: day is out of range for month - >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") - datetime.datetime(1904, 2, 29, 0, 0) + >>> from datetime import datetime + >>> value = "2/29" + >>> datetime.strptime(value, "%m/%d") + Traceback (most recent call last): + ... + ValueError: day is out of range for month + >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") + datetime.datetime(1904, 2, 29, 0, 0) Using ``datetime.strptime(date_string, format)`` is equivalent to:: From 4860a009cd824f90fcbd613d0c4663e37aab8ab8 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Fri, 21 Mar 2025 03:59:46 +0000 Subject: [PATCH 3/5] Update to match the error message. --- 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 513f32d0710d6e..efd25a1d195b41 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2635,7 +2635,7 @@ will be pulled from the default value. >>> datetime.strptime(value, "%m/%d") Traceback (most recent call last): ... - ValueError: day is out of range for month + ValueError: day 29 must be in range 1..28 for month 2 in year 1900 >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") datetime.datetime(1904, 2, 29, 0, 0) From 9c0d4f84ba56b9a89df1a7643e41b083e7530f3b Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Fri, 21 Mar 2025 04:09:38 +0000 Subject: [PATCH 4/5] remove no longer referenced footnote --- Doc/library/datetime.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index efd25a1d195b41..7eb4d4336f72c4 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2795,5 +2795,3 @@ Notes: .. [#] See R. H. van Gent's `guide to the mathematics of the ISO 8601 calendar `_ for a good explanation. - -.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since 1900 is not a leap year. From 8c6fd9b0b5b51146d04c0a0cdbe978fcbaa10fc4 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Fri, 21 Mar 2025 04:39:40 +0000 Subject: [PATCH 5/5] ignore the warning in the doctest --- Doc/library/datetime.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 7eb4d4336f72c4..b8721e17311871 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2631,8 +2631,12 @@ will be pulled from the default value. .. doctest:: >>> from datetime import datetime + >>> import warnings >>> value = "2/29" - >>> datetime.strptime(value, "%m/%d") + >>> with warnings.catch_warnings(): + ... warnings.simplefilter("ignore") + ... datetime.strptime(value, "%m/%d") + ... Traceback (most recent call last): ... ValueError: day 29 must be in range 1..28 for month 2 in year 1900