]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110850: Add PyTime_t C API (GH-115215)
authorPetr Viktorin <encukou@gmail.com>
Mon, 12 Feb 2024 17:13:10 +0000 (18:13 +0100)
committerGitHub <noreply@github.com>
Mon, 12 Feb 2024 17:13:10 +0000 (18:13 +0100)
* gh-110850: Add PyTime_t C API

Add PyTime_t API:

* PyTime_t type.
* PyTime_MIN and PyTime_MAX constants.
* PyTime_AsSecondsDouble(), PyTime_Monotonic(),
  PyTime_PerfCounter() and PyTime_GetSystemClock() functions.

Co-authored-by: Victor Stinner <vstinner@python.org>
19 files changed:
Doc/c-api/time.rst [new file with mode: 0644]
Doc/c-api/utilities.rst
Doc/conf.py
Doc/whatsnew/3.13.rst
Include/Python.h
Include/cpython/pytime.h [new file with mode: 0644]
Include/internal/pycore_time.h
Lib/test/test_capi/test_time.py [new file with mode: 0644]
Lib/test/test_time.py
Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst [new file with mode: 0644]
Modules/Setup.stdlib.in
Modules/_randommodule.c
Modules/_testcapi/parts.h
Modules/_testcapi/time.c [new file with mode: 0644]
Modules/_testcapimodule.c
Modules/_testinternalcapi/pytime.c
PCbuild/_testcapi.vcxproj
PCbuild/_testcapi.vcxproj.filters
Python/pytime.c

diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst
new file mode 100644 (file)
index 0000000..7791cdb
--- /dev/null
@@ -0,0 +1,83 @@
+.. highlight:: c
+
+PyTime C API
+============
+
+.. versionadded:: 3.13
+
+The clock C API provides access to system clocks.
+It is similar to the Python :mod:`time` module.
+
+For C API related to the :mod:`datetime` module, see :ref:`datetimeobjects`.
+
+
+Types
+-----
+
+.. c:type:: PyTime_t
+
+   A timestamp or duration in nanoseconds, represented as a signed 64-bit
+   integer.
+
+   The reference point for timestamps depends on the clock used. For example,
+   :c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch.
+
+   The supported range is around [-292.3 years; +292.3 years].
+   Using the Unix epoch (January 1st, 1970) as reference, the supported date
+   range is around [1677-09-21; 2262-04-11].
+   The exact limits are exposed as constants:
+
+.. 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`.
+
+
+Clock Functions
+---------------
+
+The following functions take a pointer to a :c:expr:`PyTime_t` that they
+set to the value of a particular clock.
+Details of each clock are given in the documentation of the corresponding
+Python function.
+
+The functions return ``0`` on success, or ``-1`` (with an exception set)
+on failure.
+
+On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and
+set ``*result`` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]``
+range.
+(On current systems, integer overflows are likely caused by misconfigured
+system time.)
+
+As any other C API (unless otherwise specified), the functions must be called
+with the :term:`GIL` held.
+
+.. c:function:: int PyTime_Monotonic(PyTime_t *result)
+
+   Read the monotonic clock.
+   See :func:`time.monotonic` for important details on this clock.
+
+.. c:function:: int PyTime_PerfCounter(PyTime_t *result)
+
+   Read the performance counter.
+   See :func:`time.perf_counter` for important details on this clock.
+
+.. c:function:: int PyTime_Time(PyTime_t *result)
+
+   Read the “wall clock” time.
+   See :func:`time.time` for details important on this clock.
+
+
+Conversion functions
+--------------------
+
+.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
+
+   Convert a timestamp to a number of seconds as a C :c:expr:`double`.
+
+   The function cannot fail, but note that :c:expr:`double` has limited
+   accuracy for large values.
index 48ae54acebe887930d24ef9be7d821c6a880350b..9d0abf440f791d40005acb076be8fb4ffb1cab63 100644 (file)
@@ -20,4 +20,5 @@ and parsing function arguments and constructing Python values from C values.
    hash.rst
    reflection.rst
    codec.rst
+   time.rst
    perfmaps.rst
index c2d57696aeeaa37c0110e9311caccc49477cd571..aa7f85bc1b3efa85381e4e37c1a315138247848f 100644 (file)
@@ -135,11 +135,14 @@ nitpick_ignore = [
     ('c:type', 'wchar_t'),
     ('c:type', '__int64'),
     ('c:type', 'unsigned __int64'),
+    ('c:type', 'double'),
     # Standard C structures
     ('c:struct', 'in6_addr'),
     ('c:struct', 'in_addr'),
     ('c:struct', 'stat'),
     ('c:struct', 'statvfs'),
+    ('c:struct', 'timeval'),
+    ('c:struct', 'timespec'),
     # Standard C macros
     ('c:macro', 'LLONG_MAX'),
     ('c:macro', 'LLONG_MIN'),
@@ -269,12 +272,12 @@ nitpick_ignore += [
     ('py:meth', 'index'),  # list.index, tuple.index, etc.
 ]
 
-# gh-106948: Copy standard C types declared in the "c:type" domain to the
-# "c:identifier" domain, since "c:function" markup looks for types in the
-# "c:identifier" domain. Use list() to not iterate on items which are being
-# added
+# gh-106948: Copy standard C types declared in the "c:type" domain and C
+# structures declared in the "c:struct" domain to the "c:identifier" domain,
+# since "c:function" markup looks for types in the "c:identifier" domain. Use
+# list() to not iterate on items which are being added
 for role, name in list(nitpick_ignore):
-    if role == 'c:type':
+    if role in ('c:type', 'c:struct'):
         nitpick_ignore.append(('c:identifier', name))
 del role, name
 
index 1b803278ae0d5bdf04460684f85a8cf384d3664c..191657061f74033fdd356024d0ac49bb572c7eb2 100644 (file)
@@ -1516,6 +1516,16 @@ New Features
 * Add :c:func:`Py_HashPointer` function to hash a pointer.
   (Contributed by Victor Stinner in :gh:`111545`.)
 
+* Add PyTime C API:
+
+  * :c:type:`PyTime_t` type.
+  * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
+  * :c:func:`PyTime_AsSecondsDouble`
+    :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and
+    :c:func:`PyTime_Time` functions.
+
+  (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.)
+
 
 Porting to Python 3.13
 ----------------------
index 196751c3201e620aedfbb6cfb62cadc4066fcb4d..01fc45137a17bbced8188f12d8dca24986002f44 100644 (file)
@@ -97,6 +97,7 @@
 #include "weakrefobject.h"
 #include "structseq.h"
 #include "cpython/picklebufobject.h"
+#include "cpython/pytime.h"
 #include "codecs.h"
 #include "pyerrors.h"
 #include "pythread.h"
diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h
new file mode 100644 (file)
index 0000000..d824470
--- /dev/null
@@ -0,0 +1,23 @@
+// PyTime_t C API: see Doc/c-api/time.rst for the documentation.
+
+#ifndef Py_LIMITED_API
+#ifndef Py_PYTIME_H
+#define Py_PYTIME_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int64_t PyTime_t;
+#define PyTime_MIN INT64_MIN
+#define PyTime_MAX INT64_MAX
+
+PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t);
+PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result);
+PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result);
+PyAPI_FUNC(int) PyTime_Time(PyTime_t *result);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* Py_PYTIME_H */
+#endif /* Py_LIMITED_API */
index dabbd7b41556cdfcf69e743f0a00c0c91e8872bb..1aad6ccea69ae35418d175a8a16b1f6ad6fd1a2f 100644 (file)
@@ -1,34 +1,39 @@
-// The _PyTime_t API is written to use timestamp and timeout values stored in
-// various formats and to read clocks.
+// Internal PyTime_t C API: see Doc/c-api/time.rst for the documentation.
 //
-// The _PyTime_t type is an integer to support directly common arithmetic
-// operations like t1 + t2.
+// The PyTime_t type is an integer to support directly common arithmetic
+// operations such as t1 + t2.
 //
-// The _PyTime_t API supports a resolution of 1 nanosecond. The _PyTime_t type
-// is signed to support negative timestamps. The supported range is around
-// [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970), the
-// supported date range is around [1677-09-21; 2262-04-11].
+// Time formats:
 //
-// Formats:
+// * Seconds.
+// * Seconds as a floating point number (C double).
+// * Milliseconds (10^-3 seconds).
+// * Microseconds (10^-6 seconds).
+// * 100 nanoseconds (10^-7 seconds), used on Windows.
+// * Nanoseconds (10^-9 seconds).
+// * timeval structure, 1 microsecond (10^-6 seconds).
+// * timespec structure, 1 nanosecond (10^-9 seconds).
 //
-// * seconds
-// * seconds as a floating pointer number (C double)
-// * milliseconds (10^-3 seconds)
-// * microseconds (10^-6 seconds)
-// * 100 nanoseconds (10^-7 seconds)
-// * nanoseconds (10^-9 seconds)
-// * timeval structure, 1 microsecond resolution (10^-6 seconds)
-// * timespec structure, 1 nanosecond resolution (10^-9 seconds)
+// Note that PyTime_t is now specified as int64_t, in nanoseconds.
+// (If we need to change this, we'll need new public API with new names.)
+// Previously, PyTime_t was configurable (in theory); some comments and code
+// might still allude to that.
 //
 // Integer overflows are detected and raise OverflowError. Conversion to a
-// resolution worse than 1 nanosecond is rounded correctly with the requested
-// rounding mode. There are 4 rounding modes: floor (towards -inf), ceiling
-// (towards +inf), half even and up (away from zero).
+// resolution larger than 1 nanosecond is rounded correctly with the requested
+// rounding mode. Available rounding modes:
 //
-// Some functions clamp the result in the range [_PyTime_MIN; _PyTime_MAX], so
-// the caller doesn't have to handle errors and doesn't need to hold the GIL.
-// For example, _PyTime_Add(t1, t2) computes t1+t2 and clamp the result on
-// overflow.
+// * Round towards minus infinity (-inf). For example, used to read a clock.
+// * Round towards infinity (+inf). For example, used for timeout to wait "at
+//   least" N seconds.
+// * Round to nearest with ties going to nearest even integer. For example, used
+//   to round from a Python float.
+// * Round away from zero. For example, used for timeout.
+//
+// Some functions clamp the result in the range [PyTime_MIN; PyTime_MAX]. The
+// caller doesn't have to handle errors and so doesn't need to hold the GIL to
+// handle exceptions. For example, _PyTime_Add(t1, t2) computes t1+t2 and
+// clamps the result on overflow.
 //
 // Clocks:
 //
 // * Monotonic clock
 // * Performance counter
 //
-// Operations like (t * k / q) with integers are implemented in a way to reduce
-// the risk of integer overflow. Such operation is used to convert a clock
-// value expressed in ticks with a frequency to _PyTime_t, like
-// QueryPerformanceCounter() with QueryPerformanceFrequency().
+// Internally, operations like (t * k / q) with integers are implemented in a
+// way to reduce the risk of integer overflow. Such operation is used to convert a
+// clock value expressed in ticks with a frequency to PyTime_t, like
+// QueryPerformanceCounter() with QueryPerformanceFrequency() on Windows.
+
 
 #ifndef Py_INTERNAL_TIME_H
 #define Py_INTERNAL_TIME_H
@@ -56,14 +62,7 @@ extern "C" {
 struct timeval;
 #endif
 
-// _PyTime_t: Python timestamp with subsecond precision. It can be used to
-// store a duration, and so indirectly a date (related to another date, like
-// UNIX epoch).
-typedef int64_t _PyTime_t;
-// _PyTime_MIN nanoseconds is around -292.3 years
-#define _PyTime_MIN INT64_MIN
-// _PyTime_MAX nanoseconds is around +292.3 years
-#define _PyTime_MAX INT64_MAX
+typedef PyTime_t _PyTime_t;
 #define _SIZEOF_PYTIME_T 8
 
 typedef enum {
@@ -147,7 +146,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t
 PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns);
 
 // Create a timestamp from a number of microseconds.
-// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
+// Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
 extern _PyTime_t _PyTime_FromMicrosecondsClamp(_PyTime_t us);
 
 // Create a timestamp from nanoseconds (Python int).
@@ -169,10 +168,6 @@ PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(_PyTime_t *t,
     PyObject *obj,
     _PyTime_round_t round);
 
-// Convert a timestamp to a number of seconds as a C double.
-// Export for '_socket' shared extension.
-PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t);
-
 // Convert timestamp to a number of milliseconds (10^-3 seconds).
 // Export for '_ssl' shared extension.
 PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
@@ -183,9 +178,6 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
 PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
     _PyTime_round_t round);
 
-// Convert timestamp to a number of nanoseconds (10^-9 seconds).
-extern _PyTime_t _PyTime_AsNanoseconds(_PyTime_t t);
-
 #ifdef MS_WINDOWS
 // Convert timestamp to a number of 100 nanoseconds (10^-7 seconds).
 extern _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t,
@@ -250,7 +242,7 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts);
 #endif
 
 
-// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
+// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
 extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2);
 
 // Structure used by time.get_clock_info()
@@ -267,7 +259,8 @@ typedef struct {
 // On integer overflow, silently ignore the overflow and clamp the clock to
 // [_PyTime_MIN; _PyTime_MAX].
 //
-// Use _PyTime_GetSystemClockWithInfo() to check for failure.
+// Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check
+// for failure.
 // Export for '_random' shared extension.
 PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void);
 
@@ -287,7 +280,8 @@ extern int _PyTime_GetSystemClockWithInfo(
 // On integer overflow, silently ignore the overflow and clamp the clock to
 // [_PyTime_MIN; _PyTime_MAX].
 //
-// Use _PyTime_GetMonotonicClockWithInfo() to check for failure.
+// Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic()
+// to check for failure.
 // Export for '_random' shared extension.
 PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
 
@@ -322,10 +316,12 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);
 // On integer overflow, silently ignore the overflow and clamp the clock to
 // [_PyTime_MIN; _PyTime_MAX].
 //
-// Use _PyTime_GetPerfCounterWithInfo() to check for failure.
+// Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter
+// to check for failure.
 // Export for '_lsprof' shared extension.
 PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void);
 
+
 // Get the performance counter: clock with the highest available resolution to
 // measure a short duration.
 //
@@ -336,6 +332,13 @@ extern int _PyTime_GetPerfCounterWithInfo(
     _PyTime_t *t,
     _Py_clock_info_t *info);
 
+// Alias for backward compatibility
+#define _PyTime_MIN PyTime_MIN
+#define _PyTime_MAX PyTime_MAX
+#define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble
+
+
+// --- _PyDeadline -----------------------------------------------------------
 
 // Create a deadline.
 // Pseudo code: _PyTime_GetMonotonicClock() + timeout.
diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py
new file mode 100644 (file)
index 0000000..10b7fbf
--- /dev/null
@@ -0,0 +1,71 @@
+import time
+import unittest
+from test.support import import_helper
+_testcapi = import_helper.import_module('_testcapi')
+
+
+PyTime_MIN = _testcapi.PyTime_MIN
+PyTime_MAX = _testcapi.PyTime_MAX
+SEC_TO_NS = 10 ** 9
+DAY_TO_SEC = (24 * 60 * 60)
+# Worst clock resolution: maximum delta between two clock reads.
+CLOCK_RES = 0.050
+
+
+class CAPITest(unittest.TestCase):
+    def test_min_max(self):
+        # PyTime_t is just int64_t
+        self.assertEqual(PyTime_MIN, -2**63)
+        self.assertEqual(PyTime_MAX, 2**63 - 1)
+
+    def check_clock(self, c_func, py_func):
+        t1 = c_func()
+        t2 = py_func()
+        self.assertAlmostEqual(t1, t2, delta=CLOCK_RES)
+
+    def test_assecondsdouble(self):
+        # Test PyTime_AsSecondsDouble()
+        def ns_to_sec(ns):
+            if abs(ns) % SEC_TO_NS == 0:
+                return float(ns // SEC_TO_NS)
+            else:
+                return float(ns) / SEC_TO_NS
+
+        seconds = (
+            0,
+            1,
+            DAY_TO_SEC,
+            365 * DAY_TO_SEC,
+        )
+        values = {
+            PyTime_MIN,
+            PyTime_MIN + 1,
+            PyTime_MAX - 1,
+            PyTime_MAX,
+        }
+        for second in seconds:
+            ns = second * SEC_TO_NS
+            values.add(ns)
+            # test nanosecond before/after to test rounding
+            values.add(ns - 1)
+            values.add(ns + 1)
+        for ns in list(values):
+            if (-ns) > PyTime_MAX:
+                continue
+            values.add(-ns)
+        for ns in sorted(values):
+            with self.subTest(ns=ns):
+                self.assertEqual(_testcapi.PyTime_AsSecondsDouble(ns),
+                                 ns_to_sec(ns))
+
+    def test_monotonic(self):
+        # Test PyTime_Monotonic()
+        self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic)
+
+    def test_perf_counter(self):
+        # Test PyTime_PerfCounter()
+        self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter)
+
+    def test_time(self):
+        # Test PyTime_time()
+        self.check_clock(_testcapi.PyTime_Time, time.time)
index 3b5640abdb6b89211d03ef6a0067589106f15d79..a0aeea515afbd69531f8c3bd8bbfd8ab922e2ffe 100644 (file)
@@ -43,8 +43,8 @@ class _PyTime(enum.IntEnum):
     ROUND_UP = 3
 
 # _PyTime_t is int64_t
-_PyTime_MIN = -2 ** 63
-_PyTime_MAX = 2 ** 63 - 1
+PyTime_MIN = -2 ** 63
+PyTime_MAX = 2 ** 63 - 1
 
 # Rounding modes supported by PyTime
 ROUNDING_MODES = (
@@ -934,7 +934,7 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase):
                 _PyTime_FromSecondsObject(float('nan'), time_rnd)
 
     def test_AsSecondsDouble(self):
-        from _testinternalcapi import _PyTime_AsSecondsDouble
+        from _testcapi import PyTime_AsSecondsDouble
 
         def float_converter(ns):
             if abs(ns) % SEC_TO_NS == 0:
@@ -942,15 +942,10 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase):
             else:
                 return float(ns) / SEC_TO_NS
 
-        self.check_int_rounding(lambda ns, rnd: _PyTime_AsSecondsDouble(ns),
+        self.check_int_rounding(lambda ns, rnd: PyTime_AsSecondsDouble(ns),
                                 float_converter,
                                 NS_TO_SEC)
 
-        # test nan
-        for time_rnd, _ in ROUNDING_MODES:
-            with self.assertRaises(TypeError):
-                _PyTime_AsSecondsDouble(float('nan'))
-
     def create_decimal_converter(self, denominator):
         denom = decimal.Decimal(denominator)
 
@@ -1009,7 +1004,7 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase):
             tv_sec_max = self.time_t_max
             tv_sec_min = self.time_t_min
 
-        for t in (_PyTime_MIN, _PyTime_MAX):
+        for t in (PyTime_MIN, PyTime_MAX):
             ts = _PyTime_AsTimeval_clamp(t, _PyTime.ROUND_CEILING)
             with decimal.localcontext() as context:
                 context.rounding = decimal.ROUND_CEILING
@@ -1028,7 +1023,7 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase):
     def test_AsTimespec_clamp(self):
         from _testinternalcapi import _PyTime_AsTimespec_clamp
 
-        for t in (_PyTime_MIN, _PyTime_MAX):
+        for t in (PyTime_MIN, PyTime_MAX):
             ts = _PyTime_AsTimespec_clamp(t)
             tv_sec, tv_nsec = divmod(t, NS_TO_SEC)
             if self.time_t_max < tv_sec:
diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst
new file mode 100644 (file)
index 0000000..998d442
--- /dev/null
@@ -0,0 +1,9 @@
+Add PyTime C API:
+
+* :c:type:`PyTime_t` type.
+* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
+* :c:func:`PyTime_AsSecondsDouble`,
+  :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and
+  :c:func:`PyTime_Time` functions.
+
+Patch by Victor Stinner.
index 8a65a9cffb1b9d87dbf15bcb617d96cd55e1346b..e98775a480876533539389dc26969db570cae4a6 100644 (file)
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
index 4403e1d132c0570918281dac8bdd11d5f43b9489..5481ed9b348ed7754a48d04218aee3f847b0ffb2 100644 (file)
@@ -262,7 +262,7 @@ random_seed_urandom(RandomObject *self)
 static void
 random_seed_time_pid(RandomObject *self)
 {
-    _PyTime_t now;
+    PyTime_t now;
     uint32_t key[5];
 
     now = _PyTime_GetSystemClock();
index 29817edd69b134876ddc250e7444e8c196e66ce4..e8cfb2423500d4b27a370337d1d8b6fc667640e4 100644 (file)
@@ -59,6 +59,7 @@ int _PyTestCapi_Init_Immortal(PyObject *module);
 int _PyTestCapi_Init_GC(PyObject *module);
 int _PyTestCapi_Init_Sys(PyObject *module);
 int _PyTestCapi_Init_Hash(PyObject *module);
+int _PyTestCapi_Init_Time(PyObject *module);
 
 int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
 int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c
new file mode 100644 (file)
index 0000000..4fbf7dd
--- /dev/null
@@ -0,0 +1,105 @@
+#include "parts.h"
+
+
+static int
+pytime_from_nanoseconds(PyTime_t *tp, PyObject *obj)
+{
+    if (!PyLong_Check(obj)) {
+        PyErr_Format(PyExc_TypeError, "expect int, got %s",
+                     Py_TYPE(obj)->tp_name);
+        return -1;
+    }
+
+    long long nsec = PyLong_AsLongLong(obj);
+    if (nsec == -1 && PyErr_Occurred()) {
+        return -1;
+    }
+
+    Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t));
+    *tp = (PyTime_t)nsec;
+    return 0;
+}
+
+
+static PyObject *
+test_pytime_assecondsdouble(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *obj;
+    if (!PyArg_ParseTuple(args, "O", &obj)) {
+        return NULL;
+    }
+    PyTime_t ts;
+    if (pytime_from_nanoseconds(&ts, obj) < 0) {
+        return NULL;
+    }
+    double d = PyTime_AsSecondsDouble(ts);
+    return PyFloat_FromDouble(d);
+}
+
+
+static PyObject*
+pytime_as_float(PyTime_t t)
+{
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t));
+}
+
+
+
+static PyObject*
+test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+    PyTime_t t;
+    if (PyTime_Monotonic(&t) < 0) {
+        return NULL;
+    }
+    return pytime_as_float(t);
+}
+
+
+static PyObject*
+test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+    PyTime_t t;
+    if (PyTime_PerfCounter(&t) < 0) {
+        return NULL;
+    }
+    return pytime_as_float(t);
+}
+
+
+static PyObject*
+test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+    PyTime_t t;
+    if (PyTime_Time(&t) < 0) {
+        printf("ERR! %d\n", (int)t);
+        return NULL;
+    }
+    printf("... %d\n", (int)t);
+    return pytime_as_float(t);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
+    {"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS},
+    {"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS},
+    {"PyTime_Time", test_pytime_time, METH_NOARGS},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Time(PyObject *m)
+{
+    if (PyModule_AddFunctions(m, test_methods) < 0) {
+        return -1;
+    }
+    Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t));
+    if (PyModule_AddObject(m, "PyTime_MIN", PyLong_FromLongLong(PyTime_MIN)) < 0) {
+        return 1;
+    }
+    if (PyModule_AddObject(m, "PyTime_MAX", PyLong_FromLongLong(PyTime_MAX)) < 0) {
+        return 1;
+    }
+    return 0;
+}
index e67de3eeb6e17eb90cf730e1b47f6399a307d5c2..b03f871b089c8a39b03c8ad7500d393f3d3839d3 100644 (file)
@@ -4107,6 +4107,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Hash(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Time(m) < 0) {
+        return NULL;
+    }
 
     PyState_AddModule(m, &_testcapimodule);
     return m;
index 2b5f9eb0ef285117b9aee09ca97adab9e02dea9e..f0f758ea032df82cd405dcae5d40c40ef5dceb7b 100644 (file)
@@ -52,21 +52,6 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args)
     return _PyTime_AsNanosecondsObject(ts);
 }
 
-static PyObject *
-test_pytime_assecondsdouble(PyObject *self, PyObject *args)
-{
-    PyObject *obj;
-    if (!PyArg_ParseTuple(args, "O", &obj)) {
-        return NULL;
-    }
-    _PyTime_t ts;
-    if (_PyTime_FromNanosecondsObject(&ts, obj) < 0) {
-        return NULL;
-    }
-    double d = _PyTime_AsSecondsDouble(ts);
-    return PyFloat_FromDouble(d);
-}
-
 static PyObject *
 test_PyTime_AsTimeval(PyObject *self, PyObject *args)
 {
@@ -254,7 +239,6 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args)
 static PyMethodDef TestMethods[] = {
     {"_PyTime_AsMicroseconds",    test_PyTime_AsMicroseconds,     METH_VARARGS},
     {"_PyTime_AsMilliseconds",    test_PyTime_AsMilliseconds,     METH_VARARGS},
-    {"_PyTime_AsSecondsDouble",   test_pytime_assecondsdouble,    METH_VARARGS},
 #ifdef HAVE_CLOCK_GETTIME
     {"_PyTime_AsTimespec",        test_PyTime_AsTimespec,         METH_VARARGS},
     {"_PyTime_AsTimespec_clamp",  test_PyTime_AsTimespec_clamp,   METH_VARARGS},
index 6911aacab29b9793ca86e967ef8c0a25536e5577..66df0a61b5b5a66c7a7623a53064079babbf4b42 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\codec.c" />
     <ClCompile Include="..\Modules\_testcapi\sys.c" />
     <ClCompile Include="..\Modules\_testcapi\hash.c" />
+    <ClCompile Include="..\Modules\_testcapi\time.c" />
     <ClCompile Include="..\Modules\_testcapi\immortal.c" />
     <ClCompile Include="..\Modules\_testcapi\gc.c" />
   </ItemGroup>
index 6059959bb9a040f393f3e07d8736cd42434082bb..651eb1d6ba0b7f3ffa2ecb45da6c8003349df3d0 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\hash.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\time.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\_testcapi\gc.c">
       <Filter>Source Files</Filter>
     </ClCompile>
index 77cb95f8feb179f07e361eb84614388bdcca1ce4..fb0ed85c541e68ca6f1f4ba9dde8267af35081fe 100644 (file)
@@ -50,7 +50,7 @@
 #  error "time_t is not a two's complement integer type"
 #endif
 
-#if _PyTime_MIN + _PyTime_MAX != -1
+#if PyTime_MIN + PyTime_MAX != -1
 #  error "_PyTime_t is not a two's complement integer type"
 #endif
 
@@ -124,16 +124,16 @@ pytime_as_nanoseconds(_PyTime_t t)
 }
 
 
-// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
+// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
 static inline int
 pytime_add(_PyTime_t *t1, _PyTime_t t2)
 {
-    if (t2 > 0 && *t1 > _PyTime_MAX - t2) {
-        *t1 = _PyTime_MAX;
+    if (t2 > 0 && *t1 > PyTime_MAX - t2) {
+        *t1 = PyTime_MAX;
         return -1;
     }
-    else if (t2 < 0 && *t1 < _PyTime_MIN - t2) {
-        *t1 = _PyTime_MIN;
+    else if (t2 < 0 && *t1 < PyTime_MIN - t2) {
+        *t1 = PyTime_MIN;
         return -1;
     }
     else {
@@ -156,7 +156,7 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b)
 {
     if (b != 0) {
         assert(b > 0);
-        return ((a < _PyTime_MIN / b) || (_PyTime_MAX / b < a));
+        return ((a < PyTime_MIN / b) || (PyTime_MAX / b < a));
     }
     else {
         return 0;
@@ -164,13 +164,13 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b)
 }
 
 
-// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
+// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
 static inline int
 pytime_mul(_PyTime_t *t, _PyTime_t k)
 {
     assert(k >= 0);
     if (pytime_mul_check_overflow(*t, k)) {
-        *t = (*t >= 0) ? _PyTime_MAX : _PyTime_MIN;
+        *t = (*t >= 0) ? PyTime_MAX : PyTime_MIN;
         return -1;
     }
     else {
@@ -180,7 +180,7 @@ pytime_mul(_PyTime_t *t, _PyTime_t k)
 }
 
 
-// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
+// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
 static inline _PyTime_t
 _PyTime_Mul(_PyTime_t t, _PyTime_t k)
 {
@@ -459,12 +459,12 @@ _PyTime_FromSeconds(int seconds)
     /* ensure that integer overflow cannot happen, int type should have 32
        bits, whereas _PyTime_t type has at least 64 bits (SEC_TO_NS takes 30
        bits). */
-    static_assert(INT_MAX <= _PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow");
-    static_assert(INT_MIN >= _PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow");
+    static_assert(INT_MAX <= PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow");
+    static_assert(INT_MIN >= PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow");
 
     _PyTime_t t = (_PyTime_t)seconds;
-    assert((t >= 0 && t <= _PyTime_MAX / SEC_TO_NS)
-           || (t < 0 && t >= _PyTime_MIN / SEC_TO_NS));
+    assert((t >= 0 && t <= PyTime_MAX / SEC_TO_NS)
+           || (t < 0 && t >= PyTime_MIN / SEC_TO_NS));
     t *= SEC_TO_NS;
     return pytime_from_nanoseconds(t);
 }
@@ -587,7 +587,7 @@ pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round,
     d = pytime_round(d, round);
 
     /* See comments in pytime_double_to_denominator */
-    if (!((double)_PyTime_MIN <= d && d < -(double)_PyTime_MIN)) {
+    if (!((double)PyTime_MIN <= d && d < -(double)PyTime_MIN)) {
         pytime_time_t_overflow();
         return -1;
     }
@@ -649,12 +649,12 @@ _PyTime_FromMillisecondsObject(_PyTime_t *tp, PyObject *obj, _PyTime_round_t rou
 
 
 double
-_PyTime_AsSecondsDouble(_PyTime_t t)
+PyTime_AsSecondsDouble(PyTime_t t)
 {
     /* volatile avoids optimization changing how numbers are rounded */
     volatile double d;
 
-    _PyTime_t ns = pytime_as_nanoseconds(t);
+    PyTime_t ns = pytime_as_nanoseconds(t);
     if (ns % SEC_TO_NS == 0) {
         /* Divide using integers to avoid rounding issues on the integer part.
            1e-9 cannot be stored exactly in IEEE 64-bit. */
@@ -695,7 +695,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k)
     assert(k > 1);
     if (t >= 0) {
         // Don't use (t + k - 1) / k to avoid integer overflow
-        // if t is equal to _PyTime_MAX
+        // if t is equal to PyTime_MAX
         _PyTime_t q = t / k;
         if (t % k) {
             q += 1;
@@ -704,7 +704,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k)
     }
     else {
         // Don't use (t - (k - 1)) / k to avoid integer overflow
-        // if t is equals to _PyTime_MIN.
+        // if t is equals to PyTime_MIN.
         _PyTime_t q = t / k;
         if (t % k) {
             q -= 1;
@@ -759,7 +759,7 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k,
 // Compute (t / k, t % k) in (pq, pr).
 // Make sure that 0 <= pr < k.
 // Return 0 on success.
-// Return -1 on underflow and store (_PyTime_MIN, 0) in (pq, pr).
+// Return -1 on underflow and store (PyTime_MIN, 0) in (pq, pr).
 static int
 pytime_divmod(const _PyTime_t t, const _PyTime_t k,
               _PyTime_t *pq, _PyTime_t *pr)
@@ -768,8 +768,8 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k,
     _PyTime_t q = t / k;
     _PyTime_t r = t % k;
     if (r < 0) {
-        if (q == _PyTime_MIN) {
-            *pq = _PyTime_MIN;
+        if (q == PyTime_MIN) {
+            *pq = PyTime_MIN;
             *pr = 0;
             return -1;
         }
@@ -784,13 +784,6 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k,
 }
 
 
-_PyTime_t
-_PyTime_AsNanoseconds(_PyTime_t t)
-{
-    return pytime_as_nanoseconds(t);
-}
-
-
 #ifdef MS_WINDOWS
 _PyTime_t
 _PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round)
@@ -926,6 +919,7 @@ _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts)
 #endif
 
 
+// N.B. If raise_exc=0, this may be called without the GIL.
 static int
 py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
 {
@@ -1050,6 +1044,18 @@ _PyTime_GetSystemClock(void)
 }
 
 
+int
+PyTime_Time(PyTime_t *result)
+{
+    if (py_get_system_clock(result, NULL, 1) < 0) {
+        // If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails:
+        // silently ignore the failure and return 0.
+        *result = 0;
+        return -1;
+    }
+    return 1;
+}
+
 int
 _PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info)
 {
@@ -1092,6 +1098,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise)
 #endif
 
 
+// N.B. If raise_exc=0, this may be called without the GIL.
 static int
 py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
 {
@@ -1102,13 +1109,13 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
     static_assert(sizeof(ticks) <= sizeof(_PyTime_t),
                   "ULONGLONG is larger than _PyTime_t");
     _PyTime_t t;
-    if (ticks <= (ULONGLONG)_PyTime_MAX) {
+    if (ticks <= (ULONGLONG)PyTime_MAX) {
         t = (_PyTime_t)ticks;
     }
     else {
         // GetTickCount64() maximum is larger than _PyTime_t maximum:
         // ULONGLONG is unsigned, whereas _PyTime_t is signed.
-        t = _PyTime_MAX;
+        t = PyTime_MAX;
     }
 
     int res = pytime_mul(&t, MS_TO_NS);
@@ -1151,7 +1158,7 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
 
     uint64_t uticks = mach_absolute_time();
     // unsigned => signed
-    assert(uticks <= (uint64_t)_PyTime_MAX);
+    assert(uticks <= (uint64_t)PyTime_MAX);
     _PyTime_t ticks = (_PyTime_t)uticks;
 
     _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base);
@@ -1229,6 +1236,17 @@ _PyTime_GetMonotonicClock(void)
 }
 
 
+int
+PyTime_Monotonic(PyTime_t *result)
+{
+    if (py_get_monotonic_clock(result, NULL, 1) < 0) {
+        *result = 0;
+        return -1;
+    }
+    return 0;
+}
+
+
 int
 _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
 {
@@ -1268,6 +1286,7 @@ py_win_perf_counter_frequency(_PyTimeFraction *base, int raise)
 }
 
 
+// N.B. If raise_exc=0, this may be called without the GIL.
 static int
 py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
 {
@@ -1335,6 +1354,25 @@ _PyTime_GetPerfCounter(void)
 }
 
 
+int
+PyTime_PerfCounter(PyTime_t *result)
+{
+    int res;
+#ifdef MS_WINDOWS
+    res = py_get_win_perf_counter(result, NULL, 1);
+#else
+    res = py_get_monotonic_clock(result, NULL, 1);
+#endif
+    if (res  < 0) {
+        // If py_win_perf_counter_frequency() or py_get_monotonic_clock()
+        // fails: silently ignore the failure and return 0.
+        *result = 0;
+        return -1;
+    }
+    return 0;
+}
+
+
 int
 _PyTime_localtime(time_t t, struct tm *tm)
 {