Issue #22117: time.monotonic() now uses the new _PyTime_t API

* Add _PyTime_FromNanoseconds()
* Add _PyTime_AsSecondsDouble()
* Add unit tests for _PyTime_AsSecondsDouble()
This commit is contained in:
Victor Stinner 2015-03-27 22:27:24 +01:00
parent 52d1493c0c
commit 4bfb460d88
5 changed files with 121 additions and 15 deletions

View File

@ -119,12 +119,18 @@ typedef PY_INT64_T _PyTime_t;
# error "_PyTime_t need signed 64-bit integer type"
#endif
/* Create a timestamp from a number of nanoseconds (C long). */
PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(PY_LONG_LONG ns);
/* Convert a Python float or int to a timetamp.
Raise an exception and return -1 on error, return 0 on success. */
PyAPI_FUNC(int) _PyTime_FromSecondsObject(_PyTime_t *t,
PyObject *obj,
_PyTime_round_t round);
/* Convert a timestamp to a number of seconds as a C double. */
PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t);
/* Convert timestamp to a number of milliseconds (10^-3 seconds). */
PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
_PyTime_round_t round);
@ -133,7 +139,8 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
object. */
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
/* Convert a timestamp to a timeval structure. */
/* Convert a timestamp to a timeval structure (microsecond resolution).
Raise an exception and return -1 on error, return 0 on success. */
PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
struct timeval *tv,
_PyTime_round_t round);
@ -147,6 +154,18 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
is available and works. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
The clock is not affected by system clock updates. The reference point of
the returned value is undefined, so that only the difference between the
results of consecutive calls is valid.
Fill info (if set) with information of the function used to get the time.
Return 0 on success, raise an exception and return -1 on error. */
PyAPI_FUNC(int) _PyTime_GetMonotonicClockWithInfo(
_PyTime_t *t,
_Py_clock_info_t *info);
#ifdef __cplusplus
}

View File

@ -16,6 +16,7 @@ SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
TIME_MINYEAR = -TIME_MAXYEAR - 1
SEC_TO_NS = 10 ** 9
class _PyTime(enum.IntEnum):
# Round towards zero
@ -770,9 +771,7 @@ class TestPytime(unittest.TestCase):
@support.cpython_only
class TestPyTime_t(unittest.TestCase):
def test_FromSecondsObject(self):
from _testcapi import pytime_fromsecondsobject
SEC_TO_NS = 10 ** 9
MAX_SEC = 2 ** 63 // 10 ** 9
from _testcapi import PyTime_FromSecondsObject
# Conversion giving the same result for all rounding methods
for rnd in ALL_ROUNDING_METHODS:
@ -811,21 +810,21 @@ class TestPyTime_t(unittest.TestCase):
(2**25 , 33554432000000000),
(2**25 + 1e-9, 33554432000000000),
# close to 2^63 nanoseconds
# 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)
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)
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
UP = _PyTime.ROUND_UP
@ -850,7 +849,52 @@ class TestPyTime_t(unittest.TestCase):
(-0.9999999999, -1000000000, UP),
):
with self.subTest(obj=obj, round=rnd, timestamp=ts):
self.assertEqual(pytime_fromsecondsobject(obj, rnd), ts)
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
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),
# near 1 second
(SEC_TO_NS + 1, 1.0 + 1e-9),
(SEC_TO_NS, 1.0),
(SEC_TO_NS - 1, 1.0 - 1e-9),
# a few seconds
(123 * SEC_TO_NS, 123.0),
(-567 * SEC_TO_NS, -567.0),
# nanosecond are kept for value <= 2^23 seconds
(4194303999999999, 2**22 - 1e-9),
(4194304000000000, 2**22),
(4194304000000001, 2**22 + 1e-9),
# start loosing precision for value > 2^23 seconds
(8388608000000002, 2**23 + 1e-9),
# 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)
if __name__ == "__main__":

View File

@ -3394,6 +3394,20 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args)
return _PyTime_AsNanosecondsObject(ts);
}
static PyObject *
test_pytime_assecondsdouble(PyObject *self, PyObject *args)
{
PY_LONG_LONG ns;
_PyTime_t ts;
double d;
if (!PyArg_ParseTuple(args, "L", &ns))
return NULL;
ts = _PyTime_FromNanoseconds(ns);
d = _PyTime_AsSecondsDouble(ts);
return PyFloat_FromDouble(d);
}
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
@ -3557,7 +3571,8 @@ static PyMethodDef TestMethods[] = {
return_null_without_error, METH_NOARGS},
{"return_result_with_error",
return_result_with_error, METH_NOARGS},
{"pytime_fromsecondsobject", test_pytime_fromsecondsobject, METH_VARARGS},
{"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS},
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
{NULL, NULL} /* sentinel */
};

View File

@ -887,12 +887,14 @@ should not be relied on.");
static PyObject *
pymonotonic(_Py_clock_info_t *info)
{
_PyTime_timeval tv;
if (_PyTime_monotonic_info(&tv, info) < 0) {
_PyTime_t t;
double d;
if (_PyTime_GetMonotonicClockWithInfo(&t, info) < 0) {
assert(info != NULL);
return NULL;
}
return PyFloat_FromDouble((double)tv.tv_sec + tv.tv_usec * 1e-6);
d = _PyTime_AsSecondsDouble(t);
return PyFloat_FromDouble(d);
}
static PyObject *

View File

@ -405,6 +405,15 @@ _PyTime_overflow(void)
"timestamp too large to convert to C _PyTime_t");
}
_PyTime_t
_PyTime_FromNanoseconds(PY_LONG_LONG ns)
{
_PyTime_t t;
assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t));
t = Py_SAFE_DOWNCAST(ns, PY_LONG_LONG, _PyTime_t);
return t;
}
#if !defined(MS_WINDOWS) && !defined(__APPLE__)
static int
_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts)
@ -470,6 +479,17 @@ _PyTime_FromSecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round)
}
}
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;
}
PyObject *
_PyTime_AsNanosecondsObject(_PyTime_t t)
{
@ -660,6 +680,12 @@ _PyTime_GetMonotonicClock(void)
return t;
}
int
_PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
{
return pymonotonic_new(tp, info, 1);
}
int
_PyTime_Init(void)
{