test_time: rewrite PyTime API rounding tests

Drop all hardcoded tests. Instead, reimplement each function in Python, usually
using decimal.Decimal for the rounding mode.

Add much more values to the dataset. Test various timestamp units from
picroseconds to seconds, in integer and float.

Enhance also _PyTime_AsSecondsDouble().
This commit is contained in:
Victor Stinner 2015-09-09 22:32:48 +02:00
parent 9ae47dfbd9
commit 3e2c8d84c6
2 changed files with 221 additions and 545 deletions

View file

@ -1,6 +1,8 @@
from test import support
import decimal
import enum
import locale
import math
import platform
import sys
import sysconfig
@ -21,9 +23,11 @@
TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
TIME_MINYEAR = -TIME_MAXYEAR - 1
SEC_TO_US = 10 ** 6
US_TO_NS = 10 ** 3
MS_TO_NS = 10 ** 6
SEC_TO_NS = 10 ** 9
NS_TO_SEC = 10 ** 9
class _PyTime(enum.IntEnum):
# Round towards minus infinity (-inf)
@ -33,8 +37,13 @@ class _PyTime(enum.IntEnum):
# Round to nearest with ties going to nearest even integer
ROUND_HALF_EVEN = 2
ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING,
_PyTime.ROUND_HALF_EVEN)
# Rounding modes supported by PyTime
ROUNDING_MODES = (
# (PyTime rounding method, decimal rounding method)
(_PyTime.ROUND_FLOOR, decimal.ROUND_FLOOR),
(_PyTime.ROUND_CEILING, decimal.ROUND_CEILING),
(_PyTime.ROUND_HALF_EVEN, decimal.ROUND_HALF_EVEN),
)
class TimeTestCase(unittest.TestCase):
@ -610,126 +619,6 @@ class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear, unittest.TestCase):
class TestPytime(unittest.TestCase):
def setUp(self):
self.invalid_values = (
-(2 ** 100), 2 ** 100,
-(2.0 ** 100.0), 2.0 ** 100.0,
)
@support.cpython_only
def test_time_t(self):
from _testcapi import pytime_object_to_time_t
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, seconds in (
# int
(-1, -1),
(0, 0),
(1, 1),
# float
(-1.0, -1),
(1.0, 1),
):
with self.subTest(obj=obj, round=rnd, seconds=seconds):
self.assertEqual(pytime_object_to_time_t(obj, rnd),
seconds)
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, seconds, rnd in (
(-1.9, -2, FLOOR),
(-1.9, -1, CEILING),
(-1.9, -2, HALF_EVEN),
(1.9, 1, FLOOR),
(1.9, 2, CEILING),
(1.9, 2, HALF_EVEN),
# half even
(-1.5, -2, HALF_EVEN),
(-0.9, -1, HALF_EVEN),
(-0.5, 0, HALF_EVEN),
( 0.5, 0, HALF_EVEN),
( 0.9, 1, HALF_EVEN),
( 1.5, 2, HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, seconds=seconds):
self.assertEqual(pytime_object_to_time_t(obj, rnd), seconds)
# Test OverflowError
rnd = _PyTime.ROUND_FLOOR
for invalid in self.invalid_values:
self.assertRaises(OverflowError,
pytime_object_to_time_t, invalid, rnd)
@support.cpython_only
def test_object_to_timespec(self):
from _testcapi import pytime_object_to_timespec
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, timespec in (
# int
(0, (0, 0)),
(-1, (-1, 0)),
# float
(-1/2**7, (-1, 992187500)),
(-1.0, (-1, 0)),
(-1e-9, (-1, 999999999)),
(1e-9, (0, 1)),
(1.0, (1, 0)),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timespec, rnd in (
# Round towards minus infinity (-inf)
(-1e-10, (0, 0), CEILING),
(-1e-10, (-1, 999999999), FLOOR),
(-1e-10, (0, 0), HALF_EVEN),
(1e-10, (0, 0), FLOOR),
(1e-10, (0, 1), CEILING),
(1e-10, (0, 0), HALF_EVEN),
(0.9999999999, (0, 999999999), FLOOR),
(0.9999999999, (1, 0), CEILING),
(1.1234567890, (1, 123456789), FLOOR),
(1.1234567899, (1, 123456789), FLOOR),
(-1.1234567890, (-2, 876543210), FLOOR),
(-1.1234567891, (-2, 876543210), FLOOR),
# Round towards infinity (+inf)
(1.1234567890, (1, 123456790), CEILING),
(1.1234567899, (1, 123456790), CEILING),
(-1.1234567890, (-2, 876543211), CEILING),
(-1.1234567891, (-2, 876543211), CEILING),
# half even
(-1.5e-9, (-1, 999999998), HALF_EVEN),
(-0.9e-9, (-1, 999999999), HALF_EVEN),
(-0.5e-9, (0, 0), HALF_EVEN),
(0.5e-9, (0, 0), HALF_EVEN),
(0.9e-9, (0, 1), HALF_EVEN),
(1.5e-9, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
# Test OverflowError
rnd = _PyTime.ROUND_FLOOR
for invalid in self.invalid_values:
self.assertRaises(OverflowError,
pytime_object_to_timespec, invalid, rnd)
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_localtime_timezone(self):
@ -784,476 +673,259 @@ def test_short_times(self):
self.assertIs(lt.tm_zone, None)
@unittest.skipUnless(_testcapi is not None,
'need the _testcapi module')
class TestPyTime_t(unittest.TestCase):
@unittest.skipIf(_testcapi is None, 'need the _testcapi module')
class CPyTimeTestCase:
"""
Test the _PyTime_t API.
Base class to test the C _PyTime_t API.
"""
OVERFLOW_SECONDS = None
def _rounding_values(self, use_float):
"Build timestamps used to test rounding."
units = [1, US_TO_NS, MS_TO_NS, SEC_TO_NS]
if use_float:
# picoseconds are only tested to pytime_converter accepting floats
units.append(1e-3)
values = (
# small values
1, 2, 5, 7, 123, 456, 1234,
# 10^k - 1
9,
99,
999,
9999,
99999,
999999,
# test half even rounding near 0.5, 1.5, 2.5, 3.5, 4.5
499, 500, 501,
1499, 1500, 1501,
2500,
3500,
4500,
)
ns_timestamps = [0]
for unit in units:
for value in values:
ns = value * unit
ns_timestamps.extend((-ns, ns))
for pow2 in (0, 5, 10, 15, 22, 23, 24, 30, 33):
ns = (2 ** pow2) * SEC_TO_NS
ns_timestamps.extend((
-ns-1, -ns, -ns+1,
ns-1, ns, ns+1
))
for seconds in (_testcapi.INT_MIN, _testcapi.INT_MAX):
ns_timestamps.append(seconds * SEC_TO_NS)
if use_float:
# numbers with an extract representation in IEEE 754 (base 2)
for pow2 in (3, 7, 10, 15):
ns = 2.0 ** (-pow2)
ns_timestamps.extend((-ns, ns))
# seconds close to _PyTime_t type limit
ns = (2 ** 63 // SEC_TO_NS) * SEC_TO_NS
ns_timestamps.extend((-ns, ns))
return ns_timestamps
def _check_rounding(self, pytime_converter, expected_func,
use_float, unit_to_sec, value_filter=None):
def convert_values(ns_timestamps):
if use_float:
unit_to_ns = SEC_TO_NS / float(unit_to_sec)
values = [ns / unit_to_ns for ns in ns_timestamps]
else:
unit_to_ns = SEC_TO_NS // unit_to_sec
values = [ns // unit_to_ns for ns in ns_timestamps]
if value_filter:
values = filter(value_filter, values)
# remove duplicates and sort
return sorted(set(values))
# test rounding
ns_timestamps = self._rounding_values(use_float)
valid_values = convert_values(ns_timestamps)
for time_rnd, decimal_rnd in ROUNDING_MODES :
context = decimal.getcontext()
context.rounding = decimal_rnd
for value in valid_values:
expected = expected_func(value)
self.assertEqual(pytime_converter(value, time_rnd),
expected,
{'value': value, 'rounding': decimal_rnd})
# test overflow
ns = self.OVERFLOW_SECONDS * SEC_TO_NS
ns_timestamps = (-ns, ns)
overflow_values = convert_values(ns_timestamps)
for time_rnd, _ in ROUNDING_MODES :
for value in overflow_values:
with self.assertRaises(OverflowError):
pytime_converter(value, time_rnd)
def check_int_rounding(self, pytime_converter, expected_func, unit_to_sec=1,
value_filter=None):
self._check_rounding(pytime_converter, expected_func,
False, unit_to_sec, value_filter)
def check_float_rounding(self, pytime_converter, expected_func, unit_to_sec=1):
self._check_rounding(pytime_converter, expected_func,
True, unit_to_sec)
def decimal_round(self, x):
d = decimal.Decimal(x)
d = d.quantize(1)
return int(d)
class TestCPyTime(CPyTimeTestCase, unittest.TestCase):
"""
Test the C _PyTime_t API.
"""
# _PyTime_t is a 64-bit signed integer
OVERFLOW_SECONDS = math.ceil((2**63 + 1) / SEC_TO_NS)
def test_FromSeconds(self):
from _testcapi import PyTime_FromSeconds
for seconds in (0, 3, -456, _testcapi.INT_MAX, _testcapi.INT_MIN):
with self.subTest(seconds=seconds):
self.assertEqual(PyTime_FromSeconds(seconds),
seconds * SEC_TO_NS)
# PyTime_FromSeconds() expects a C int, reject values out of range
def c_int_filter(secs):
return (_testcapi.INT_MIN <= secs <= _testcapi.INT_MAX)
self.check_int_rounding(lambda secs, rnd: PyTime_FromSeconds(secs),
lambda secs: secs * SEC_TO_NS,
value_filter=c_int_filter)
def test_FromSecondsObject(self):
from _testcapi import PyTime_FromSecondsObject
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, ts in (
# integers
(0, 0),
(1, SEC_TO_NS),
(-3, -3 * SEC_TO_NS),
self.check_int_rounding(
PyTime_FromSecondsObject,
lambda secs: secs * SEC_TO_NS)
# float: subseconds
(0.0, 0),
(1e-9, 1),
(1e-6, 10 ** 3),
(1e-3, 10 ** 6),
# float: seconds
(2.0, 2 * SEC_TO_NS),
(123.0, 123 * SEC_TO_NS),
(-7.0, -7 * SEC_TO_NS),
# nanosecond are kept for value <= 2^23 seconds,
(2**22 - 1e-9, 4194303999999999),
(2**22, 4194304000000000),
(2**22 + 1e-9, 4194304000000001),
(2**23 - 1e-9, 8388607999999999),
(2**23, 8388608000000000),
# start loosing precision for value > 2^23 seconds
(2**23 + 1e-9, 8388608000000002),
# nanoseconds are lost for value > 2^23 seconds
(2**24 - 1e-9, 16777215999999998),
(2**24, 16777216000000000),
(2**24 + 1e-9, 16777216000000000),
(2**25 - 1e-9, 33554432000000000),
(2**25 , 33554432000000000),
(2**25 + 1e-9, 33554432000000000),
# close to 2^63 nanoseconds (_PyTime_t limit)
(9223372036, 9223372036 * SEC_TO_NS),
(9223372036.0, 9223372036 * SEC_TO_NS),
(-9223372036, -9223372036 * SEC_TO_NS),
(-9223372036.0, -9223372036 * SEC_TO_NS),
):
with self.subTest(obj=obj, round=rnd, timestamp=ts):
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
with self.subTest(round=rnd):
with self.assertRaises(OverflowError):
PyTime_FromSecondsObject(9223372037, rnd)
PyTime_FromSecondsObject(9223372037.0, rnd)
PyTime_FromSecondsObject(-9223372037, rnd)
PyTime_FromSecondsObject(-9223372037.0, rnd)
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, ts, rnd in (
# close to zero
( 1e-10, 0, FLOOR),
( 1e-10, 1, CEILING),
( 1e-10, 0, HALF_EVEN),
(-1e-10, -1, FLOOR),
(-1e-10, 0, CEILING),
(-1e-10, 0, HALF_EVEN),
# test rounding of the last nanosecond
( 1.1234567899, 1123456789, FLOOR),
( 1.1234567899, 1123456790, CEILING),
( 1.1234567899, 1123456790, HALF_EVEN),
(-1.1234567899, -1123456790, FLOOR),
(-1.1234567899, -1123456789, CEILING),
(-1.1234567899, -1123456790, HALF_EVEN),
# close to 1 second
( 0.9999999999, 999999999, FLOOR),
( 0.9999999999, 1000000000, CEILING),
( 0.9999999999, 1000000000, HALF_EVEN),
(-0.9999999999, -1000000000, FLOOR),
(-0.9999999999, -999999999, CEILING),
(-0.9999999999, -1000000000, HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timestamp=ts):
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
self.check_float_rounding(
PyTime_FromSecondsObject,
lambda ns: self.decimal_round(ns * SEC_TO_NS))
def test_AsSecondsDouble(self):
from _testcapi import PyTime_AsSecondsDouble
for nanoseconds, seconds in (
# near 1 nanosecond
( 0, 0.0),
( 1, 1e-9),
(-1, -1e-9),
def float_converter(ns):
if abs(ns) % SEC_TO_NS == 0:
return float(ns // SEC_TO_NS)
else:
return float(ns) / SEC_TO_NS
# near 1 second
(SEC_TO_NS + 1, 1.0 + 1e-9),
(SEC_TO_NS, 1.0),
(SEC_TO_NS - 1, 1.0 - 1e-9),
self.check_int_rounding(lambda ns, rnd: PyTime_AsSecondsDouble(ns),
float_converter,
NS_TO_SEC)
# a few seconds
(123 * SEC_TO_NS, 123.0),
(-567 * SEC_TO_NS, -567.0),
def create_decimal_converter(self, denominator):
denom = decimal.Decimal(denominator)
# nanosecond are kept for value <= 2^23 seconds
(4194303999999999, 2**22 - 1e-9),
(4194304000000000, 2**22),
(4194304000000001, 2**22 + 1e-9),
def converter(value):
d = decimal.Decimal(value) / denom
return self.decimal_round(d)
# start loosing precision for value > 2^23 seconds
(8388608000000002, 2**23 + 1e-9),
return converter
# nanoseconds are lost for value > 2^23 seconds
(16777215999999998, 2**24 - 1e-9),
(16777215999999999, 2**24 - 1e-9),
(16777216000000000, 2**24 ),
(16777216000000001, 2**24 ),
(16777216000000002, 2**24 + 2e-9),
(33554432000000000, 2**25 ),
(33554432000000002, 2**25 ),
(33554432000000004, 2**25 + 4e-9),
# close to 2^63 nanoseconds (_PyTime_t limit)
(9223372036 * SEC_TO_NS, 9223372036.0),
(-9223372036 * SEC_TO_NS, -9223372036.0),
):
with self.subTest(nanoseconds=nanoseconds, seconds=seconds):
self.assertEqual(PyTime_AsSecondsDouble(nanoseconds),
seconds)
def test_timeval(self):
def test_AsTimeval(self):
from _testcapi import PyTime_AsTimeval
for rnd in ALL_ROUNDING_METHODS:
for ns, tv in (
# microseconds
(0, (0, 0)),
(1000, (0, 1)),
(-1000, (-1, 999999)),
# seconds
(2 * SEC_TO_NS, (2, 0)),
(-3 * SEC_TO_NS, (-3, 0)),
):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
us_converter = self.create_decimal_converter(US_TO_NS)
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, tv, rnd in (
# nanoseconds
(1, (0, 0), FLOOR),
(1, (0, 1), CEILING),
(1, (0, 0), HALF_EVEN),
(-1, (-1, 999999), FLOOR),
(-1, (0, 0), CEILING),
(-1, (0, 0), HALF_EVEN),
def timeval_converter(ns):
us = us_converter(ns)
return divmod(us, SEC_TO_US)
# half even
(-1500, (-1, 999998), HALF_EVEN),
(-999, (-1, 999999), HALF_EVEN),
(-500, (0, 0), HALF_EVEN),
(500, (0, 0), HALF_EVEN),
(999, (0, 1), HALF_EVEN),
(1500, (0, 2), HALF_EVEN),
):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
self.check_int_rounding(PyTime_AsTimeval,
timeval_converter,
NS_TO_SEC)
@unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'),
'need _testcapi.PyTime_AsTimespec')
def test_timespec(self):
def test_AsTimespec(self):
from _testcapi import PyTime_AsTimespec
for ns, ts in (
# nanoseconds
(0, (0, 0)),
(1, (0, 1)),
(-1, (-1, 999999999)),
# seconds
(2 * SEC_TO_NS, (2, 0)),
(-3 * SEC_TO_NS, (-3, 0)),
def timespec_converter(ns):
return divmod(ns, SEC_TO_NS)
# seconds + nanoseconds
(1234567890, (1, 234567890)),
(-1234567890, (-2, 765432110)),
):
with self.subTest(nanoseconds=ns, timespec=ts):
self.assertEqual(PyTime_AsTimespec(ns), ts)
self.check_int_rounding(lambda ns, rnd: PyTime_AsTimespec(ns),
timespec_converter,
NS_TO_SEC)
def test_milliseconds(self):
def test_AsMilliseconds(self):
from _testcapi import PyTime_AsMilliseconds
for rnd in ALL_ROUNDING_METHODS:
for ns, tv in (
# milliseconds
(1 * MS_TO_NS, 1),
(-2 * MS_TO_NS, -2),
# seconds
(2 * SEC_TO_NS, 2000),
(-3 * SEC_TO_NS, -3000),
):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), tv)
self.check_int_rounding(PyTime_AsMilliseconds,
self.create_decimal_converter(MS_TO_NS),
NS_TO_SEC)
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, ms, rnd in (
# nanoseconds
(1, 0, FLOOR),
(1, 1, CEILING),
(1, 0, HALF_EVEN),
(-1, -1, FLOOR),
(-1, 0, CEILING),
(-1, 0, HALF_EVEN),
# seconds + nanoseconds
(1234 * MS_TO_NS + 1, 1234, FLOOR),
(1234 * MS_TO_NS + 1, 1235, CEILING),
(1234 * MS_TO_NS + 1, 1234, HALF_EVEN),
(-1234 * MS_TO_NS - 1, -1235, FLOOR),
(-1234 * MS_TO_NS - 1, -1234, CEILING),
(-1234 * MS_TO_NS - 1, -1234, HALF_EVEN),
# half up
(-1500000, -2, HALF_EVEN),
(-999999, -1, HALF_EVEN),
(-500000, 0, HALF_EVEN),
(500000, 0, HALF_EVEN),
(999999, 1, HALF_EVEN),
(1500000, 2, HALF_EVEN),
):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms)
def test_microseconds(self):
def test_AsMicroseconds(self):
from _testcapi import PyTime_AsMicroseconds
for rnd in ALL_ROUNDING_METHODS:
for ns, tv in (
# microseconds
(1 * US_TO_NS, 1),
(-2 * US_TO_NS, -2),
# milliseconds
(1 * MS_TO_NS, 1000),
(-2 * MS_TO_NS, -2000),
# seconds
(2 * SEC_TO_NS, 2000000),
(-3 * SEC_TO_NS, -3000000),
):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), tv)
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, ms, rnd in (
# nanoseconds
(1, 0, FLOOR),
(1, 1, CEILING),
(1, 0, HALF_EVEN),
(-1, -1, FLOOR),
(-1, 0, CEILING),
(-1, 0, HALF_EVEN),
# seconds + nanoseconds
(1234 * US_TO_NS + 1, 1234, FLOOR),
(1234 * US_TO_NS + 1, 1235, CEILING),
(1234 * US_TO_NS + 1, 1234, HALF_EVEN),
(-1234 * US_TO_NS - 1, -1235, FLOOR),
(-1234 * US_TO_NS - 1, -1234, CEILING),
(-1234 * US_TO_NS - 1, -1234, HALF_EVEN),
# half up
(-1500, -2, HALF_EVEN),
(-999, -1, HALF_EVEN),
(-500, 0, HALF_EVEN),
(500, 0, HALF_EVEN),
(999, 1, HALF_EVEN),
(1500, 2, HALF_EVEN),
):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms)
self.check_int_rounding(PyTime_AsMicroseconds,
self.create_decimal_converter(US_TO_NS),
NS_TO_SEC)
@unittest.skipUnless(_testcapi is not None,
'need the _testcapi module')
class TestOldPyTime(unittest.TestCase):
class TestOldPyTime(CPyTimeTestCase, unittest.TestCase):
"""
Test the old _PyTime_t API: _PyTime_ObjectToXXX() functions.
Test the old C _PyTime_t API: _PyTime_ObjectToXXX() functions.
"""
def setUp(self):
self.invalid_values = (
-(2 ** 100), 2 ** 100,
-(2.0 ** 100.0), 2.0 ** 100.0,
)
@support.cpython_only
def test_time_t(self):
# time_t is a 32-bit or 64-bit signed integer
OVERFLOW_SECONDS = 2 ** 64
def test_object_to_time_t(self):
from _testcapi import pytime_object_to_time_t
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, time_t in (
# int
(0, 0),
(-1, -1),
self.check_int_rounding(pytime_object_to_time_t,
lambda secs: secs)
# float
(1.0, 1),
(-1.0, -1),
):
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
self.check_float_rounding(pytime_object_to_time_t,
self.decimal_round)
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, time_t, rnd in (
(-1.9, -2, FLOOR),
(-1.9, -2, HALF_EVEN),
(-1.9, -1, CEILING),
(1.9, 1, FLOOR),
(1.9, 2, HALF_EVEN),
(1.9, 2, CEILING),
# half even
(-1.5, -2, HALF_EVEN),
(-0.9, -1, HALF_EVEN),
(-0.5, 0, HALF_EVEN),
( 0.5, 0, HALF_EVEN),
( 0.9, 1, HALF_EVEN),
( 1.5, 2, HALF_EVEN),
):
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
# Test OverflowError
rnd = _PyTime.ROUND_FLOOR
for invalid in self.invalid_values:
self.assertRaises(OverflowError,
pytime_object_to_time_t, invalid, rnd)
def create_converter(self, sec_to_unit):
def converter(secs):
floatpart, intpart = math.modf(secs)
intpart = int(intpart)
floatpart *= sec_to_unit
floatpart = self.decimal_round(floatpart)
if floatpart < 0:
floatpart += sec_to_unit
intpart -= 1
elif floatpart >= sec_to_unit:
floatpart -= sec_to_unit
intpart += 1
return (intpart, floatpart)
return converter
def test_object_to_timeval(self):
from _testcapi import pytime_object_to_timeval
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, timeval in (
# int
(0, (0, 0)),
(-1, (-1, 0)),
self.check_int_rounding(pytime_object_to_timeval,
lambda secs: (secs, 0))
# float
(-1.0, (-1, 0)),
(1/2**6, (0, 15625)),
(-1/2**6, (-1, 984375)),
(-1e-6, (-1, 999999)),
(1e-6, (0, 1)),
):
with self.subTest(obj=obj, round=rnd, timeval=timeval):
self.assertEqual(pytime_object_to_timeval(obj, rnd),
timeval)
self.check_float_rounding(pytime_object_to_timeval,
self.create_converter(SEC_TO_US))
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timeval, rnd in (
(-1e-7, (-1, 999999), FLOOR),
(-1e-7, (0, 0), CEILING),
(-1e-7, (0, 0), HALF_EVEN),
(1e-7, (0, 0), FLOOR),
(1e-7, (0, 1), CEILING),
(1e-7, (0, 0), HALF_EVEN),
(0.9999999, (0, 999999), FLOOR),
(0.9999999, (1, 0), CEILING),
(0.9999999, (1, 0), HALF_EVEN),
# half even
(-1.5e-6, (-1, 999998), HALF_EVEN),
(-0.9e-6, (-1, 999999), HALF_EVEN),
(-0.5e-6, (0, 0), HALF_EVEN),
(0.5e-6, (0, 0), HALF_EVEN),
(0.9e-6, (0, 1), HALF_EVEN),
(1.5e-6, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timeval=timeval):
self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
rnd = _PyTime.ROUND_FLOOR
for invalid in self.invalid_values:
self.assertRaises(OverflowError,
pytime_object_to_timeval, invalid, rnd)
@support.cpython_only
def test_timespec(self):
def test_object_to_timespec(self):
from _testcapi import pytime_object_to_timespec
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
for obj, timespec in (
# int
(0, (0, 0)),
(-1, (-1, 0)),
self.check_int_rounding(pytime_object_to_timespec,
lambda secs: (secs, 0))
# float
(-1.0, (-1, 0)),
(-1e-9, (-1, 999999999)),
(1e-9, (0, 1)),
(-1/2**9, (-1, 998046875)),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd),
timespec)
self.check_float_rounding(pytime_object_to_timespec,
self.create_converter(SEC_TO_NS))
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timespec, rnd in (
(-1e-10, (-1, 999999999), FLOOR),
(-1e-10, (0, 0), CEILING),
(-1e-10, (0, 0), HALF_EVEN),
(1e-10, (0, 0), FLOOR),
(1e-10, (0, 1), CEILING),
(1e-10, (0, 0), HALF_EVEN),
(0.9999999999, (0, 999999999), FLOOR),
(0.9999999999, (1, 0), CEILING),
(0.9999999999, (1, 0), HALF_EVEN),
# half even
(-1.5e-9, (-1, 999999998), HALF_EVEN),
(-0.9e-9, (-1, 999999999), HALF_EVEN),
(-0.5e-9, (0, 0), HALF_EVEN),
(0.5e-9, (0, 0), HALF_EVEN),
(0.9e-9, (0, 1), HALF_EVEN),
(1.5e-9, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
# Test OverflowError
rnd = FLOOR
for invalid in self.invalid_values:
self.assertRaises(OverflowError,
pytime_object_to_timespec, invalid, rnd)
if __name__ == "__main__":

View file

@ -324,12 +324,16 @@ _PyTime_FromMillisecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t roun
double
_PyTime_AsSecondsDouble(_PyTime_t t)
{
_PyTime_t sec, ns;
/* Divide using integers to avoid rounding issues on the integer part.
1e-9 cannot be stored exactly in IEEE 64-bit. */
sec = t / SEC_TO_NS;
ns = t % SEC_TO_NS;
return (double)sec + (double)ns * 1e-9;
if (t % SEC_TO_NS == 0) {
_PyTime_t secs;
/* Divide using integers to avoid rounding issues on the integer part.
1e-9 cannot be stored exactly in IEEE 64-bit. */
secs = t / SEC_TO_NS;
return (double)secs;
}
else {
return (double)t / 1e9;
}
}
PyObject *