Surprising behaviour of Python date and timedelta subtraction

Viewed 1388

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
        ...
2 Answers
Related