Python: fix ValueError: day of month directive '%d' may not be used without a year directive

Take this code, parsing the date of an annually recurring event that only has a month and day, such as a work anniversary:
from datetime import datetime
def parse_anniversary(date_str: str) -> datetime:
parsed = datetime.strptime(date_str, "%d/%m")
return parsed.day, parsed.month
With Python 3.13 or 3.14, run it on January 1st, and it will log a warning, but still parse the date:
>>> parse_anniversary("01/01")
/.../example.py:7: DeprecationWarning: Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
See https://github.com/python/cpython/issues/70647.
parsed = datetime.strptime(date_str, "%d/%m")
(1, 1)
Note the returned value’s year is 1900.
But, parse February 29th, and the warning is followed by a ValueError:
>>> parse_anniversary("29/02")
/.../example.py:7: DeprecationWarning: Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day...
Traceback (most recent call last):
...
ValueError: day is out of range for month
That ValueError isn’t new: without a year, strptime() has always filled in 1900 behind the scenes, which isn’t a leap year, so parsing “29 February” has always failed, on every Python version. The new part is the warning, telling us this whole approach is on its way out.
On Python 3.15+, any call to strptime() with a %d in the format string, without a year, will raise a ValueError immediately, no matter the input:
>>> parse_anniversary("01/01")
Traceback (most recent call last):
...
ValueError: Day of month directive '%d' may not be used without a year directive. Parsing dates involving a day of month without a year is ambiguous and fails to parse leap day. Add a year to the input and format. See https://github.com/python/cpython/issues/70647.
From the release notes:
strptime()now raisesValueErrorwhen the format string contains%d(day of month) without a year directive. This has been deprecated since Python 3.13. (Contributed by Stan Ulbrych and Gregory P. Smith in gh-70647.)
It seems like a fair trade-off to break this usage completely, rather than let it continue to hide around in code bases and only fail in the rare case that it is handed February 29th. But it does mean any such call sites need fixing…
The fix
Add a year to both the format string and input. If you do have a specific year, use it, otherwise add a leap year (2000 works) and ignore the year in the returned value:
from datetime import datetime
def parse_anniversary(date_str: str) -> datetime:
date_str = f"{date_str}/2000" # Add leap year to input
parsed = datetime.strptime(date_str, "%d/%m/%Y")
return parsed.day, parsed.month
This will work fine on all Python versions for all dates, including February 29th:
>>> parse_anniversary("01/01")
(1, 1)
>>> parse_anniversary("29/02")
(29, 2)
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
One summary email a week, no spam, I pinky promise.
Related posts:
- Python: fix
SyntaxWarning: 'return' in a 'finally' block - Why does Python log a DeprecationWarning saying “invalid escape sequence”?
- Why does Python log a warning for “invalid decimal literal”?
Tags: python