Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4bfb460

Browse files
committed
Issue #22117: time.monotonic() now uses the new _PyTime_t API
* Add _PyTime_FromNanoseconds() * Add _PyTime_AsSecondsDouble() * Add unit tests for _PyTime_AsSecondsDouble()
1 parent 52d1493 commit 4bfb460

5 files changed

Lines changed: 121 additions & 15 deletions

File tree

Include/pytime.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,18 @@ typedef PY_INT64_T _PyTime_t;
119119
# error "_PyTime_t need signed 64-bit integer type"
120120
#endif
121121

122+
/* Create a timestamp from a number of nanoseconds (C long). */
123+
PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(PY_LONG_LONG ns);
124+
122125
/* Convert a Python float or int to a timetamp.
123126
Raise an exception and return -1 on error, return 0 on success. */
124127
PyAPI_FUNC(int) _PyTime_FromSecondsObject(_PyTime_t *t,
125128
PyObject *obj,
126129
_PyTime_round_t round);
127130

131+
/* Convert a timestamp to a number of seconds as a C double. */
132+
PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t);
133+
128134
/* Convert timestamp to a number of milliseconds (10^-3 seconds). */
129135
PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
130136
_PyTime_round_t round);
@@ -133,7 +139,8 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
133139
object. */
134140
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
135141

136-
/* Convert a timestamp to a timeval structure. */
142+
/* Convert a timestamp to a timeval structure (microsecond resolution).
143+
Raise an exception and return -1 on error, return 0 on success. */
137144
PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
138145
struct timeval *tv,
139146
_PyTime_round_t round);
@@ -147,6 +154,18 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
147154
is available and works. */
148155
PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
149156

157+
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
158+
The clock is not affected by system clock updates. The reference point of
159+
the returned value is undefined, so that only the difference between the
160+
results of consecutive calls is valid.
161+
162+
Fill info (if set) with information of the function used to get the time.
163+
164+
Return 0 on success, raise an exception and return -1 on error. */
165+
PyAPI_FUNC(int) _PyTime_GetMonotonicClockWithInfo(
166+
_PyTime_t *t,
167+
_Py_clock_info_t *info);
168+
150169

151170
#ifdef __cplusplus
152171
}

Lib/test/test_time.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
1717
TIME_MINYEAR = -TIME_MAXYEAR - 1
1818

19+
SEC_TO_NS = 10 ** 9
1920

2021
class _PyTime(enum.IntEnum):
2122
# Round towards zero
@@ -770,9 +771,7 @@ def test_short_times(self):
770771
@support.cpython_only
771772
class TestPyTime_t(unittest.TestCase):
772773
def test_FromSecondsObject(self):
773-
from _testcapi import pytime_fromsecondsobject
774-
SEC_TO_NS = 10 ** 9
775-
MAX_SEC = 2 ** 63 // 10 ** 9
774+
from _testcapi import PyTime_FromSecondsObject
776775

777776
# Conversion giving the same result for all rounding methods
778777
for rnd in ALL_ROUNDING_METHODS:
@@ -811,21 +810,21 @@ def test_FromSecondsObject(self):
811810
(2**25 , 33554432000000000),
812811
(2**25 + 1e-9, 33554432000000000),
813812

814-
# close to 2^63 nanoseconds
813+
# close to 2^63 nanoseconds (_PyTime_t limit)
815814
(9223372036, 9223372036 * SEC_TO_NS),
816815
(9223372036.0, 9223372036 * SEC_TO_NS),
817816
(-9223372036, -9223372036 * SEC_TO_NS),
818817
(-9223372036.0, -9223372036 * SEC_TO_NS),
819818
):
820819
with self.subTest(obj=obj, round=rnd, timestamp=ts):
821-
self.assertEqual(pytime_fromsecondsobject(obj, rnd), ts)
820+
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
822821

823822
with self.subTest(round=rnd):
824823
with self.assertRaises(OverflowError):
825-
pytime_fromsecondsobject(9223372037, rnd)
826-
pytime_fromsecondsobject(9223372037.0, rnd)
827-
pytime_fromsecondsobject(-9223372037, rnd)
828-
pytime_fromsecondsobject(-9223372037.0, rnd)
824+
PyTime_FromSecondsObject(9223372037, rnd)
825+
PyTime_FromSecondsObject(9223372037.0, rnd)
826+
PyTime_FromSecondsObject(-9223372037, rnd)
827+
PyTime_FromSecondsObject(-9223372037.0, rnd)
829828

830829
# Conversion giving different results depending on the rounding method
831830
UP = _PyTime.ROUND_UP
@@ -850,7 +849,52 @@ def test_FromSecondsObject(self):
850849
(-0.9999999999, -1000000000, UP),
851850
):
852851
with self.subTest(obj=obj, round=rnd, timestamp=ts):
853-
self.assertEqual(pytime_fromsecondsobject(obj, rnd), ts)
852+
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
853+
854+
def test_AsSecondsDouble(self):
855+
from _testcapi import PyTime_AsSecondsDouble
856+
857+
for nanoseconds, seconds in (
858+
# near 1 nanosecond
859+
( 0, 0.0),
860+
( 1, 1e-9),
861+
(-1, -1e-9),
862+
863+
# near 1 second
864+
(SEC_TO_NS + 1, 1.0 + 1e-9),
865+
(SEC_TO_NS, 1.0),
866+
(SEC_TO_NS - 1, 1.0 - 1e-9),
867+
868+
# a few seconds
869+
(123 * SEC_TO_NS, 123.0),
870+
(-567 * SEC_TO_NS, -567.0),
871+
872+
# nanosecond are kept for value <= 2^23 seconds
873+
(4194303999999999, 2**22 - 1e-9),
874+
(4194304000000000, 2**22),
875+
(4194304000000001, 2**22 + 1e-9),
876+
877+
# start loosing precision for value > 2^23 seconds
878+
(8388608000000002, 2**23 + 1e-9),
879+
880+
# nanoseconds are lost for value > 2^23 seconds
881+
(16777215999999998, 2**24 - 1e-9),
882+
(16777215999999999, 2**24 - 1e-9),
883+
(16777216000000000, 2**24 ),
884+
(16777216000000001, 2**24 ),
885+
(16777216000000002, 2**24 + 2e-9),
886+
887+
(33554432000000000, 2**25 ),
888+
(33554432000000002, 2**25 ),
889+
(33554432000000004, 2**25 + 4e-9),
890+
891+
# close to 2^63 nanoseconds (_PyTime_t limit)
892+
(9223372036 * SEC_TO_NS, 9223372036.0),
893+
(-9223372036 * SEC_TO_NS, -9223372036.0),
894+
):
895+
with self.subTest(nanoseconds=nanoseconds, seconds=seconds):
896+
self.assertEqual(PyTime_AsSecondsDouble(nanoseconds),
897+
seconds)
854898

855899

856900
if __name__ == "__main__":

Modules/_testcapimodule.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3394,6 +3394,20 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args)
33943394
return _PyTime_AsNanosecondsObject(ts);
33953395
}
33963396

3397+
static PyObject *
3398+
test_pytime_assecondsdouble(PyObject *self, PyObject *args)
3399+
{
3400+
PY_LONG_LONG ns;
3401+
_PyTime_t ts;
3402+
double d;
3403+
3404+
if (!PyArg_ParseTuple(args, "L", &ns))
3405+
return NULL;
3406+
ts = _PyTime_FromNanoseconds(ns);
3407+
d = _PyTime_AsSecondsDouble(ts);
3408+
return PyFloat_FromDouble(d);
3409+
}
3410+
33973411

33983412
static PyMethodDef TestMethods[] = {
33993413
{"raise_exception", raise_exception, METH_VARARGS},
@@ -3557,7 +3571,8 @@ static PyMethodDef TestMethods[] = {
35573571
return_null_without_error, METH_NOARGS},
35583572
{"return_result_with_error",
35593573
return_result_with_error, METH_NOARGS},
3560-
{"pytime_fromsecondsobject", test_pytime_fromsecondsobject, METH_VARARGS},
3574+
{"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS},
3575+
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
35613576
{NULL, NULL} /* sentinel */
35623577
};
35633578

Modules/timemodule.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,12 +887,14 @@ should not be relied on.");
887887
static PyObject *
888888
pymonotonic(_Py_clock_info_t *info)
889889
{
890-
_PyTime_timeval tv;
891-
if (_PyTime_monotonic_info(&tv, info) < 0) {
890+
_PyTime_t t;
891+
double d;
892+
if (_PyTime_GetMonotonicClockWithInfo(&t, info) < 0) {
892893
assert(info != NULL);
893894
return NULL;
894895
}
895-
return PyFloat_FromDouble((double)tv.tv_sec + tv.tv_usec * 1e-6);
896+
d = _PyTime_AsSecondsDouble(t);
897+
return PyFloat_FromDouble(d);
896898
}
897899

898900
static PyObject *

Python/pytime.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,15 @@ _PyTime_overflow(void)
405405
"timestamp too large to convert to C _PyTime_t");
406406
}
407407

408+
_PyTime_t
409+
_PyTime_FromNanoseconds(PY_LONG_LONG ns)
410+
{
411+
_PyTime_t t;
412+
assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t));
413+
t = Py_SAFE_DOWNCAST(ns, PY_LONG_LONG, _PyTime_t);
414+
return t;
415+
}
416+
408417
#if !defined(MS_WINDOWS) && !defined(__APPLE__)
409418
static int
410419
_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts)
@@ -470,6 +479,17 @@ _PyTime_FromSecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round)
470479
}
471480
}
472481

482+
double
483+
_PyTime_AsSecondsDouble(_PyTime_t t)
484+
{
485+
_PyTime_t sec, ns;
486+
/* Divide using integers to avoid rounding issues on the integer part.
487+
1e-9 cannot be stored exactly in IEEE 64-bit. */
488+
sec = t / SEC_TO_NS;
489+
ns = t % SEC_TO_NS;
490+
return (double)sec + (double)ns * 1e-9;
491+
}
492+
473493
PyObject *
474494
_PyTime_AsNanosecondsObject(_PyTime_t t)
475495
{
@@ -660,6 +680,12 @@ _PyTime_GetMonotonicClock(void)
660680
return t;
661681
}
662682

683+
int
684+
_PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
685+
{
686+
return pymonotonic_new(tp, info, 1);
687+
}
688+
663689
int
664690
_PyTime_Init(void)
665691
{

0 commit comments

Comments
 (0)