Skip to content

Commit 52486a9

Browse files
authored
Add PyTime API (python#84)
1 parent deb6f40 commit 52486a9

File tree

6 files changed

+171
-0
lines changed

6 files changed

+171
-0
lines changed

docs/api.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,36 @@ Python 3.13
141141
142142
See `Py_HashPointer() documentation <https://docs.python.org/dev/c-api/hash.html#c.Py_HashPointer>`__.
143143
144+
.. c:type:: PyTime_t
145+
146+
A timestamp or duration in nanoseconds, represented as a signed 64-bit
147+
integer.
148+
149+
.. c:var:: PyTime_t PyTime_MIN
150+
151+
Minimum value of :c:type:`PyTime_t`.
152+
153+
.. c:var:: PyTime_t PyTime_MAX
154+
155+
Maximum value of :c:type:`PyTime_t`.
156+
157+
.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
158+
159+
See `PyTime_AsSecondsDouble() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_AsSecondsDouble>`__.
160+
161+
.. c:function:: int PyTime_Monotonic(PyTime_t *result)
162+
163+
See `PyTime_Monotonic() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_Monotonic>`__.
164+
165+
.. c:function:: int PyTime_Time(PyTime_t *result)
166+
167+
See `PyTime_Time() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_Time>`__.
168+
169+
.. c:function:: int PyTime_PerfCounter(PyTime_t *result)
170+
171+
See `PyTime_PerfCounter() documentation <https://docs.python.org/dev/c-api/time.html#c.PyTime_PerfCounter>`__.
172+
173+
144174
Not supported:
145175
146176
* ``PySys_Audit()``.

docs/changelog.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changelog
22
=========
33

4+
* 2024-02-20: Add PyTime API:
5+
6+
* ``PyTime_t`` type
7+
* ``PyTime_MIN`` and ``PyTime_MAX`` constants
8+
* ``PyTime_AsSecondsDouble()``
9+
* ``PyTime_Monotonic()``
10+
* ``PyTime_PerfCounter()``
11+
* ``PyTime_Time()``
12+
413
* 2023-12-15: Add function ``Py_HashPointer()``.
514
* 2023-11-14: Add functions:
615

pythoncapi_compat.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,95 @@ static inline Py_hash_t Py_HashPointer(const void *ptr)
11081108
}
11091109
#endif
11101110

1111+
1112+
// Python 3.13a4 added a PyTime API.
1113+
// Use the private API added to Python 3.5.
1114+
#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000
1115+
typedef _PyTime_t PyTime_t;
1116+
#define PyTime_MIN _PyTime_MIN
1117+
#define PyTime_MAX _PyTime_MAX
1118+
1119+
static inline double PyTime_AsSecondsDouble(PyTime_t t)
1120+
{ return _PyTime_AsSecondsDouble(t); }
1121+
1122+
static inline int PyTime_Monotonic(PyTime_t *result)
1123+
{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }
1124+
1125+
static inline int PyTime_Time(PyTime_t *result)
1126+
{ return _PyTime_GetSystemClockWithInfo(result, NULL); }
1127+
1128+
static inline int PyTime_PerfCounter(PyTime_t *result)
1129+
{
1130+
#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)
1131+
return _PyTime_GetPerfCounterWithInfo(result, NULL);
1132+
#elif PY_VERSION_HEX >= 0x03070000
1133+
// Call time.perf_counter_ns() and convert Python int object to PyTime_t.
1134+
// Cache time.perf_counter_ns() function for best performance.
1135+
static PyObject *func = NULL;
1136+
if (func == NULL) {
1137+
PyObject *mod = PyImport_ImportModule("time");
1138+
if (mod == NULL) {
1139+
return -1;
1140+
}
1141+
1142+
func = PyObject_GetAttrString(mod, "perf_counter_ns");
1143+
Py_DECREF(mod);
1144+
if (func == NULL) {
1145+
return -1;
1146+
}
1147+
}
1148+
1149+
PyObject *res = PyObject_CallNoArgs(func);
1150+
if (res == NULL) {
1151+
return -1;
1152+
}
1153+
long long value = PyLong_AsLongLong(res);
1154+
Py_DECREF(res);
1155+
1156+
if (value == -1 && PyErr_Occurred()) {
1157+
return -1;
1158+
}
1159+
1160+
Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t));
1161+
*result = (PyTime_t)value;
1162+
return 0;
1163+
#else
1164+
// Call time.perf_counter() and convert C double to PyTime_t.
1165+
// Cache time.perf_counter() function for best performance.
1166+
static PyObject *func = NULL;
1167+
if (func == NULL) {
1168+
PyObject *mod = PyImport_ImportModule("time");
1169+
if (mod == NULL) {
1170+
return -1;
1171+
}
1172+
1173+
func = PyObject_GetAttrString(mod, "perf_counter");
1174+
Py_DECREF(mod);
1175+
if (func == NULL) {
1176+
return -1;
1177+
}
1178+
}
1179+
1180+
PyObject *res = PyObject_CallNoArgs(func);
1181+
if (res == NULL) {
1182+
return -1;
1183+
}
1184+
double d = PyFloat_AsDouble(res);
1185+
Py_DECREF(res);
1186+
1187+
if (d == -1.0 && PyErr_Occurred()) {
1188+
return -1;
1189+
}
1190+
1191+
// Avoid floor() to avoid having to link to libm
1192+
*result = (PyTime_t)(d * 1e9);
1193+
return 0;
1194+
#endif
1195+
}
1196+
1197+
#endif
1198+
1199+
11111200
#ifdef __cplusplus
11121201
}
11131202
#endif

runtests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py")
3131

3232
PYTHONS = (
33+
# CPython
3334
"python3-debug",
3435
"python3",
3536
"python2.7",
@@ -43,6 +44,8 @@
4344
"python3.11",
4445
"python3.12",
4546
"python3.13",
47+
48+
# PyPy
4649
"pypy",
4750
"pypy2",
4851
"pypy2.7",

tests/test_pythoncapi_compat.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ def main():
188188
global VERBOSE
189189
VERBOSE = ("-v" in sys.argv[1:] or "--verbose" in sys.argv[1:])
190190

191+
if (3, 13) <= sys.version_info <= (3, 13, 0, 'alpha', 4):
192+
print("SKIP Python 3.13 alpha 1..4: not supported!")
193+
return
194+
191195
if faulthandler is not None:
192196
faulthandler.enable()
193197

tests/test_pythoncapi_compat_cext.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,39 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
15261526
}
15271527

15281528

1529+
#if PY_VERSION_HEX >= 0x03050000
1530+
#define TEST_PYTIME
1531+
1532+
static PyObject *
1533+
test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
1534+
{
1535+
PyTime_t t;
1536+
#define UNINITIALIZED_TIME ((PyTime_t)-483884113929936179)
1537+
1538+
t = UNINITIALIZED_TIME;
1539+
assert(PyTime_Time(&t) == 0);
1540+
assert(t != UNINITIALIZED_TIME);
1541+
1542+
t = UNINITIALIZED_TIME;
1543+
assert(PyTime_Monotonic(&t) == 0);
1544+
assert(t != UNINITIALIZED_TIME);
1545+
1546+
// Test multiple times since an implementation uses a cache
1547+
for (int i=0; i < 5; i++) {
1548+
t = UNINITIALIZED_TIME;
1549+
assert(PyTime_PerfCounter(&t) == 0);
1550+
assert(t != UNINITIALIZED_TIME);
1551+
}
1552+
1553+
assert(PyTime_AsSecondsDouble(1) == 1e-9);
1554+
assert(PyTime_AsSecondsDouble(1500 * 1000 * 1000) == 1.5);
1555+
assert(PyTime_AsSecondsDouble(-500 * 1000 * 1000) == -0.5);
1556+
1557+
Py_RETURN_NONE;
1558+
}
1559+
#endif
1560+
1561+
15291562
static struct PyMethodDef methods[] = {
15301563
{"test_object", test_object, METH_NOARGS, _Py_NULL},
15311564
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -1559,6 +1592,9 @@ static struct PyMethodDef methods[] = {
15591592
{"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
15601593
{"test_list", test_list, METH_NOARGS, _Py_NULL},
15611594
{"test_hash", test_hash, METH_NOARGS, _Py_NULL},
1595+
#ifdef TEST_PYTIME
1596+
{"test_time", test_time, METH_NOARGS, _Py_NULL},
1597+
#endif
15621598
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
15631599
};
15641600

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy