{"record":{"id":"c373579f390a7a54","repo":"python/cpython","slug":"day-of-month-directive-d-may-not-be-used-withou","errorCode":null,"errorMessage":"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.","messagePattern":"Day of month directive '(.+?)' 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\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_strptime.py","lineNumber":485,"sourceCode":"        day_d_in_format = False\n        day_e_in_format = False\n        def repl(m):\n            directive = m.group()[1:] # exclude `%` symbol\n            match directive:\n                case 'Y' | 'y' | 'G':\n                    nonlocal year_in_format\n                    year_in_format = True\n                case 'd':\n                    nonlocal day_d_in_format\n                    day_d_in_format = True\n                case 'e':\n                    nonlocal day_e_in_format\n                    day_e_in_format = True\n            return self[directive]\n        format = re_sub(r'%[-_0^#]*[0-9]*([OE]?[:\\\\]?.?)', repl, format)\n        if not year_in_format:\n            if day_d_in_format:\n                raise ValueError(\n                    \"Day of month directive '%d' may not be used without \"\n                    \"a year directive. Parsing dates involving a day of \"\n                    \"month without a year is ambiguous and fails to parse \"\n                    \"leap day. Add a year to the input and format. \"\n                    \"See https://github.com/python/cpython/issues/70647.\")\n            if day_e_in_format:\n                import warnings\n                warnings.warn(\"\"\"\\\nParsing dates involving a day of month without a year specified is ambiguous\nand fails to parse leap day. '%e' without a year will become an error in Python 3.17.\nTo avoid trouble, add a specific year to the input and format.\nSee https://github.com/python/cpython/issues/70647.\"\"\",\n                              DeprecationWarning,\n                              skip_file_prefixes=(os.path.dirname(__file__),))\n        return format\n\n    def compile(self, format):\n        \"\"\"Return a compiled re object for the format string.\"\"\"","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_strptime.py#L467-L503","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n>>> datetime.strptime('29 02', '%d %m')\nValueError: Day of month directive '%d' may not be used without a year directive...\n\n# after\n>>> datetime.strptime('2000-02-29', '%Y-%m-%d')\ndatetime.datetime(2000, 2, 29, 0, 0)","handlingStrategy":"validation","validationCode":"import re\n\ndef has_year_with_day(fmt: str) -> bool:\n    year = re.search(r'%[-_0^#]*[-_0^#]*[YOEyG]', fmt)  # any year-ish directive\n    day = re.search(r'%[-_0^#]*d', fmt)\n    return not (day and not re.search(r'%(Y|y|G)', fmt))","typeGuard":null,"tryCatchPattern":"try:\n    dt = datetime.strptime(s, fmt)\nexcept ValueError as e:\n    if 'without a year directive' in str(e):\n        dt = datetime.strptime(f'2000-{s}', f'%Y-{fmt}')\n    else:\n        raise","preventionTips":["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"],"tags":["strptime","datetime","format-string","cpython"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}