Python strptime 省略年份:三月能解析,为什么二月二十九日突然报错

前天 3阅读

一个每年重复使用的展览目录只保存“月/日”。大多数值都能用 %m/%d 解析,只有 02/29 报出日期越界。输入未必有错:datetime.strptime 会为没有提供的部分使用默认值,其中年份是 1900,而这一年没有二月二十九日。

解析一个没有年份的月日,与把它安排到某一年,是两个问题。下面先验证省略年份的行为,再用明确的闰年作为解析支架,最后检查两个目标年份。保存为 demo.py,运行 python demo.py,只使用标准库与固定字符串。

Python strptime 省略年份:三月能解析,为什么二月二十九日突然报错

AI生成概念插图:月日卡需要先接上明确的年份卡,闰日才能进入完整日期格;不是日历软件截图。

from datetime import date, datetime

plain = datetime.strptime("03/01", "%m/%d")
assert plain.year == 1900
print("default year:", plain.year)

try:
    datetime.strptime("02/29", "%m/%d")
except ValueError:
    print("missing-year leap day rejected")
else:
    raise AssertionError("unexpected leap day")

def parse_month_day(text):
    parsed = datetime.strptime("2000/" + text, "%Y/%m/%d")
    return parsed.month, parsed.day

month_day = parse_month_day("02/29")
assert month_day == (2, 29)
print("month/day:", month_day)

def place_in_year(year, month_day):
    month, day = month_day
    return date(year, month, day)

leap_date = place_in_year(2028, month_day)
assert leap_date.isoformat() == "2028-02-29"
print("in 2028:", leap_date.isoformat())
try:
    place_in_year(2027, month_day)
except ValueError:
    print("in 2027: no such date")
else:
    raise AssertionError("non-leap date accepted")

try:
    parse_month_day("04/31")
except ValueError:
    print("invalid month/day rejected")
else:
    raise AssertionError("April 31 accepted")

缺失的年份已经参加了校验

程序先打印 default year: 1900。虽然输入只有月日,返回的对象仍有完整年月日,不会保留“年份未知”的标记。下一步 02/29 失败,是因为解析过程构造的完整日期不成立。若把三月的结果直接写入数据库,1900 也会作为真实数值流入后面的排序与筛选。

parse_month_day 主动补上 2000,完成合法月日检查后只返回 (2, 29)。这个元组明确表达月日信息;2000 只是解析所用的闰年,不是展览发生年份。选闰年的目的是允许所有合法月日出现,同时仍拒绝四月三十一日这样的错误组合。

安排到哪一年要另做决定

place_in_year 用 date 构造目标年份的日期。2028 可以得到 2028-02-29,2027 则抛出 ValueError。解析成功不能保证每个目标年份都存在相应日期。每年重复的活动遇到这种情况,业务可以规定跳过、提前或延后,但解析函数不应悄悄替使用者选定其中一种。

如果资料本来提供了完整日期,最好直接要求年份并按完整格式解析。只有字段确实表达循环月日时,才需要保留月日结构。不要为了修复报错而固定补上当前年份,因为当前年份会变化,某个今年有效的输入明年可能再次失败,测试也随运行日期改变。

格式解析还应与文本格式约束分开。这里的示例关心日期合法性,并没有保证输入必须恰好两位月份、斜杠和两位日期;strptime 对某些数字字段允许省略前导零。如果接口必须接收固定宽度,可以在日期解析之前增加独立的文本检查,并给出不同错误提示。

本例固定在 Python 3.12.14 下运行。Python 3.13 对“格式含月内日期但不含年份”的调用新增 DeprecationWarning;其文档还提示后续版本可能改为报错或调整默认年份,所以开头的反例不宜原样当作跨版本断言。实际使用应采用后半段显式补闰年的方案,并测试普通日、闰日和不存在的月日。

资料核对日期:2026年10月2日。代码在本地 Python 3.12.14 实际运行并通过断言;结果只对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。