Skip to content

Commit

Permalink
Add PyTime API
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Feb 20, 2024
1 parent deb6f40 commit 0354c26
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 0 deletions.
30 changes: 30 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,36 @@ Python 3.13
See `Py_HashPointer() documentation <https://docs.python.org/dev/c-api/hash.html#c.Py_HashPointer>`__.
.. c:type:: PyTime_t
A timestamp or duration in nanoseconds, represented as a signed 64-bit
integer.
.. c:var:: PyTime_t PyTime_MIN
Minimum value of :c:type:`PyTime_t`.
.. c:var:: PyTime_t PyTime_MAX
Maximum value of :c:type:`PyTime_t`.
.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
See `PyTime_AsSecondsDouble() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_AsSecondsDouble>`__.
.. c:function:: int PyTime_Monotonic(PyTime_t *result)
See `PyTime_Monotonic() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_Monotonic>`__.
.. c:function:: int PyTime_Time(PyTime_t *result)
See `PyTime_Time() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_Time>`__.
.. c:function:: int PyTime_PerfCounter(PyTime_t *result)
See `PyTime_PerfCounter() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_PerfCounter>`__.
Not supported:
* ``PySys_Audit()``.
Expand Down
9 changes: 9 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
=========

* 2024-02-20: Add PyTime API:

* ``PyTime_t`` type
* ``PyTime_MIN`` and ``PyTime_MAX`` constants
* ``PyTime_AsSecondsDouble()``
* ``PyTime_Monotonic()``
* ``PyTime_PerfCounter()``
* ``PyTime_Time()``

* 2023-12-15: Add function ``Py_HashPointer()``.
* 2023-11-14: Add functions:

Expand Down
58 changes: 58 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,64 @@ static inline Py_hash_t Py_HashPointer(const void *ptr)
}
#endif


// Python 3.13a4 added a PyTime API.
// Use the private API added to Python 3.5.
#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000
typedef _PyTime_t PyTime_t;
#define PyTime_MIN _PyTime_MIN
#define PyTime_MAX _PyTime_MAX

static inline double PyTime_AsSecondsDouble(PyTime_t t)
{ return _PyTime_AsSecondsDouble(t); }

static inline int PyTime_Monotonic(PyTime_t *result)
{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }

static inline int PyTime_Time(PyTime_t *result)
{ return _PyTime_GetSystemClockWithInfo(result, NULL); }

static inline int PyTime_PerfCounter(PyTime_t *result)
{
#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)
return _PyTime_GetPerfCounterWithInfo(result, NULL);
#else
// Cache time.perf_counter() function for best performance
static PyObject *func = NULL;
if (func == NULL) {
// Call time.perf_counter() and convert C double to PyTime_t
PyObject *mod = PyImport_ImportModule("time");
if (mod == NULL) {
return -1;
}

func = PyObject_GetAttrString(mod, "perf_counter");
Py_DECREF(mod);
if (func == NULL) {
return -1;
}
}

PyObject *res = PyObject_CallNoArgs(func);
if (res == NULL) {
return -1;
}
double d = PyFloat_AsDouble(res);
Py_DECREF(res);

if (d == -1.0 && PyErr_Occurred()) {
return -1;
}

// Avoid floor() to avoid having to link to libm
*result = (PyTime_t)(d * 1e9);
return 0;
#endif
}

#endif


#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py")

PYTHONS = (
# CPython
"python3-debug",
"python3",
"python2.7",
Expand All @@ -43,6 +44,8 @@
"python3.11",
"python3.12",
"python3.13",

# PyPy
"pypy",
"pypy2",
"pypy2.7",
Expand Down
4 changes: 4 additions & 0 deletions tests/test_pythoncapi_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ def main():
global VERBOSE
VERBOSE = ("-v" in sys.argv[1:] or "--verbose" in sys.argv[1:])

if (3, 13) <= sys.version_info <= (3, 13, 0, 'alpha', 4):
print("SKIP Python 3.13 alpha 1..4: not supported!")
return

if faulthandler is not None:
faulthandler.enable()

Expand Down
36 changes: 36 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,39 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}


#if PY_VERSION_HEX >= 0x03050000
#define TEST_PYTIME

static PyObject *
test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
PyTime_t t;
#define UNINITIALIZED_TIME ((PyTime_t)-483884113929936179)

t = UNINITIALIZED_TIME;
assert(PyTime_Time(&t) == 0);
assert(t != UNINITIALIZED_TIME);

t = UNINITIALIZED_TIME;
assert(PyTime_Monotonic(&t) == 0);
assert(t != UNINITIALIZED_TIME);

// Test multiple times since an implementation uses a cache
for (int i=0; i < 5; i++) {
t = UNINITIALIZED_TIME;
assert(PyTime_PerfCounter(&t) == 0);
assert(t != UNINITIALIZED_TIME);
}

assert(PyTime_AsSecondsDouble(1) == 1e-9);
assert(PyTime_AsSecondsDouble(1500 * 1000 * 1000) == 1.5);
assert(PyTime_AsSecondsDouble(-500 * 1000 * 1000) == -0.5);

Py_RETURN_NONE;
}
#endif


static struct PyMethodDef methods[] = {
{"test_object", test_object, METH_NOARGS, _Py_NULL},
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
Expand Down Expand Up @@ -1559,6 +1592,9 @@ static struct PyMethodDef methods[] = {
{"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
{"test_list", test_list, METH_NOARGS, _Py_NULL},
{"test_hash", test_hash, METH_NOARGS, _Py_NULL},
#ifdef TEST_PYTIME
{"test_time", test_time, METH_NOARGS, _Py_NULL},
#endif
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
};

Expand Down

0 comments on commit 0354c26

Please sign in to comment.