I am trying to do some Python date and timedelta maths and stumbled upon this.
>>> import datetime
>>> dt = datetime.date(2000, 4, 20)
>>> td = datetime.timedelta(days=1)
>>> dt - td
datetime.date(2000, 4, 19)
>>> -(td) + dt
datetime.date(2000, 4, 19)
>>> dt - td == dt + (-td)
True
So far so good, but when the timedelta also includes some hours it gets interesting.
>>> td = datetime.timedelta(days=1, hours=1)
>>> dt - td
datetime.date(2000, 4, 19)
>>> -(td) + dt
datetime.date(2000, 4, 18)
or in a comparison:
>>> dt - td == dt + (-td)
False
I would have expected that a - b == a + (-b), but this doesn't seem to work for date and timedelta. As far as I was able to track that down, this happens because adding/subtracting date and timedelta only considers the days field of timedelta, which is probably correct. However negating a timedelta considers all fields and may change the days field as well.
>>> -datetime.timedelta(days=1)
datetime.timedelta(-1)
>>> -datetime.timedelta(days=1, hours=1)
datetime.timedelta(-2, 82800)
As can be seen in the second example, days=-2 after the negation, and therefore date + timedelta will actually subtract 2 days.
Should this be considered a bug in the python datetime module? Or is this rather some 'normal' behaviour which needs to be taken into account when doing things like that?
Internally the datetime module creates a new timedelta, with just the days field of the original timedelta object passed in, when subtracting a timedelta to a date object. Which equates to following, code that seems to be quite odd.
>>> dt + datetime.timedelta(-(-(-dt).days))
datetime.date(2000, 4, 18)
I can't really sea a reason for just using the negated days field when doing date - timedelta subtractions.
Edit:
Here is the relevant code path in python datetime module:
class date:
...
def __sub__(self, other):
"""Subtract two dates, or a date and a timedelta."""
if isinstance(other, timedelta):
return self + timedelta(-other.days)
...
If it would just pass on -other then the condition a - b == a + (-b) would hold true. (It would change current behaviour though).
class date:
...
def __sub__(self, other):
"""Subtract two dates, or a date and a timedelta."""
if isinstance(other, timedelta):
return self - other # timedelta.__rsub__ would take care of negating other
...