Issue #23517: fromtimestamp() and utcfromtimestamp() methods of
datetime.datetime now round microseconds to nearest with ties going to nearest even integer (ROUND_HALF_EVEN), as round(float), instead of rounding towards -Infinity (ROUND_FLOOR). pytime API: replace _PyTime_ROUND_HALF_UP with _PyTime_ROUND_HALF_EVEN. Fix also _PyTime_Divide() for negative numbers. _PyTime_AsTimeval_impl() now reuses _PyTime_Divide() instead of reimplementing rounding modes.
This commit is contained in:
parent
69cc487df4
commit
7667f58151
|
@ -31,9 +31,9 @@ typedef enum {
|
||||||
/* Round towards infinity (+inf).
|
/* Round towards infinity (+inf).
|
||||||
For example, used for timeout to wait "at least" N seconds. */
|
For example, used for timeout to wait "at least" N seconds. */
|
||||||
_PyTime_ROUND_CEILING=1,
|
_PyTime_ROUND_CEILING=1,
|
||||||
/* Round to nearest with ties going away from zero.
|
/* Round to nearest with ties going to nearest even integer.
|
||||||
For example, used to round from a Python float. */
|
For example, used to round from a Python float. */
|
||||||
_PyTime_ROUND_HALF_UP
|
_PyTime_ROUND_HALF_EVEN
|
||||||
} _PyTime_round_t;
|
} _PyTime_round_t;
|
||||||
|
|
||||||
/* Convert a time_t to a PyLong. */
|
/* Convert a time_t to a PyLong. */
|
||||||
|
@ -44,8 +44,9 @@ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
|
||||||
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
|
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
|
||||||
PyObject *obj);
|
PyObject *obj);
|
||||||
|
|
||||||
/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
|
/* Round to nearest with ties going to nearest even integer
|
||||||
PyAPI_FUNC(double) _PyTime_RoundHalfUp(
|
(_PyTime_ROUND_HALF_EVEN) */
|
||||||
|
PyAPI_FUNC(double) _PyTime_RoundHalfEven(
|
||||||
double x);
|
double x);
|
||||||
|
|
||||||
/* Convert a number of seconds, int or float, to time_t. */
|
/* Convert a number of seconds, int or float, to time_t. */
|
||||||
|
|
|
@ -1380,7 +1380,7 @@ class datetime(date):
|
||||||
A timezone info object may be passed in as well.
|
A timezone info object may be passed in as well.
|
||||||
"""
|
"""
|
||||||
frac, t = _math.modf(t)
|
frac, t = _math.modf(t)
|
||||||
us = _round_half_up(frac * 1e6)
|
us = round(frac * 1e6)
|
||||||
if us >= 1000000:
|
if us >= 1000000:
|
||||||
t += 1
|
t += 1
|
||||||
us -= 1000000
|
us -= 1000000
|
||||||
|
|
|
@ -1874,7 +1874,7 @@ class TestDateTime(TestDate):
|
||||||
self.assertEqual(t, zero)
|
self.assertEqual(t, zero)
|
||||||
t = fts(-1/2**7)
|
t = fts(-1/2**7)
|
||||||
self.assertEqual(t.second, 59)
|
self.assertEqual(t.second, 59)
|
||||||
self.assertEqual(t.microsecond, 992187)
|
self.assertEqual(t.microsecond, 992188)
|
||||||
|
|
||||||
t = fts(1e-7)
|
t = fts(1e-7)
|
||||||
self.assertEqual(t, zero)
|
self.assertEqual(t, zero)
|
||||||
|
@ -1888,7 +1888,7 @@ class TestDateTime(TestDate):
|
||||||
self.assertEqual(t.microsecond, 0)
|
self.assertEqual(t.microsecond, 0)
|
||||||
t = fts(1/2**7)
|
t = fts(1/2**7)
|
||||||
self.assertEqual(t.second, 0)
|
self.assertEqual(t.second, 0)
|
||||||
self.assertEqual(t.microsecond, 7813)
|
self.assertEqual(t.microsecond, 7812)
|
||||||
|
|
||||||
def test_insane_fromtimestamp(self):
|
def test_insane_fromtimestamp(self):
|
||||||
# It's possible that some platform maps time_t to double,
|
# It's possible that some platform maps time_t to double,
|
||||||
|
|
|
@ -30,11 +30,11 @@ class _PyTime(enum.IntEnum):
|
||||||
ROUND_FLOOR = 0
|
ROUND_FLOOR = 0
|
||||||
# Round towards infinity (+inf)
|
# Round towards infinity (+inf)
|
||||||
ROUND_CEILING = 1
|
ROUND_CEILING = 1
|
||||||
# Round to nearest with ties going away from zero
|
# Round to nearest with ties going to nearest even integer
|
||||||
ROUND_HALF_UP = 2
|
ROUND_HALF_EVEN = 2
|
||||||
|
|
||||||
ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING,
|
ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING,
|
||||||
_PyTime.ROUND_HALF_UP)
|
_PyTime.ROUND_HALF_EVEN)
|
||||||
|
|
||||||
|
|
||||||
class TimeTestCase(unittest.TestCase):
|
class TimeTestCase(unittest.TestCase):
|
||||||
|
@ -639,27 +639,26 @@ class TestPytime(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, time_t, rnd in (
|
for obj, seconds, rnd in (
|
||||||
(-1.9, -2, FLOOR),
|
(-1.9, -2, FLOOR),
|
||||||
(-1.9, -1, CEILING),
|
(-1.9, -1, CEILING),
|
||||||
(-1.9, -2, HALF_UP),
|
(-1.9, -2, HALF_EVEN),
|
||||||
|
|
||||||
(1.9, 1, FLOOR),
|
(1.9, 1, FLOOR),
|
||||||
(1.9, 2, CEILING),
|
(1.9, 2, CEILING),
|
||||||
(1.9, 2, HALF_UP),
|
(1.9, 2, HALF_EVEN),
|
||||||
|
|
||||||
# half up
|
# half even
|
||||||
(-0.999, -1, HALF_UP),
|
(-1.5, -2, HALF_EVEN),
|
||||||
(-0.510, -1, HALF_UP),
|
(-0.9, -1, HALF_EVEN),
|
||||||
(-0.500, -1, HALF_UP),
|
(-0.5, 0, HALF_EVEN),
|
||||||
(-0.490, 0, HALF_UP),
|
( 0.5, 0, HALF_EVEN),
|
||||||
( 0.490, 0, HALF_UP),
|
( 0.9, 1, HALF_EVEN),
|
||||||
( 0.500, 1, HALF_UP),
|
( 1.5, 2, HALF_EVEN),
|
||||||
( 0.510, 1, HALF_UP),
|
|
||||||
( 0.999, 1, HALF_UP),
|
|
||||||
):
|
):
|
||||||
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
|
with self.subTest(obj=obj, round=rnd, seconds=seconds):
|
||||||
|
self.assertEqual(pytime_object_to_time_t(obj, rnd), seconds)
|
||||||
|
|
||||||
# Test OverflowError
|
# Test OverflowError
|
||||||
rnd = _PyTime.ROUND_FLOOR
|
rnd = _PyTime.ROUND_FLOOR
|
||||||
|
@ -691,15 +690,15 @@ class TestPytime(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, timespec, rnd in (
|
for obj, timespec, rnd in (
|
||||||
# Round towards minus infinity (-inf)
|
# Round towards minus infinity (-inf)
|
||||||
(-1e-10, (0, 0), CEILING),
|
(-1e-10, (0, 0), CEILING),
|
||||||
(-1e-10, (-1, 999999999), FLOOR),
|
(-1e-10, (-1, 999999999), FLOOR),
|
||||||
(-1e-10, (0, 0), HALF_UP),
|
(-1e-10, (0, 0), HALF_EVEN),
|
||||||
(1e-10, (0, 0), FLOOR),
|
(1e-10, (0, 0), FLOOR),
|
||||||
(1e-10, (0, 1), CEILING),
|
(1e-10, (0, 1), CEILING),
|
||||||
(1e-10, (0, 0), HALF_UP),
|
(1e-10, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
(0.9999999999, (0, 999999999), FLOOR),
|
(0.9999999999, (0, 999999999), FLOOR),
|
||||||
(0.9999999999, (1, 0), CEILING),
|
(0.9999999999, (1, 0), CEILING),
|
||||||
|
@ -714,15 +713,13 @@ class TestPytime(unittest.TestCase):
|
||||||
(-1.1234567890, (-2, 876543211), CEILING),
|
(-1.1234567890, (-2, 876543211), CEILING),
|
||||||
(-1.1234567891, (-2, 876543211), CEILING),
|
(-1.1234567891, (-2, 876543211), CEILING),
|
||||||
|
|
||||||
# half up
|
# half even
|
||||||
(-0.6e-9, (-1, 999999999), HALF_UP),
|
(-1.5e-9, (-1, 999999998), HALF_EVEN),
|
||||||
# skipped, 0.5e-6 is inexact in base 2
|
(-0.9e-9, (-1, 999999999), HALF_EVEN),
|
||||||
#(-0.5e-9, (-1, 999999999), HALF_UP),
|
(-0.5e-9, (0, 0), HALF_EVEN),
|
||||||
(-0.4e-9, (0, 0), HALF_UP),
|
(0.5e-9, (0, 0), HALF_EVEN),
|
||||||
|
(0.9e-9, (0, 1), HALF_EVEN),
|
||||||
(0.4e-9, (0, 0), HALF_UP),
|
(1.5e-9, (0, 2), HALF_EVEN),
|
||||||
(0.5e-9, (0, 1), HALF_UP),
|
|
||||||
(0.6e-9, (0, 1), HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj, round=rnd, timespec=timespec):
|
with self.subTest(obj=obj, round=rnd, timespec=timespec):
|
||||||
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
|
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
|
||||||
|
@ -823,10 +820,10 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
(-7.0, -7 * SEC_TO_NS),
|
(-7.0, -7 * SEC_TO_NS),
|
||||||
|
|
||||||
# nanosecond are kept for value <= 2^23 seconds,
|
# nanosecond are kept for value <= 2^23 seconds,
|
||||||
# except 2**23-1e-9 with HALF_UP
|
|
||||||
(2**22 - 1e-9, 4194303999999999),
|
(2**22 - 1e-9, 4194303999999999),
|
||||||
(2**22, 4194304000000000),
|
(2**22, 4194304000000000),
|
||||||
(2**22 + 1e-9, 4194304000000001),
|
(2**22 + 1e-9, 4194304000000001),
|
||||||
|
(2**23 - 1e-9, 8388607999999999),
|
||||||
(2**23, 8388608000000000),
|
(2**23, 8388608000000000),
|
||||||
|
|
||||||
# start loosing precision for value > 2^23 seconds
|
# start loosing precision for value > 2^23 seconds
|
||||||
|
@ -859,38 +856,31 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, ts, rnd in (
|
for obj, ts, rnd in (
|
||||||
# close to zero
|
# close to zero
|
||||||
( 1e-10, 0, FLOOR),
|
( 1e-10, 0, FLOOR),
|
||||||
( 1e-10, 1, CEILING),
|
( 1e-10, 1, CEILING),
|
||||||
( 1e-10, 0, HALF_UP),
|
( 1e-10, 0, HALF_EVEN),
|
||||||
(-1e-10, -1, FLOOR),
|
(-1e-10, -1, FLOOR),
|
||||||
(-1e-10, 0, CEILING),
|
(-1e-10, 0, CEILING),
|
||||||
(-1e-10, 0, HALF_UP),
|
(-1e-10, 0, HALF_EVEN),
|
||||||
|
|
||||||
# test rounding of the last nanosecond
|
# test rounding of the last nanosecond
|
||||||
( 1.1234567899, 1123456789, FLOOR),
|
( 1.1234567899, 1123456789, FLOOR),
|
||||||
( 1.1234567899, 1123456790, CEILING),
|
( 1.1234567899, 1123456790, CEILING),
|
||||||
( 1.1234567899, 1123456790, HALF_UP),
|
( 1.1234567899, 1123456790, HALF_EVEN),
|
||||||
(-1.1234567899, -1123456790, FLOOR),
|
(-1.1234567899, -1123456790, FLOOR),
|
||||||
(-1.1234567899, -1123456789, CEILING),
|
(-1.1234567899, -1123456789, CEILING),
|
||||||
(-1.1234567899, -1123456790, HALF_UP),
|
(-1.1234567899, -1123456790, HALF_EVEN),
|
||||||
|
|
||||||
# close to 1 second
|
# close to 1 second
|
||||||
( 0.9999999999, 999999999, FLOOR),
|
( 0.9999999999, 999999999, FLOOR),
|
||||||
( 0.9999999999, 1000000000, CEILING),
|
( 0.9999999999, 1000000000, CEILING),
|
||||||
( 0.9999999999, 1000000000, HALF_UP),
|
( 0.9999999999, 1000000000, HALF_EVEN),
|
||||||
(-0.9999999999, -1000000000, FLOOR),
|
(-0.9999999999, -1000000000, FLOOR),
|
||||||
(-0.9999999999, -999999999, CEILING),
|
(-0.9999999999, -999999999, CEILING),
|
||||||
(-0.9999999999, -1000000000, HALF_UP),
|
(-0.9999999999, -1000000000, HALF_EVEN),
|
||||||
|
|
||||||
# close to 2^23 seconds
|
|
||||||
(2**23 - 1e-9, 8388607999999999, FLOOR),
|
|
||||||
(2**23 - 1e-9, 8388607999999999, CEILING),
|
|
||||||
# Issue #23517: skip HALF_UP test because the result is different
|
|
||||||
# depending on the FPU and how the compiler optimize the code :-/
|
|
||||||
#(2**23 - 1e-9, 8388608000000000, HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj, round=rnd, timestamp=ts):
|
with self.subTest(obj=obj, round=rnd, timestamp=ts):
|
||||||
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
|
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
|
||||||
|
@ -958,33 +948,23 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
|
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for ns, tv, rnd in (
|
for ns, tv, rnd in (
|
||||||
# nanoseconds
|
# nanoseconds
|
||||||
(1, (0, 0), FLOOR),
|
(1, (0, 0), FLOOR),
|
||||||
(1, (0, 1), CEILING),
|
(1, (0, 1), CEILING),
|
||||||
(1, (0, 0), HALF_UP),
|
(1, (0, 0), HALF_EVEN),
|
||||||
(-1, (-1, 999999), FLOOR),
|
(-1, (-1, 999999), FLOOR),
|
||||||
(-1, (0, 0), CEILING),
|
(-1, (0, 0), CEILING),
|
||||||
(-1, (0, 0), HALF_UP),
|
(-1, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
# seconds + nanoseconds
|
# half even
|
||||||
(1234567001, (1, 234567), FLOOR),
|
(-1500, (-1, 999998), HALF_EVEN),
|
||||||
(1234567001, (1, 234568), CEILING),
|
(-999, (-1, 999999), HALF_EVEN),
|
||||||
(1234567001, (1, 234567), HALF_UP),
|
(-500, (0, 0), HALF_EVEN),
|
||||||
(-1234567001, (-2, 765432), FLOOR),
|
(500, (0, 0), HALF_EVEN),
|
||||||
(-1234567001, (-2, 765433), CEILING),
|
(999, (0, 1), HALF_EVEN),
|
||||||
(-1234567001, (-2, 765433), HALF_UP),
|
(1500, (0, 2), HALF_EVEN),
|
||||||
|
|
||||||
# half up
|
|
||||||
(499, (0, 0), HALF_UP),
|
|
||||||
(500, (0, 1), HALF_UP),
|
|
||||||
(501, (0, 1), HALF_UP),
|
|
||||||
(999, (0, 1), HALF_UP),
|
|
||||||
(-499, (0, 0), HALF_UP),
|
|
||||||
(-500, (0, 0), HALF_UP),
|
|
||||||
(-501, (-1, 999999), HALF_UP),
|
|
||||||
(-999, (-1, 999999), HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
|
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
|
||||||
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
|
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
|
||||||
|
@ -1027,33 +1007,31 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
|
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for ns, ms, rnd in (
|
for ns, ms, rnd in (
|
||||||
# nanoseconds
|
# nanoseconds
|
||||||
(1, 0, FLOOR),
|
(1, 0, FLOOR),
|
||||||
(1, 1, CEILING),
|
(1, 1, CEILING),
|
||||||
(1, 0, HALF_UP),
|
(1, 0, HALF_EVEN),
|
||||||
(-1, 0, FLOOR),
|
(-1, -1, FLOOR),
|
||||||
(-1, -1, CEILING),
|
(-1, 0, CEILING),
|
||||||
(-1, 0, HALF_UP),
|
(-1, 0, HALF_EVEN),
|
||||||
|
|
||||||
# seconds + nanoseconds
|
# seconds + nanoseconds
|
||||||
(1234 * MS_TO_NS + 1, 1234, FLOOR),
|
(1234 * MS_TO_NS + 1, 1234, FLOOR),
|
||||||
(1234 * MS_TO_NS + 1, 1235, CEILING),
|
(1234 * MS_TO_NS + 1, 1235, CEILING),
|
||||||
(1234 * MS_TO_NS + 1, 1234, HALF_UP),
|
(1234 * MS_TO_NS + 1, 1234, HALF_EVEN),
|
||||||
(-1234 * MS_TO_NS - 1, -1234, FLOOR),
|
(-1234 * MS_TO_NS - 1, -1235, FLOOR),
|
||||||
(-1234 * MS_TO_NS - 1, -1235, CEILING),
|
(-1234 * MS_TO_NS - 1, -1234, CEILING),
|
||||||
(-1234 * MS_TO_NS - 1, -1234, HALF_UP),
|
(-1234 * MS_TO_NS - 1, -1234, HALF_EVEN),
|
||||||
|
|
||||||
# half up
|
# half up
|
||||||
(499999, 0, HALF_UP),
|
(-1500000, -2, HALF_EVEN),
|
||||||
(499999, 0, HALF_UP),
|
(-999999, -1, HALF_EVEN),
|
||||||
(500000, 1, HALF_UP),
|
(-500000, 0, HALF_EVEN),
|
||||||
(999999, 1, HALF_UP),
|
(500000, 0, HALF_EVEN),
|
||||||
(-499999, 0, HALF_UP),
|
(999999, 1, HALF_EVEN),
|
||||||
(-500000, -1, HALF_UP),
|
(1500000, 2, HALF_EVEN),
|
||||||
(-500001, -1, HALF_UP),
|
|
||||||
(-999999, -1, HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
|
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
|
||||||
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms)
|
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms)
|
||||||
|
@ -1079,31 +1057,31 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
|
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for ns, ms, rnd in (
|
for ns, ms, rnd in (
|
||||||
# nanoseconds
|
# nanoseconds
|
||||||
(1, 0, FLOOR),
|
(1, 0, FLOOR),
|
||||||
(1, 1, CEILING),
|
(1, 1, CEILING),
|
||||||
(1, 0, HALF_UP),
|
(1, 0, HALF_EVEN),
|
||||||
(-1, 0, FLOOR),
|
(-1, -1, FLOOR),
|
||||||
(-1, -1, CEILING),
|
(-1, 0, CEILING),
|
||||||
(-1, 0, HALF_UP),
|
(-1, 0, HALF_EVEN),
|
||||||
|
|
||||||
# seconds + nanoseconds
|
# seconds + nanoseconds
|
||||||
(1234 * US_TO_NS + 1, 1234, FLOOR),
|
(1234 * US_TO_NS + 1, 1234, FLOOR),
|
||||||
(1234 * US_TO_NS + 1, 1235, CEILING),
|
(1234 * US_TO_NS + 1, 1235, CEILING),
|
||||||
(1234 * US_TO_NS + 1, 1234, HALF_UP),
|
(1234 * US_TO_NS + 1, 1234, HALF_EVEN),
|
||||||
(-1234 * US_TO_NS - 1, -1234, FLOOR),
|
(-1234 * US_TO_NS - 1, -1235, FLOOR),
|
||||||
(-1234 * US_TO_NS - 1, -1235, CEILING),
|
(-1234 * US_TO_NS - 1, -1234, CEILING),
|
||||||
(-1234 * US_TO_NS - 1, -1234, HALF_UP),
|
(-1234 * US_TO_NS - 1, -1234, HALF_EVEN),
|
||||||
|
|
||||||
# half up
|
# half up
|
||||||
(1499, 1, HALF_UP),
|
(-1500, -2, HALF_EVEN),
|
||||||
(1500, 2, HALF_UP),
|
(-999, -1, HALF_EVEN),
|
||||||
(1501, 2, HALF_UP),
|
(-500, 0, HALF_EVEN),
|
||||||
(-1499, -1, HALF_UP),
|
(500, 0, HALF_EVEN),
|
||||||
(-1500, -2, HALF_UP),
|
(999, 1, HALF_EVEN),
|
||||||
(-1501, -2, HALF_UP),
|
(1500, 2, HALF_EVEN),
|
||||||
):
|
):
|
||||||
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
|
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
|
||||||
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms)
|
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms)
|
||||||
|
@ -1142,23 +1120,23 @@ class TestOldPyTime(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, time_t, rnd in (
|
for obj, time_t, rnd in (
|
||||||
(-1.9, -2, FLOOR),
|
(-1.9, -2, FLOOR),
|
||||||
(-1.9, -2, HALF_UP),
|
(-1.9, -2, HALF_EVEN),
|
||||||
(-1.9, -1, CEILING),
|
(-1.9, -1, CEILING),
|
||||||
|
|
||||||
(1.9, 1, FLOOR),
|
(1.9, 1, FLOOR),
|
||||||
(1.9, 2, HALF_UP),
|
(1.9, 2, HALF_EVEN),
|
||||||
(1.9, 2, CEILING),
|
(1.9, 2, CEILING),
|
||||||
|
|
||||||
(-0.6, -1, HALF_UP),
|
# half even
|
||||||
(-0.5, -1, HALF_UP),
|
(-1.5, -2, HALF_EVEN),
|
||||||
(-0.4, 0, HALF_UP),
|
(-0.9, -1, HALF_EVEN),
|
||||||
|
(-0.5, 0, HALF_EVEN),
|
||||||
(0.4, 0, HALF_UP),
|
( 0.5, 0, HALF_EVEN),
|
||||||
(0.5, 1, HALF_UP),
|
( 0.9, 1, HALF_EVEN),
|
||||||
(0.6, 1, HALF_UP),
|
( 1.5, 2, HALF_EVEN),
|
||||||
):
|
):
|
||||||
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
|
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
|
||||||
|
|
||||||
|
@ -1192,29 +1170,27 @@ class TestOldPyTime(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, timeval, rnd in (
|
for obj, timeval, rnd in (
|
||||||
(-1e-7, (-1, 999999), FLOOR),
|
(-1e-7, (-1, 999999), FLOOR),
|
||||||
(-1e-7, (0, 0), CEILING),
|
(-1e-7, (0, 0), CEILING),
|
||||||
(-1e-7, (0, 0), HALF_UP),
|
(-1e-7, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
(1e-7, (0, 0), FLOOR),
|
(1e-7, (0, 0), FLOOR),
|
||||||
(1e-7, (0, 1), CEILING),
|
(1e-7, (0, 1), CEILING),
|
||||||
(1e-7, (0, 0), HALF_UP),
|
(1e-7, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
(0.9999999, (0, 999999), FLOOR),
|
(0.9999999, (0, 999999), FLOOR),
|
||||||
(0.9999999, (1, 0), CEILING),
|
(0.9999999, (1, 0), CEILING),
|
||||||
(0.9999999, (1, 0), HALF_UP),
|
(0.9999999, (1, 0), HALF_EVEN),
|
||||||
|
|
||||||
(-0.6e-6, (-1, 999999), HALF_UP),
|
# half even
|
||||||
# skipped, -0.5e-6 is inexact in base 2
|
(-1.5e-6, (-1, 999998), HALF_EVEN),
|
||||||
#(-0.5e-6, (-1, 999999), HALF_UP),
|
(-0.9e-6, (-1, 999999), HALF_EVEN),
|
||||||
(-0.4e-6, (0, 0), HALF_UP),
|
(-0.5e-6, (0, 0), HALF_EVEN),
|
||||||
|
(0.5e-6, (0, 0), HALF_EVEN),
|
||||||
(0.4e-6, (0, 0), HALF_UP),
|
(0.9e-6, (0, 1), HALF_EVEN),
|
||||||
# skipped, 0.5e-6 is inexact in base 2
|
(1.5e-6, (0, 2), HALF_EVEN),
|
||||||
#(0.5e-6, (0, 1), HALF_UP),
|
|
||||||
(0.6e-6, (0, 1), HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj, round=rnd, timeval=timeval):
|
with self.subTest(obj=obj, round=rnd, timeval=timeval):
|
||||||
self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
|
self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
|
||||||
|
@ -1248,28 +1224,27 @@ class TestOldPyTime(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
FLOOR = _PyTime.ROUND_FLOOR
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
CEILING = _PyTime.ROUND_CEILING
|
CEILING = _PyTime.ROUND_CEILING
|
||||||
HALF_UP = _PyTime.ROUND_HALF_UP
|
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
|
||||||
for obj, timespec, rnd in (
|
for obj, timespec, rnd in (
|
||||||
(-1e-10, (-1, 999999999), FLOOR),
|
(-1e-10, (-1, 999999999), FLOOR),
|
||||||
(-1e-10, (0, 0), CEILING),
|
(-1e-10, (0, 0), CEILING),
|
||||||
(-1e-10, (0, 0), HALF_UP),
|
(-1e-10, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
(1e-10, (0, 0), FLOOR),
|
(1e-10, (0, 0), FLOOR),
|
||||||
(1e-10, (0, 1), CEILING),
|
(1e-10, (0, 1), CEILING),
|
||||||
(1e-10, (0, 0), HALF_UP),
|
(1e-10, (0, 0), HALF_EVEN),
|
||||||
|
|
||||||
(0.9999999999, (0, 999999999), FLOOR),
|
(0.9999999999, (0, 999999999), FLOOR),
|
||||||
(0.9999999999, (1, 0), CEILING),
|
(0.9999999999, (1, 0), CEILING),
|
||||||
(0.9999999999, (1, 0), HALF_UP),
|
(0.9999999999, (1, 0), HALF_EVEN),
|
||||||
|
|
||||||
(-0.6e-9, (-1, 999999999), HALF_UP),
|
# half even
|
||||||
# skipped, 0.5e-6 is inexact in base 2
|
(-1.5e-9, (-1, 999999998), HALF_EVEN),
|
||||||
#(-0.5e-9, (-1, 999999999), HALF_UP),
|
(-0.9e-9, (-1, 999999999), HALF_EVEN),
|
||||||
(-0.4e-9, (0, 0), HALF_UP),
|
(-0.5e-9, (0, 0), HALF_EVEN),
|
||||||
|
(0.5e-9, (0, 0), HALF_EVEN),
|
||||||
(0.4e-9, (0, 0), HALF_UP),
|
(0.9e-9, (0, 1), HALF_EVEN),
|
||||||
(0.5e-9, (0, 1), HALF_UP),
|
(1.5e-9, (0, 2), HALF_EVEN),
|
||||||
(0.6e-9, (0, 1), HALF_UP),
|
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj, round=rnd, timespec=timespec):
|
with self.subTest(obj=obj, round=rnd, timespec=timespec):
|
||||||
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
|
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
|
||||||
|
|
|
@ -20,9 +20,9 @@ Library
|
||||||
- Issue #22241: timezone.utc name is now plain 'UTC', not 'UTC-00:00'.
|
- Issue #22241: timezone.utc name is now plain 'UTC', not 'UTC-00:00'.
|
||||||
|
|
||||||
- Issue #23517: fromtimestamp() and utcfromtimestamp() methods of
|
- Issue #23517: fromtimestamp() and utcfromtimestamp() methods of
|
||||||
datetime.datetime now round microseconds to nearest with ties going away from
|
datetime.datetime now round microseconds to nearest with ties going to
|
||||||
zero (ROUND_HALF_UP), as Python 2 and Python older than 3.3, instead of
|
nearest even integer (ROUND_HALF_EVEN), as round(float), instead of rounding
|
||||||
rounding towards -Infinity (ROUND_FLOOR).
|
towards -Infinity (ROUND_FLOOR).
|
||||||
|
|
||||||
- Issue #23552: Timeit now warns when there is substantial (4x) variance
|
- Issue #23552: Timeit now warns when there is substantial (4x) variance
|
||||||
between best and worst times. Patch from Serhiy Storchaka.
|
between best and worst times. Patch from Serhiy Storchaka.
|
||||||
|
|
|
@ -4103,7 +4103,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
|
||||||
long us;
|
long us;
|
||||||
|
|
||||||
if (_PyTime_ObjectToTimeval(timestamp,
|
if (_PyTime_ObjectToTimeval(timestamp,
|
||||||
&timet, &us, _PyTime_ROUND_HALF_UP) == -1)
|
&timet, &us, _PyTime_ROUND_HALF_EVEN) == -1)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo);
|
return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo);
|
||||||
|
|
|
@ -2648,7 +2648,7 @@ check_time_rounding(int round)
|
||||||
{
|
{
|
||||||
if (round != _PyTime_ROUND_FLOOR
|
if (round != _PyTime_ROUND_FLOOR
|
||||||
&& round != _PyTime_ROUND_CEILING
|
&& round != _PyTime_ROUND_CEILING
|
||||||
&& round != _PyTime_ROUND_HALF_UP) {
|
&& round != _PyTime_ROUND_HALF_EVEN) {
|
||||||
PyErr_SetString(PyExc_ValueError, "invalid rounding");
|
PyErr_SetString(PyExc_ValueError, "invalid rounding");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,18 +61,15 @@ _PyLong_FromTime_t(time_t t)
|
||||||
}
|
}
|
||||||
|
|
||||||
double
|
double
|
||||||
_PyTime_RoundHalfUp(double x)
|
_PyTime_RoundHalfEven(double x)
|
||||||
{
|
{
|
||||||
/* volatile avoids optimization changing how numbers are rounded */
|
double rounded = round(x);
|
||||||
volatile double d = x;
|
if (fabs(x-rounded) == 0.5)
|
||||||
if (d >= 0.0)
|
/* halfway case: round to even */
|
||||||
d = floor(d + 0.5);
|
rounded = 2.0*round(x/2.0);
|
||||||
else
|
return rounded;
|
||||||
d = ceil(d - 0.5);
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
|
_PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
|
||||||
double denominator, _PyTime_round_t round)
|
double denominator, _PyTime_round_t round)
|
||||||
|
@ -84,8 +81,8 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
|
||||||
floatpart = modf(d, &intpart);
|
floatpart = modf(d, &intpart);
|
||||||
|
|
||||||
floatpart *= denominator;
|
floatpart *= denominator;
|
||||||
if (round == _PyTime_ROUND_HALF_UP)
|
if (round == _PyTime_ROUND_HALF_EVEN)
|
||||||
floatpart = _PyTime_RoundHalfUp(floatpart);
|
floatpart = _PyTime_RoundHalfEven(floatpart);
|
||||||
else if (round == _PyTime_ROUND_CEILING)
|
else if (round == _PyTime_ROUND_CEILING)
|
||||||
floatpart = ceil(floatpart);
|
floatpart = ceil(floatpart);
|
||||||
else
|
else
|
||||||
|
@ -140,8 +137,8 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round)
|
||||||
volatile double d;
|
volatile double d;
|
||||||
|
|
||||||
d = PyFloat_AsDouble(obj);
|
d = PyFloat_AsDouble(obj);
|
||||||
if (round == _PyTime_ROUND_HALF_UP)
|
if (round == _PyTime_ROUND_HALF_EVEN)
|
||||||
d = _PyTime_RoundHalfUp(d);
|
d = _PyTime_RoundHalfEven(d);
|
||||||
else if (round == _PyTime_ROUND_CEILING)
|
else if (round == _PyTime_ROUND_CEILING)
|
||||||
d = ceil(d);
|
d = ceil(d);
|
||||||
else
|
else
|
||||||
|
@ -266,8 +263,8 @@ _PyTime_FromFloatObject(_PyTime_t *t, double value, _PyTime_round_t round,
|
||||||
d = value;
|
d = value;
|
||||||
d *= to_nanoseconds;
|
d *= to_nanoseconds;
|
||||||
|
|
||||||
if (round == _PyTime_ROUND_HALF_UP)
|
if (round == _PyTime_ROUND_HALF_EVEN)
|
||||||
d = _PyTime_RoundHalfUp(d);
|
d = _PyTime_RoundHalfEven(d);
|
||||||
else if (round == _PyTime_ROUND_CEILING)
|
else if (round == _PyTime_ROUND_CEILING)
|
||||||
d = ceil(d);
|
d = ceil(d);
|
||||||
else
|
else
|
||||||
|
@ -351,14 +348,16 @@ _PyTime_AsNanosecondsObject(_PyTime_t t)
|
||||||
}
|
}
|
||||||
|
|
||||||
static _PyTime_t
|
static _PyTime_t
|
||||||
_PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round)
|
_PyTime_Divide(const _PyTime_t t, const _PyTime_t k,
|
||||||
|
const _PyTime_round_t round)
|
||||||
{
|
{
|
||||||
assert(k > 1);
|
assert(k > 1);
|
||||||
if (round == _PyTime_ROUND_HALF_UP) {
|
if (round == _PyTime_ROUND_HALF_EVEN) {
|
||||||
_PyTime_t x, r;
|
_PyTime_t x, r, abs_r;
|
||||||
x = t / k;
|
x = t / k;
|
||||||
r = t % k;
|
r = t % k;
|
||||||
if (Py_ABS(r) >= k / 2) {
|
abs_r = Py_ABS(r);
|
||||||
|
if (abs_r > k / 2 || (abs_r == k / 2 && (Py_ABS(x) & 1))) {
|
||||||
if (t >= 0)
|
if (t >= 0)
|
||||||
x++;
|
x++;
|
||||||
else
|
else
|
||||||
|
@ -369,11 +368,15 @@ _PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round)
|
||||||
else if (round == _PyTime_ROUND_CEILING) {
|
else if (round == _PyTime_ROUND_CEILING) {
|
||||||
if (t >= 0)
|
if (t >= 0)
|
||||||
return (t + k - 1) / k;
|
return (t + k - 1) / k;
|
||||||
|
else
|
||||||
|
return t / k;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (t >= 0)
|
||||||
|
return t / k;
|
||||||
else
|
else
|
||||||
return (t - (k - 1)) / k;
|
return (t - (k - 1)) / k;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return t / k;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_PyTime_t
|
_PyTime_t
|
||||||
|
@ -392,17 +395,12 @@ static int
|
||||||
_PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
|
_PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
|
||||||
int raise)
|
int raise)
|
||||||
{
|
{
|
||||||
const long k = US_TO_NS;
|
|
||||||
_PyTime_t secs, ns;
|
_PyTime_t secs, ns;
|
||||||
int res = 0;
|
int res = 0;
|
||||||
int usec;
|
int usec;
|
||||||
|
|
||||||
secs = t / SEC_TO_NS;
|
secs = t / SEC_TO_NS;
|
||||||
ns = t % SEC_TO_NS;
|
ns = t % SEC_TO_NS;
|
||||||
if (ns < 0) {
|
|
||||||
ns += SEC_TO_NS;
|
|
||||||
secs -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
/* On Windows, timeval.tv_sec is a long (32 bit),
|
/* On Windows, timeval.tv_sec is a long (32 bit),
|
||||||
|
@ -427,23 +425,12 @@ _PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
|
||||||
res = -1;
|
res = -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (round == _PyTime_ROUND_HALF_UP) {
|
usec = (int)_PyTime_Divide(ns, US_TO_NS, round);
|
||||||
_PyTime_t r;
|
if (usec < 0) {
|
||||||
usec = (int)(ns / k);
|
usec += SEC_TO_US;
|
||||||
r = ns % k;
|
tv->tv_sec -= 1;
|
||||||
if (Py_ABS(r) >= k / 2) {
|
|
||||||
if (ns >= 0)
|
|
||||||
usec++;
|
|
||||||
else
|
|
||||||
usec--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (round == _PyTime_ROUND_CEILING)
|
else if (usec >= SEC_TO_US) {
|
||||||
usec = (int)((ns + k - 1) / k);
|
|
||||||
else
|
|
||||||
usec = (int)(ns / k);
|
|
||||||
|
|
||||||
if (usec >= SEC_TO_US) {
|
|
||||||
usec -= SEC_TO_US;
|
usec -= SEC_TO_US;
|
||||||
tv->tv_sec += 1;
|
tv->tv_sec += 1;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue