Python relativedelta 为什么会把日期区间算成 1 个月?
结论
relativedelta 没有算错。它会把两个日期的差分解成年、月、日,而不是换算成总天数。你的代码在分解前执行了 d2 += timedelta(days=1),把 2025-12-22 调整为 2025-12-23。从 2025-11-23 到 2025-12-23 正好是一个日历月,所以结果是 1 month。
你列出的预期用了两种规则:2025-11-23 至 2025-12-22 按首尾都包含计算,结果是 30 天;而 2025-11-23 至 2025-12-31 预期为 1 month and 8 days,这是不把结束日期加一天后进行日历分解的结果。需要先确定“月和天”采用哪种表示约定,再统一处理边界。
为什么会出现这些结果
原代码的关键逻辑是:
if include_end:
d2 = d2 + timedelta(days=1)
r = relativedelta(d2, d1)
这几个日期分别会得到以下结果:
2025-11-23到2025-12-21相差 28 天,首尾都计入时为 29 天。调整后的结束日期是2025-12-22,还没到一个月的日历周年,因此结果是29 days。2025-11-23到2025-12-22相差 29 天,首尾都计入时为 30 天。但代码把结束日期加一天,改为2025-12-23。因此relativedelta(2025-12-23, 2025-11-23)正好是1 month。2025-11-23到2025-12-31不调整时是1 month and 8 days。加一天后,结束日期变为2026-01-01,与2025-11-23相差1 month and 9 days。
2025-12-22 的结果变化,是因为结束日期加一天后刚好跨过了月周年。2025-12-31 多出的那一天也来自同一处调整。
按需要选择计算方式
只需要总天数,并且首尾日期都计入
要计算首尾都包含的总天数,直接用 timedelta,不要用 relativedelta 表示结果:
from datetime import date, timedelta
def inclusive_days(d1: date, d2: date) -> int:
if d2 < d1:
d1, d2 = d2, d1
return (d2 - d1).days + 1
print(inclusive_days(date(2025, 11, 23), date(2025, 12, 22)))
# 30
需要按日历单位显示年、月、日
如果用 relativedelta 按日历单位分解,就不要为了“包含结束日”而先把 d2 加一天:
from dateutil.relativedelta import relativedelta
r = relativedelta(date(2025, 12, 31), date(2025, 11, 23))
print(r.months, r.days)
# 1 8
结果是提问中期望的 1 month and 8 days。但按这条规则,2025-11-23 至 2025-12-22 会得到 29 days,而不是首尾都计入的 30 days。
如果产品需要“未满一个完整日历月时,按首尾包含的总天数显示;达到完整日历月后,再显示日历月和剩余天数”,可以先判断是否已经满一个日历月,再分别计算:
from datetime import date
from dateutil.relativedelta import relativedelta
def inclusive_duration(d1: date, d2: date) -> tuple[int, int, int]:
if d2 < d1:
d1, d2 = d2, d1
r = relativedelta(d2, d1)
if r.years == 0 and r.months == 0:
return 0, 0, (d2 - d1).days + 1
return r.years, r.months, r.days
按这段代码,2025-11-23 至 2025-12-22 会显示为 30 天,2025-11-23 至 2025-12-31 会显示为 1 个月零 8 天。这种展示约定需要明确:出现完整日历月后,剩余天数按未经调整的结束日期计算,不再额外加一天。
还需检查的代码细节
get_my_tuple 的同月分支直接计算 abs(d2.day - d1.day),但此时日期可能已经按 include_start、include_end 调整过。结果是否符合预期,取决于这两个开关是否表示“把区间端点计入”,以及同月日期的处理方式。建议先统一端点规则,再让同月和跨月路径遵循同一口径,避免结果不一致。
另外,relativedelta 的月分量表示日历月,不是固定天数。每个月的天数不同,因此不能把“1 month”当作固定天数,也无法将它唯一换算成总天数。
备注:内容仅供参考。
