Valentin B.
Valentin B.

Reputation: 622

Slight deviation between datetime.datetime calculations in Python 2 and 3

While porting some code from Python 2 to Python 3, the tests highlighted a numerical regression when computing dates with datetime.datetime that I'm having a hard time to explain.

How to reproduce

date_max = datetime.datetime(2016, 9, 28, 4, 21, 5, 228000)
date_min = datetime.datetime(2016, 9, 28, 4, 21, 4, 460315)
date_futur = date_min + datetime.timedelta(seconds=((date_max - date_min).total_seconds() / 2))

Outputs

Output from print date_futur in Python 2.7.12:

2016-09-28 04:21:04.844158

Output from print(date_futur) in Python 3.5.2:

2016-09-28 04:21:04.844157

Issue

This is just one microsecond difference, but it bugs me because I cannot explain it, so I do not know if I can update my test results with the new Python 3 behavior or if something more complicated is at hand.

Possible lead

Maybe it is due to how Python 3 rounds 0.5 to the closest even number, and not up like Python 2 ?


Update

Result of (date_max - date_min).total_seconds() / 2 in both cases is 0.8441575 seconds. However, once handed to datetime.timedelta constructor:

Python 2:

datetime.timedelta(0, 0, 383843)

Python 3:

datetime.timedelta(0, 0, 383842)

So something wonky is happening in the timedelta constructor !

Upvotes: 0

Views: 75

Answers (2)

Valentin B.
Valentin B.

Reputation: 622

From datetime official Python 3 documentation:

If any argument is a float and there are fractional microseconds, the fractional microseconds left over from all arguments are combined and their sum is rounded to the nearest microsecond using round-half-to-even tiebreaker. If no argument is a float, the conversion and normalization processes are exact (no information is lost).

Replacing with numerical values, we are doing:

datetime.timedelta(seconds=0.3838425)

The float value 0.3838425 is then converted to microseconds, which yields 383842.5 microseconds. It is then rounded to 383843 in Python 2 (.5 always rounded up) and to 383842 in Python 3 (rounded to closest even number). What was misleading was the fact that this value is then added to another date with an odd number of microseconds (460315), flipping the parity of the final result !

Upvotes: 0

DeepSpace
DeepSpace

Reputation: 81594

The cause for this difference is the different division behavior of Python 2 and 3. Python 2 uses integer division while Python 3 uses float division.

3 / 2 outputs 1 in Python 2 and 1.5 in Python 3.

So the culprit is this part of your code: (date_max - date_min).total_seconds() / 2

Using from __future__ import division will cause Python 2 to use float division. Changing 2 to 2.0 will also make Python use float division.

Upvotes: 2

Related Questions