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

  1. Add a year to both the format and the input, e.g. use '%Y-%m-%d' instead of '%m-%d'
  2. If the year is genuinely absent, prepend a known year to the data string before parsing: datetime.strptime(f'2000-{s}', '%Y-%m-%d')
  3. Replace %d with %e is NOT a fix (it only downgrades to a deprecation warning until 3.17) — add a year instead
  4. 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

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


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/c373579f390a7a54. Report an issue: GitHub.