cpython: 3c29d05c0710 (original) (raw)
Mercurial > cpython
changeset 97679:3c29d05c0710
Issue #23517: Fix implementation of the ROUND_HALF_UP rounding mode in datetime.datetime.fromtimestamp() and datetime.datetime.utcfromtimestamp(). microseconds sign should be kept before rounding. [#23517]
Victor Stinner victor.stinner@gmail.com | |
---|---|
date | Fri, 04 Sep 2015 23:57:25 +0200 |
parents | d4089ff1e656 |
children | d755f75019c8 |
files | Lib/datetime.py Lib/test/datetimetester.py Lib/test/test_time.py Python/pytime.c |
diffstat | 4 files changed, 43 insertions(+), 40 deletions(-)[+] [-] Lib/datetime.py 50 Lib/test/datetimetester.py 12 Lib/test/test_time.py 13 Python/pytime.c 8 |
line wrap: on
line diff
--- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1374,6 +1374,26 @@ class datetime(date): return self._tzinfo @classmethod
- def _fromtimestamp(cls, t, utc, tz):
"""Construct a datetime from a POSIX timestamp (like time.time()).[](#l1.8)
A timezone info object may be passed in as well.[](#l1.10)
"""[](#l1.11)
frac, t = _math.modf(t)[](#l1.12)
us = _round_half_up(frac * 1e6)[](#l1.13)
if us >= 1000000:[](#l1.14)
t += 1[](#l1.15)
us -= 1000000[](#l1.16)
elif us < 0:[](#l1.17)
t -= 1[](#l1.18)
us += 1000000[](#l1.19)
converter = _time.gmtime if utc else _time.localtime[](#l1.21)
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)[](#l1.22)
ss = min(ss, 59) # clamp out leap seconds if the platform has them[](#l1.23)
return cls(y, m, d, hh, mm, ss, us, tz)[](#l1.24)
- @classmethod def fromtimestamp(cls, t, tz=None): """Construct a datetime from a POSIX timestamp (like time.time()).
@@ -1381,21 +1401,7 @@ class datetime(date): """ _check_tzinfo_arg(tz)
converter = _time.localtime if tz is None else _time.gmtime[](#l1.34)
t, frac = divmod(t, 1.0)[](#l1.36)
us = _round_half_up(frac * 1e6)[](#l1.37)
# If timestamp is less than one microsecond smaller than a[](#l1.39)
# full second, us can be rounded up to 1000000. In this case,[](#l1.40)
# roll over to seconds, otherwise, ValueError is raised[](#l1.41)
# by the constructor.[](#l1.42)
if us == 1000000:[](#l1.43)
t += 1[](#l1.44)
us = 0[](#l1.45)
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)[](#l1.46)
ss = min(ss, 59) # clamp out leap seconds if the platform has them[](#l1.47)
result = cls(y, m, d, hh, mm, ss, us, tz)[](#l1.48)
result = cls._fromtimestamp(t, tz is not None, tz)[](#l1.49) if tz is not None:[](#l1.50) result = tz.fromutc(result)[](#l1.51) return result[](#l1.52)
@@ -1403,19 +1409,7 @@ class datetime(date): @classmethod def utcfromtimestamp(cls, t): """Construct a naive UTC datetime from a POSIX timestamp."""
t, frac = divmod(t, 1.0)[](#l1.57)
us = _round_half_up(frac * 1e6)[](#l1.58)
# If timestamp is less than one microsecond smaller than a[](#l1.60)
# full second, us can be rounded up to 1000000. In this case,[](#l1.61)
# roll over to seconds, otherwise, ValueError is raised[](#l1.62)
# by the constructor.[](#l1.63)
if us == 1000000:[](#l1.64)
t += 1[](#l1.65)
us = 0[](#l1.66)
y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t)[](#l1.67)
ss = min(ss, 59) # clamp out leap seconds if the platform has them[](#l1.68)
return cls(y, m, d, hh, mm, ss, us)[](#l1.69)
return cls._fromtimestamp(t, True, None)[](#l1.70)
@classmethod def now(cls, tz=None):
--- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -668,6 +668,8 @@ class TestTimeDelta(HarmlessMixedCompari eq(td(milliseconds=-0.6/1000), td(microseconds=-1)) eq(td(seconds=0.5/106), td(microseconds=1)) eq(td(seconds=-0.5/106), td(microseconds=-1))
eq(td(seconds=1/2**7), td(microseconds=7813))[](#l2.7)
eq(td(seconds=-1/2**7), td(microseconds=-7813))[](#l2.8)
# Rounding due to contributions from more than one field. us_per_hour = 3600e6 @@ -1842,8 +1844,8 @@ class TestDateTime(TestDate): 18000 + 3600 + 260 + 3 + 41e-6) def test_microsecond_rounding(self):
for fts in [self.theclass.fromtimestamp,[](#l2.16)
self.theclass.utcfromtimestamp]:[](#l2.17)
for fts in (datetime.fromtimestamp,[](#l2.18)
self.theclass.utcfromtimestamp):[](#l2.19) zero = fts(0)[](#l2.20) self.assertEqual(zero.second, 0)[](#l2.21) self.assertEqual(zero.microsecond, 0)[](#l2.22)
@@ -1874,6 +1876,12 @@ class TestDateTime(TestDate): t = fts(0.9999999) self.assertEqual(t.second, 1) self.assertEqual(t.microsecond, 0)
t = fts(1/2**7)[](#l2.27)
self.assertEqual(t.second, 0)[](#l2.28)
self.assertEqual(t.microsecond, 7813)[](#l2.29)
t = fts(-1/2**7)[](#l2.30)
self.assertEqual(t.second, 59)[](#l2.31)
self.assertEqual(t.microsecond, 992187)[](#l2.32)
def test_insane_fromtimestamp(self): # It's possible that some platform maps time_t to double,
--- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -655,7 +655,7 @@ class TestPytime(unittest.TestCase): pytime_object_to_time_t, invalid, rnd) @support.cpython_only
# Conversion giving the same result for all rounding methods @@ -666,7 +666,7 @@ class TestPytime(unittest.TestCase): (-1, (-1, 0)), # float
(-1.2, (-2, 800000000)),[](#l3.16)
(-1/2**7, (-1, 992187500)),[](#l3.17) (-1.0, (-1, 0)),[](#l3.18) (-1e-9, (-1, 999999999)),[](#l3.19) (1e-9, (0, 1)),[](#l3.20)
@@ -693,7 +693,7 @@ class TestPytime(unittest.TestCase): (1.1234567890, (1, 123456789), FLOOR), (1.1234567899, (1, 123456789), FLOOR),
(-1.1234567890, (-2, 876543211), FLOOR),[](#l3.25)
(-1.1234567890, (-2, 876543210), FLOOR),[](#l3.26) (-1.1234567891, (-2, 876543210), FLOOR),[](#l3.27) # Round towards infinity (+inf)[](#l3.28) (1.1234567890, (1, 123456790), CEILING),[](#l3.29)
@@ -1155,7 +1155,7 @@ class TestOldPyTime(unittest.TestCase): self.assertRaises(OverflowError, pytime_object_to_time_t, invalid, rnd)
# Conversion giving the same result for all rounding methods @@ -1167,7 +1167,8 @@ class TestOldPyTime(unittest.TestCase): # float (-1.0, (-1, 0)),
(-1.2, (-2, 800000)),[](#l3.43)
(1/2**6, (0, 15625)),[](#l3.44)
(-1/2**6, (-1, 984375)),[](#l3.45) (-1e-6, (-1, 999999)),[](#l3.46) (1e-6, (0, 1)),[](#l3.47) ):[](#l3.48)
@@ -1225,7 +1226,7 @@ class TestOldPyTime(unittest.TestCase): (-1.0, (-1, 0)), (-1e-9, (-1, 999999999)), (1e-9, (0, 1)),
(-1.2, (-2, 800000000)),[](#l3.53)
(-1/2**9, (-1, 998046875)),[](#l3.54) ):[](#l3.55) with self.subTest(obj=obj, round=rnd, timespec=timespec):[](#l3.56) self.assertEqual(pytime_object_to_timespec(obj, rnd),[](#l3.57)
--- a/Python/pytime.c +++ b/Python/pytime.c @@ -82,10 +82,6 @@ static int volatile double floatpart; floatpart = modf(d, &intpart);
floatpart *= denominator; if (round == _PyTime_ROUND_HALF_UP) @@ -98,6 +94,10 @@ static int floatpart -= denominator; intpart += 1.0; }