Repository navigation
Closed
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
| BPO | 23517 |
|---|---|
| Nosy | @tim-one, @mdickinson, @abalkin, @vstinner, @larryhastings, @bitdancer, @serhiy-storchaka |
| Files |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = 'https://github.com/abalkin'
closed_at = <Date 2015-09-18.13:11:31.154>
created_at = <Date 2015-02-24.22:38:42.616>
labels = ['type-bug', 'library']
title = 'datetime.utcfromtimestamp rounds results incorrectly'
updated_at = <Date 2015-09-18.13:11:31.153>
user = 'https://bugs.python.org/tbarbugli'bugs.python.org fields:
activity = <Date 2015-09-18.13:11:31.153>
actor = 'vstinner'
assignee = 'belopolsky'
closed = True
closed_date = <Date 2015-09-18.13:11:31.154>
closer = 'vstinner'
components = ['Library (Lib)']
creation = <Date 2015-02-24.22:38:42.616>
creator = 'tbarbugli'
dependencies = []
files = ['40414']
hgrepos = []
issue_num = 23517
keywords = ['patch', '3.3regression']
message_count = 100.0
messages = ['236552', '236553', '236577', '236580', '236581', '236585', '236587', '236589', '236597', '236607', '236608', '236609', '236610', '246049', '246073', '246097', '246104', '246121', '246284', '246306', '246307', '246334', '248942', '249282', '249300', '249301', '249303', '249304', '249307', '249308', '249309', '249519', '249520', '249521', '249525', '249528', '249530', '249532', '249539', '249569', '249611', '249728', '249744', '249771', '249777', '249778', '249779', '249786', '249787', '249788', '249791', '249796', '249798', '249825', '249827', '249834', '249835', '249836', '249841', '249842', '249844', '249845', '249847', '249848', '249849', '249850', '249851', '249853', '249856', '249875', '249879', '249885', '249886', '249898', '249909', '249910', '249925', '249926', '250043', '250047', '250048', '250049', '250066', '250075', '250083', '250101', '250103', '250104', '250107', '250108', '250113', '250259', '250265', '250268', '250269', '250270', '250304', '250405', '250973', '250974']
nosy_count = 13.0
nosy_names = ['tim.peters', 'mark.dickinson', 'belopolsky', 'vstinner', 'larry', 'r.david.murray', 'aconrad', 'BreamoreBoy', 'vivanov', 'python-dev', 'serhiy.storchaka', 'tbarbugli', 'trcarden']
pr_nums = []
priority = 'normal'
resolution = 'fixed'
stage = 'commit review'
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue23517'
versions = ['Python 3.4', 'Python 3.5', 'Python 3.6']Metadata
Metadata
Assignees
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
tbarbugli commentedon Feb 24, 2015
Hi,
I am porting a library from python 2.7 to 3.4 and I noticed that the behaviour of datetime.utcfromtimestamp is not consistent between the two versions.
For example on python 2.7.5
datetime.utcfromtimestamp(1424817268.274)
returns a datetime with 274000 microseconds
the same code in python 3.4 returns a datetime with 273999 microseconds.
ethanfurman commentedon Feb 24, 2015
This seems to have changed in 3.3 (versions up to 3.2 return 274000).
bitdancer commentedon Feb 25, 2015
Most likely this was a rounding fix (ie: not a bug), but hopefully Alexander will know for sure.
abalkin commentedon Feb 25, 2015
Let me dig up the history, but this does not look like correct rounding to me:
abalkin commentedon Feb 25, 2015
It looks like it was an intentional change. See bpo-14180 (changeset 75590:1e9cc1a03365).
I am not sure what the motivation was. Note that this change made utcfromtimestamp(t) different from datetime(1970,1,1) + timedelta(seconds=t).
abalkin commentedon Feb 25, 2015
Victor's motivation for the change was (msg154811):
"""
I chose this rounding method because it is the method used by int(float) and int(time.time()) is a common in programs (more than round(time.time()). Rounding towards zero avoids also producing timestamps in the future.
"""
I recall the earlier discussions of rounding in the datetime module and Mark's explanation that rounding up is fine as long as ordering is preserved. i.e. for x < y round(x) <= round(y).
There are cases when producing times in the future are problematic, for example UNIX make really dislikes when file timestamps are in the future, but if this was the main motivation - rounding towards -infinity would be more appropriate.
In any case, as long as we have the following in the datetime module documentation, I think this behavior is a bug:
"""
On the POSIX compliant platforms, utcfromtimestamp(timestamp) is equivalent to the following expression:
datetime(1970, 1, 1) + timedelta(seconds=timestamp)
"""
vstinner commentedon Feb 25, 2015
I started a large change set to support nanoseconds in the C "pytime" API: see the issue bpo-22117. While working on this change, I noticed that the rounding mode of datetime is currently wrong. Extract of a private patch:
typedef enum {
/* Round towards zero. */
_PyTime_ROUND_DOWN=0,
/* Round away from zero.
For example, used for timeout to wait "at least" N seconds. */
_PyTime_ROUND_UP=1,
/* Round towards minus infinity (-inf).
For example, used for the system clock with UNIX epoch (time_t). */
_PyTime_ROUND_FLOOR=2
} _PyTime_round_t;
I changed Modules/_datetimemodule.c to use _PyTime_ROUND_FLOOR, instead of _PyTime_ROUND_DOWN.
abalkin commentedon Feb 25, 2015
What do you mean by "currently"? What versions of python have it wrong?
abalkin commentedon Feb 25, 2015
Victor,
Would you consider going back to round to nearest? Mark and I put in a lot of effort to get the rounding in the datetime module right. (See for example, bpo-8860.)
Sub-microsecond timesources are still rare and users who work with such should avoid FP timestamps in any case. On the other hand, double precision timestamps are adequate for microsecond resolution now and in the next few decades.
Timestamps like OP's (sec=1424817268, us=274000) should not change when converted to double and back. IMO, the following behavior is a bug.
vstinner commentedon Feb 25, 2015
I don't understand "nearest". I prefer to use names of decimal rounding modes:
https://docs.python.org/dev/library/decimal.html#rounding-modes
In my local patch, I'm using ROUND_FLOOR in _decimal: "Round towards -Infinity."
I'm unable right now to say which rounding mode should be used in the decimal module. But it's important to use the same rounding mode for all similar operations. For example, time.time() and datetime.datetime.now() should have the same rounding method (bad example, time.time() returns a float, which doesn't round the result).
For example, in my local patch, I'm using ROUND_FLOOR for:
Note: the Python implementation of datetime uses time.localtime() and time.gmtime() for fromtimestamp(), so these functions should also have the same rounding method.
vstinner commentedon Feb 25, 2015
I search for "ROUND" in Modules/_datetimemodule.c: in the Python development branch (default), I found _PyTime_ROUND_DOWN (Round towards zero). Since a bug was reported, I understand that it's not the good rounding method?
abalkin commentedon Feb 25, 2015
Sorry for using loose terms. I was hoping the in the context of "going back", it would be clear.
I believe the correct mode is "ROUND_HALF_EVEN". This is the mode used by the builtin round() function:
abalkin commentedon Feb 25, 2015
These should use ROUND_HALF_EVEN
These should not involve floating point arithmetics, but when converting from nanoseconds to microseconds, you should round to nearest 1000 ns with 500 ns ties resolved to even number of microseconds.
This takes nanoseconds as an optional argument. Passing floats in times should probably be deprecated. In any case, here you would be rounding floats to nanoseconds and what you do with 0.5 nanoseconds is less important because in most cases they are not even representable as floats.
Is this a new method? I don't see it in 3.5.0a1.
This should be fixed
>>> time.gmtime(1.999999999).tm_sec 1is really bad and
is probably even worse.
Same story as in time.gmtime.
80 remaining items
vstinner commentedon Sep 7, 2015
Hum, please don't revert this change. I spent a lot of days to write pytime.c/.h.
My patches add more unit tests to datetime, to test the exact rounding mode.
The rounding mode of microseconds only impact a very few people. The rounding mode changed in 3.3, again in 3.4 and again in 3.5. This issue is the first bug report. So I think it's fine to modify 3.4.x and 3.5.x.
abalkin commentedon Sep 7, 2015
Victor> please don't revert this change.
I did not suggest reverting the entire commit. The change that affects fromdatetime() is just
+ us = int(frac * 1e6)
in datetime.py. It is probably more involved in _datetimemodule.c, but cannot be that bad. You can still keep pytime.c/.h.
mdickinson commentedon Sep 7, 2015
FWIW, count me as +1 on roundTiesToEven, mostly just for consistency. It's easier to remember that pretty much everything in Python 3 does round-ties-to-even (round function, string formatting, float literal evaluations, int-to-float conversion, Fraction-to-float conversion, ...) than to have to remember that there's a handful of corner cases where we do roundTiesToAway instead.
python-dev commentedon Sep 8, 2015
New changeset d0bb896f9b14 by Victor Stinner in branch 'default':
Revert change 0eb8c182131e:
https://hg.python.org/cpython/rev/d0bb896f9b14
python-dev commentedon Sep 8, 2015
New changeset 171d5590ebc3 by Victor Stinner in branch 'default':
Issue bpo-23517: fromtimestamp() and utcfromtimestamp() methods of
https://hg.python.org/cpython/rev/171d5590ebc3
larryhastings commentedon Sep 9, 2015
I wish people wouldn't remove old patches. There's no harm in leaving them, and it may be a useful historical record.
vstinner commentedon Sep 9, 2015
I modified Python 3.6 to use ROUND_HALF_EVEN rounding mode in datetime.timedelta, datetime.datetime.fromtimestamp(), datetime.datetime.utcfromtimestamp().
round_half_even_py34.patch: Backport this change to Python 3.4. I tried to write a minimal patch only changing datetime, not pytime.
vstinner commentedon Sep 9, 2015
It became hard to read this issue, it has a long history. My old patches for Python 3.4 were for ROUND_HALF_UP, but it was then decided to use ROUND_HALF_EVEN. Technically, patches are not "removed" but "unlinked" from the issue: you can get from the history at the bottom of this page.
larryhastings commentedon Sep 9, 2015
Given Victor's reluctance to get this in to 3.5.0, this can't even be marked as a "deferred blocker" anymore. Demoting to normal priority.
vstinner commentedon Sep 10, 2015
Alexander: can you please review attached round_half_even_py34.patch? It's the minimum patch to fix datetime/timedelta rounding for Python 3.4 (and 3.5).
python-dev commentedon Sep 18, 2015
New changeset ee1cf1b188d2 by Victor Stinner in branch '3.4':
Issue bpo-23517: Fix rounding in fromtimestamp() and utcfromtimestamp() methods
https://hg.python.org/cpython/rev/ee1cf1b188d2
vstinner commentedon Sep 18, 2015
Ok, I fixed Python 3.4 and 3.5 too: fromtimestamp() and utcfromtimestamp() now uses also ROUND_HALF_EVEN rounding mode, as timedelta constructor. I explained in Misc/NEWS that the change was made to respect the property:
(datetime(1970,1,1) + timedelta(seconds=t)) == datetime.utcfromtimestamp(t)
Thanks Alexander Belopolsky & Tim Peters for your explanation on rounding. It's not a simple problem :-)
Thanks Tommaso Barbugli for the bug report: it's now fixed on all (maintained) Python 3 versions: 3.4, 3.5 and 3.6. The fix will be part of Python 3.5.1.