Python strptime 不带年份解析 02/29 报日期超出范围
Python datetime.strptime 省略年份时使用默认年 1900,无法构造 2 月 29 日。完整日期应补真实年份,只有月日的字段可用闰年占位后取出月日,并保留无效日期校验。
解析 02/28、03/01 都正常,唯独 02/29 失败,即使程序运行的当年是闰年:
from datetime import datetime
value = datetime.strptime("02/29", "%m/%d")
# 可能看到 ValueError: day is out of range for month
在 Python 3.12–3.14 的 datetime.strptime() 中,格式没有给出年份时,年份会从默认值 1900 取得。1900 不是闰年,因此无法构造 1900 年 2 月 29 日。函数不会自动用程序运行当年的年份;修改系统日期也不能解决这个输入缺项。
Python 3.13 起,指定月份中的日期而未指定年份还会触发 DeprecationWarning,是否显示取决于警告配置。这里的处理方式是补全输入语义,而不是屏蔽警告;更高版本对省略年份的约束需要按对应版本检查。
先决定字段表示完整日期还是只有月日
订单时间、日志事件通常需要真实年份。生日的月日、每年重复的纪念日则可能只存月和日。这两种数据不能共用“随便补一个年份后直接保存”的处理方式。
检查出错的字符串和格式:如果使用 %m/%d、%m-%d 等没有 %Y 的格式,而业务实际需要完整日期,应从原始记录或明确的批次上下文取得年份。输入缺年份时,不能仅因当前年份恰好能解析就认定日期属于今年。
使用英文月份的 %b 还受语言环境影响。下面采用数字月日,把缺年份问题与月份语言不匹配区分开;修改格式时仍应匹配真实输入,而不是只替换格式字符串。
有真实年份时,把它一起传入解析
假设输入批次已经明确属于 2024 年,月日字段是 02/29,可以同时补年份文本和 %Y:
from datetime import datetime
def parse_in_year(month_day, year):
return datetime.strptime(
f"{month_day};{year:04d}",
"%m/%d;%Y",
).date()
result = parse_in_year("02/29", 2024)
print(result.isoformat())
# 预期 2024-02-29
这里 year 应是业务提供并校验过的整数年份,2024 只是示例。分号把补充年份与原月日明确分开,避免直接连成一串数字产生格式歧义。补了字符串也必须补格式,两者需一致。
将同一月日和 2023 组合仍应报错,因为 2023 年不存在 2 月 29 日。不能为让输入通过而把日期自动改成 2 月 28 日或 3 月 1 日;如果业务确实需要这样的归并规则,应另行定义,而不是藏在解析函数里。
只需要月日时,用闰年占位后取出月和日
对于不包含真实年份的字段,可用固定闰年完成日历有效性检查,然后只保留所需的两个字段:
from datetime import datetime
def parse_month_day(text):
parsed = datetime.strptime(f"{text};2000", "%m/%d;%Y")
return parsed.month, parsed.day
month, day = parse_month_day("02/29")
print(month, day)
# 预期 2 29
2000 年可以容纳 2 月 29 日。这个年份只是解析占位,不是出生年、事件年或保存时间。不要把 parsed 的完整日期直接写进要求真实年月日的数据库列,也不要拿这个占位日期计算年龄或事件间隔。
若后续要把这组月日安排到某个具体年份,还需要再验证目标年份。例如“每年生日提醒”遇到非闰年的 2 月 29 日,应由提醒规则决定是否以及何时触发;解析成功本身没有替你做这个决定。
先解析再 replace 年份解决不了原错误
下面这种顺序会在调用 replace() 前就失败:
parsed = datetime.strptime("02/29", "%m/%d")
parsed = parsed.replace(year=2024)
年份必须进入首次解析,或直接用于构造完整日期。先得到一个无效的 1900 年日期再修改年份,没有可供修改的对象。
也不要只把 datetime.strptime() 换成 time.strptime() 作为通用修复。某些版本和输入条件下,后者可以返回包含 1900 年 2 月 29 日的结构,这并不证明它是可构造的有效完整日期;后续转换成 date 或 datetime 时仍会受到日期有效性约束。选择接口前先确定需要的是完整日期还是月日字段。
验证修复没有把无效日期一并放过
对只存月日的解析函数,应覆盖 02/28、02/29、03/01,并确认 02/30、04/31 和超出范围的月份仍被拒绝。对完整日期函数,还要同时覆盖闰年和非闰年的 02/29,核对返回的年份确实来自业务上下文。
保留原始输入和修改前的解析代码,再替换调用入口。若以前已有数据被补成当前年或占位年,恢复代码不会还原这些记录;应依据原始日期来源核对,缺少真实年份的记录继续保留待确认状态,不批量猜测年份。临时回退时也应显式报告这类输入无法解析,避免捕获 ValueError 后直接丢掉整条记录。


