diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2015-09-18 12:42:05 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2015-09-18 12:42:05 (GMT) |
commit | 511491ade0bb77febb176bc75f049797f0c71ed0 (patch) | |
tree | 1981db4c2c00eef86f32c03589174f98e46df857 /Lib/datetime.py | |
parent | e3bcbd2bbade81a3591d69f607e1722c6016a489 (diff) | |
download | cpython-511491ade0bb77febb176bc75f049797f0c71ed0.zip cpython-511491ade0bb77febb176bc75f049797f0c71ed0.tar.gz cpython-511491ade0bb77febb176bc75f049797f0c71ed0.tar.bz2 |
Issue #23517: Fix rounding in fromtimestamp() and utcfromtimestamp() methods
of datetime.datetime: microseconds are now rounded to nearest with ties going
to nearest even integer (ROUND_HALF_EVEN), instead of being rounding towards
zero (ROUND_DOWN). It's important that these methods use the same rounding
mode than datetime.timedelta to keep the property:
(datetime(1970,1,1) + timedelta(seconds=t)) == datetime.utcfromtimestamp(t)
It also the rounding mode used by round(float) for example.
Add more unit tests on the rounding mode in test_datetime.
Diffstat (limited to 'Lib/datetime.py')
-rw-r--r-- | Lib/datetime.py | 51 |
1 files changed, 22 insertions, 29 deletions
diff --git a/Lib/datetime.py b/Lib/datetime.py index 34e5d38..3af12e7 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1362,49 +1362,42 @@ class datetime(date): return self._tzinfo @classmethod - def fromtimestamp(cls, t, tz=None): + def _fromtimestamp(cls, t, utc, tz): """Construct a datetime from a POSIX timestamp (like time.time()). A timezone info object may be passed in as well. """ + frac, t = _math.modf(t) + us = round(frac * 1e6) + if us >= 1000000: + t += 1 + us -= 1000000 + elif us < 0: + t -= 1 + us += 1000000 - _check_tzinfo_arg(tz) + converter = _time.gmtime if utc else _time.localtime + y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) + ss = min(ss, 59) # clamp out leap seconds if the platform has them + return cls(y, m, d, hh, mm, ss, us, tz) - converter = _time.localtime if tz is None else _time.gmtime + @classmethod + def fromtimestamp(cls, t, tz=None): + """Construct a datetime from a POSIX timestamp (like time.time()). - t, frac = divmod(t, 1.0) - us = int(frac * 1e6) + A timezone info object may be passed in as well. + """ + _check_tzinfo_arg(tz) - # If timestamp is less than one microsecond smaller than a - # full second, us can be rounded up to 1000000. In this case, - # roll over to seconds, otherwise, ValueError is raised - # by the constructor. - if us == 1000000: - t += 1 - us = 0 - y, m, d, hh, mm, ss, weekday, jday, dst = converter(t) - ss = min(ss, 59) # clamp out leap seconds if the platform has them - result = cls(y, m, d, hh, mm, ss, us, tz) + result = cls._fromtimestamp(t, tz is not None, tz) if tz is not None: result = tz.fromutc(result) return result @classmethod def utcfromtimestamp(cls, t): - "Construct a UTC datetime from a POSIX timestamp (like time.time())." - t, frac = divmod(t, 1.0) - us = int(frac * 1e6) - - # If timestamp is less than one microsecond smaller than a - # full second, us can be rounded up to 1000000. In this case, - # roll over to seconds, otherwise, ValueError is raised - # by the constructor. - if us == 1000000: - t += 1 - us = 0 - y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t) - ss = min(ss, 59) # clamp out leap seconds if the platform has them - return cls(y, m, d, hh, mm, ss, us) + """Construct a naive UTC datetime from a POSIX timestamp.""" + return cls._fromtimestamp(t, True, None) # XXX This is supposed to do better than we *can* do by using time.time(), # XXX if the platform supports a more accurate way. The C implementation |