summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/pytime.h21
-rw-r--r--Lib/test/test_time.py64
-rw-r--r--Modules/_testcapimodule.c17
-rw-r--r--Modules/timemodule.c8
-rw-r--r--Python/pytime.c26
5 files changed, 121 insertions, 15 deletions
diff --git a/Include/pytime.h b/Include/pytime.h
index 78e4ae9..9446b33 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 1bf0d09..817da8a 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 ec513bc..b382081 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 1ce2f6a..6563d83 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 2aeeddc..a496335 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)
{
@@ -661,6 +681,12 @@ _PyTime_GetMonotonicClock(void)
}
int
+_PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
+{
+ return pymonotonic_new(tp, info, 1);
+}
+
+int
_PyTime_Init(void)
{
_PyTime_timeval tv;