diff --git a/Include/pytime.h b/Include/pytime.h index 78e4ae900fe..9446b33967d 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -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 } diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 1bf0d09bd85..817da8a01d4 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -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__": diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ec513bca359..b3820811583 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -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 */ }; diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 1ce2f6a0edb..6563d838442 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -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 * diff --git a/Python/pytime.c b/Python/pytime.c index 2aeeddc9436..a4963357df1 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -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) {