]> granicus.if.org Git - python/commitdiff
Issue #22117: time.monotonic() now uses the new _PyTime_t API
authorVictor Stinner <victor.stinner@gmail.com>
Fri, 27 Mar 2015 21:27:24 +0000 (22:27 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Fri, 27 Mar 2015 21:27:24 +0000 (22:27 +0100)
* Add _PyTime_FromNanoseconds()
* Add _PyTime_AsSecondsDouble()
* Add unit tests for _PyTime_AsSecondsDouble()

Include/pytime.h
Lib/test/test_time.py
Modules/_testcapimodule.c
Modules/timemodule.c
Python/pytime.c

index 78e4ae900feeab54287d12264f08f74c2216399a..9446b33967d294f578603484ad654b693d7518d2 100644 (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
 }
index 1bf0d09bd85d7553c47475145ad9b77459180fa9..817da8a01d42b005e77fd21a215a754d7d744825 100644 (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__":
index ec513bca35909220320b926033dcf4a63f3ae473..b3820811583e493f63fd7e302fede71a849ad36d 100644 (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 */
 };
 
index 1ce2f6a0edb76721459b8cb6c7f5ae829369e03f..6563d838442c9b761d913c56b0799a39852821cf 100644 (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 *
index 2aeeddc9436f253d3ab7990e01d2c0ff148d3d4f..a4963357df11ccdd14d2ad6db4450f70703325aa 100644 (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)
 {