timedelta.days Is -1 for Any Negative Sub-Day Duration (tested)
timedelta normalizes its internal fields so .days, .seconds and .microseconds are always consistent with the total duration. We measured what that normalization does to .days for a duration that's negative but less than a full day — it's not 0.
1. A one-hour-negative timedelta
from datetime import timedelta
d = timedelta(hours=-1)
print(repr(d))
print("days:", d.days)
print("seconds:", d.seconds)
print("total_seconds():", d.total_seconds())
datetime.timedelta(days=-1, seconds=82800)
days: -1
seconds: 82800
total_seconds(): -3600.0
Measured: .days is -1 and .seconds is 82800 (23 hours) — not 0 and -3600 as the "minus one hour" framing might suggest. Per the docs, timedelta always normalizes so 0 <= seconds < 86400; the only place the negative sign can live is .days, so it absorbs it even for a duration that's mostly zero. total_seconds() gives the intuitive -3600.0.
2. Where this breaks an age check
def is_more_than_a_week_old(delta):
return delta.days >= 7
future_event = timedelta(hours=-1) # a timestamp 1 hour in the FUTURE
print(is_more_than_a_week_old(future_event))
False
Measured: that particular check happens to come out False here, but only because -1 < 7. The real danger is the reverse pattern — code written as if delta.days < 0: handle_future_timestamp() actually fires for any negative duration, which is correct, but code that instead checks delta.days == 0 to mean "within the last day" misses every negative sub-day duration, since .days is -1 for all of them, not 0.
d2 = timedelta(hours=-1)
print("is this 'within a day'?", d2.days == 0) # intended to catch same-day deltas
is this 'within a day'? False
Measured: a timestamp just one hour in the future is excluded from a "same day" check written against .days == 0, with no exception — it silently falls through to whatever branch handles "more than a day away," even though a one-hour difference is about as "same day" as it gets.
3. Fix: compare total_seconds() or the timedelta itself, not .days
print("within a day (total_seconds):", abs(d2.total_seconds()) < 86400)
print("within a day (compare timedelta):", abs(d2) < timedelta(days=1))
within a day (total_seconds): True
within a day (compare timedelta): True
Measured: both alternatives give the intuitive answer. total_seconds() returns a single signed float with no normalization surprises, and comparing the timedelta object directly (optionally via abs()) avoids touching .days/.seconds individually at all. Reach for .days only when you specifically want "how many whole, normalized days are baked into this duration," not as a stand-in for "roughly how big is this duration."
Frequently asked questions
Why is timedelta(hours=-1).days equal to -1 instead of 0?
Per the datetime docs, timedelta always normalizes so seconds and microseconds are non-negative, which means .days absorbs the entire sign of a negative duration -- we measured .days as -1 and .seconds as 82800 for a one-hour-negative delta.
Does this mean timedelta.days is unreliable for all negative durations?
It's reliable for what it's documented to mean (whole normalized days), but we confirmed it's not a usable proxy for 'is this duration roughly zero or negative' -- any negative duration under 24 hours still reports -1.
How do I check if a timedelta represents 'within the last day' correctly?
We confirmed comparing total_seconds() (abs(d.total_seconds()) < 86400) or the timedelta object itself (abs(d) < timedelta(days=1)) both give the correct answer, unlike checking d.days == 0.
Does this affect timedelta subtraction results from real datetime objects?
Yes -- any datetime subtraction that produces a small negative timedelta (e.g. a timestamp slightly in the future) hits the same .days == -1 result; we tested the timedelta constructor directly, but the normalization rule is the same regardless of how the timedelta was produced.
Is total_seconds() always safe to use instead of .days and .seconds?
For comparisons and magnitude checks, yes -- we confirmed it returns a single signed float reflecting the true duration. Use .days/.seconds only when you need the normalized component breakdown itself.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in datetime.timedelta: constructed timedelta(hours=-1) and inspected .days, .seconds and total_seconds(), then ran a common age-check pattern (if delta.days >= N) against it. PlainHeaders is our extension; Python's datetime module is built in.