python/cpython · error · ValueError
Day of month directive '%d' may not be used without a year d
Error message
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.
What it means
datetime.strptime()/time.strptime() raises this ValueError when the format string contains the day-of-month directive %d but no year directive (%Y or %y). Parsing a day without a year is ambiguous (the parser cannot know the month length or handle Feb 29), so since the fix for gh-70647 it is a hard error in _strptime's format preprocessing.
Source
Thrown at Lib/_strptime.py:485
day_d_in_format = False
day_e_in_format = False
def repl(m):
directive = m.group()[1:] # exclude `%` symbol
match directive:
case 'Y' | 'y' | 'G':
nonlocal year_in_format
year_in_format = True
case 'd':
nonlocal day_d_in_format
day_d_in_format = True
case 'e':
nonlocal day_e_in_format
day_e_in_format = True
return self[directive]
format = re_sub(r'%[-_0^#]*[0-9]*([OE]?[:\\]?.?)', repl, format)
if not year_in_format:
if day_d_in_format:
raise 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.")
if day_e_in_format:
import warnings
warnings.warn("""\
Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day. '%e' without a year will become an error in Python 3.17.
To avoid trouble, add a specific year to the input and format.
See https://github.com/python/cpython/issues/70647.""",
DeprecationWarning,
skip_file_prefixes=(os.path.dirname(__file__),))
return format
def compile(self, format):
"""Return a compiled re object for the format string."""View on GitHub (pinned to bc6749cc3b)
Solutions
- Add a year to both the format and the input, e.g. use '%Y-%m-%d' instead of '%m-%d'
- If the year is genuinely absent, prepend a known year to the data string before parsing: datetime.strptime(f'2000-{s}', '%Y-%m-%d')
- Replace %d with %e is NOT a fix (it only downgrades to a deprecation warning until 3.17) — add a year instead
- Use dateutil.parser which infers the year, if a third-party dependency is acceptable
Example fix
# before
>>> datetime.strptime('29 02', '%d %m')
ValueError: Day of month directive '%d' may not be used without a year directive...
# after
>>> datetime.strptime('2000-02-29', '%Y-%m-%d')
datetime.datetime(2000, 2, 29, 0, 0) Defensive patterns
Strategy: validation
Validate before calling
import re
def has_year_with_day(fmt: str) -> bool:
year = re.search(r'%[-_0^#]*[-_0^#]*[YOEyG]', fmt) # any year-ish directive
day = re.search(r'%[-_0^#]*d', fmt)
return not (day and not re.search(r'%(Y|y|G)', fmt)) Try / catch
try:
dt = datetime.strptime(s, fmt)
except ValueError as e:
if 'without a year directive' in str(e):
dt = datetime.strptime(f'2000-{s}', f'%Y-{fmt}')
else:
raise Prevention
- Always include %Y (or %G with %V) when a day-of-month appears in the format
- Add a unit test parsing '29 02' style edge inputs to catch year-less formats early
- Run test suites on the newest Python; this became an error after being silent
When it happens
Trigger: Calling _strptime (via datetime.strptime or time.strptime) with a format like '%d %b' or '%m/%d' where a %d directive is present and the regex substitution pass detects no 'Y'/'y' directive.
Common situations: Parsing log lines or user-supplied dates that omit the year; code that worked on Python < 3.13-ish where %d without %Y silently defaulted to year 1900 and mis-parsed leap days; migrating parsers after a Python upgrade.
Related errors
- Day of the year directive '%j' is not compatible with ISO ye
- ISO year directive '%G' must be used with the ISO week direc
- ISO week directive '%V' must be used with the ISO year direc
- ISO week directive '%V' is incompatible with the year direct
- stray %% in format '%s'
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/c373579f390a7a54.
Report an issue: GitHub.