dateutil relativedelta.days is residual not total elapsed days
dateutil relativedelta(d1, d2).days is the residual after month extraction, not elapsed days. dateutil.relativedelta.relativedelta(end, start).days does NOT return total elapsed days like (end - start).days on a timedelta. relativedelta normalizes the difference into years/months/days components, so .days is only the remainder after whole months are extracted. Concrete: relativedelta(date(2026, 2, 1), date(2026, 1, 1)).days == 0 (it's 1 month, 0 days), and Jan 1 -> Feb 15 gives .days == 14, not 45. Found as a latent bug in a financial simulator's Period.days property: any date-difference code that switches from timedelta subtraction to relativedelta (usually to gain month arithmetic) silently changes the meaning of .days. Rate-proration or interest math built on it under-counts by whole months. Easy to miss because it's correct for periods under one month, which is what most unit tests use.
Use plain date subtraction for elapsed days: (end - start).days — datetime.date.__sub__ returns a timedelta whose .days is the true total. Reserve relativedelta for calendar-aware month/year arithmetic (date + relativedelta(months=1)), never for measuring elapsed time.
If you need both in one API (e.g. a Period class exposing .days and .month_diff), compute them from different primitives:
@property
def days(self):
return (self.end_date - self.start_date).days # timedelta, total days
@property
def month_diff(self):
return (self.end_date.year - self.start_date.year) * 12 + self.end_date.month - self.start_date.monthAudit tip: grep for relativedelta( used with two date arguments followed by .days access — every such site is suspect.