Skip to content

Commit

Permalink
pythongh-110850: Add PyTime_t C API
Browse files Browse the repository at this point in the history
Add PyTime_t API:

* PyTime_t type.
* PyTime_MIN and PyTime_MAX constants.
* PyTime_AsSecondsDouble(), PyTime_GetMonotonicClock(),
  PyTime_GetPerfCounter() and PyTime_GetSystemClock() functions.
  • Loading branch information
vstinner committed Nov 16, 2023
1 parent 7218bac commit c3b4c45
Show file tree
Hide file tree
Showing 23 changed files with 345 additions and 190 deletions.
132 changes: 132 additions & 0 deletions Doc/c-api/time.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
.. highlight:: c

PyTime API
==========

.. versionadded:: 3.13

PyTime API
----------

The PyTime_t API is written to use timestamp and timeout values stored in
various formats and to read clocks.

The :c:type:`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
:c:type:`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:

* Seconds.
* Seconds as a floating pointer number (C :c:type:`double`).
* Milliseconds (10\ :sup:`-3` seconds).
* Microseconds (10\ :sup:`-6` seconds).
* 100 nanoseconds (10\ :sup:`-7` seconds), used on Windows.
* Nanoseconds (10\ :sup:`-9` seconds).
* :c:expr:`timeval` structure, 1 microsecond (10\ :sup:`-6` seconds).
* :c:expr:`timespec` structure, 1 nanosecond (10\ :sup:`-9` seconds).

Integer overflows are detected and raise :exc:`OverflowError`. Conversion to a
resolution larger than 1 nanosecond is rounded correctly with the requested
rounding mode. Available rounding modes:

* 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:

* System clock
* Monotonic clock
* Performance counter

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.


Types
-----

.. c:type:: PyTime_t
Timestamp type with subsecond precision: 64-bit signed integer.

This type can be used to store a duration. Indirectly, it can be used to
store a date relative to a reference date, such as the UNIX epoch.


Constants
---------

.. c:var:: PyTime_t PyTime_MIN
Minimum value of the :c:type:`PyTime_t` type.
:c:macro`PyTime_MIN` nanoseconds is around -292.3 years.

.. c:var:: PyTime_t PyTime_MAX
Maximum value of the :c:type:`PyTime_t` type.
:c:macro`PyTime_MAX` nanoseconds is around +292.3 years.


Functions
---------

.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
Convert a timestamp to a number of seconds as a C :c:type:`double`.
The function cannot fail.
.. c:function:: 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.
If reading the clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and clamp the clock to
the [PyTime_MIN; PyTime_MAX] range.
See also the :func:`time.monotonic` function.

.. c:function:: PyTime_t PyTime_GetPerfCounter(void)
Get the performance counter: clock with the highest available resolution to
measure a short duration.
If reading the clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and clamp the time to the
[PyTime_MIN; PyTime_MAX] range.
See also the :func:`time.perf_counter` function.


.. c:function:: PyTime_t PyTime_GetSystemClock(void)
Get the current time from the system clock.
If reading the clock fails, silently ignore the error and return ``0``.
On integer overflow, silently ignore the overflow and clamp the clock to
the [PyTime_MIN; PyTime_MAX] range.
See also the :func:`time.time` function.
1 change: 1 addition & 0 deletions Doc/c-api/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,16 @@ New Features
:exc:`KeyError` if the key missing.
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)

* Add PyTime_t C API:

* :c:type:`PyTime_t` type.
* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
* :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_GetMonotonicClock`,
:c:func:`PyTime_GetPerfCounter` and :c:func:`PyTime_GetSystemClock`
functions.

(Contributed by Victor Stinner in :gh:`110850`.)


Porting to Python 3.13
----------------------
Expand Down
1 change: 1 addition & 0 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
98 changes: 98 additions & 0 deletions Include/cpython/pytime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// The PyTime_t API is written to use timestamp and timeout values stored in
// various formats and to read clocks.
//
// The PyTime_t type is an integer to support directly common arithmetic
// operations like 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].
//
// Formats:
//
// * 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)
//
// 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).
//
// 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.
//
// Clocks:
//
// * System clock
// * 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().

#ifndef Py_LIMITED_API
#ifndef Py_PYTIME_H
#define Py_PYTIME_H
#ifdef __cplusplus
extern "C" {
#endif

// PyTime_t: Python timestamp with subsecond precision. It can be used to store
// a duration, and so indirectly a date relative to a reference date, such as
// the 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

// Convert a timestamp to a number of seconds as a C double.
// The function cannot fail.
PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t);

// 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.
//
// If reading the clock fails, silently ignore the error and return 0.
//
// On integer overflow, silently ignore the overflow and clamp the time to the
// [PyTime_MIN; PyTime_MAX] range.
PyAPI_FUNC(PyTime_t) PyTime_GetMonotonicClock(void);

// Get the performance counter: clock with the highest available resolution to
// measure a short duration.
//
// If reading the clock fails, silently ignore the error and return 0.
//
// On integer overflow, silently ignore the overflow and clamp the time to the
// [PyTime_MIN; PyTime_MAX] range.
PyAPI_FUNC(PyTime_t) PyTime_GetPerfCounter(void);

// Get the current time from the system clock.
//
// If reading the clock fails, silently ignore the error and return 0.
//
// On integer overflow, silently ignore the overflow and clamp the time to the
// [PyTime_MIN; PyTime_MAX] range.
PyAPI_FUNC(PyTime_t) PyTime_GetSystemClock(void);

#ifdef __cplusplus
}
#endif
#endif /* Py_PYTIME_H */
#endif /* Py_LIMITED_API */
Loading

0 comments on commit c3b4c45

Please sign in to comment.