GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-110850: Add PyTime_t C API by vstinner · Pull Request #112135 · python/cpython

gh-110850: Add PyTime_t C API #112135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
vstinner wants to merge 3 commits into python:main from vstinner:pytime_api

Uh oh!

There was an error while loading. Please reload this page.

Closed

gh-110850: Add PyTime_t C API #112135

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
3 commits
Select commit Hold shift + click to select a range
6c40241
gh-110850: Add PyTime_t C API
vstinner Nov 15, 2023
d8cf97d
Address Sam's review: update doc
vstinner Dec 14, 2023
c6f5be9
Avoid reference to epoch_
vstinner Dec 15, 2023
File filter
Filter by extension


Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

90 changes: 90 additions & 0 deletions 90 Doc/c-api/time.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.. highlight:: c

PyTime C API
============

.. versionadded:: 3.13

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

The PyTime API provides access to system clocks and time conversion functions.
It is similar to the Python :mod:`time` module.


Types
-----

.. c:type:: PyTime_t

A timestamp or duration in nanoseconds represented as a 64-bit signed
integer.
Comment thread
Comment on lines +20 to +21

@encukou encukou Feb 8, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the C-API WG issue, you say that the units are supposed to be an implementation detail.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner Feb 8, 2024

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I'm confused. I don't recall the exact history of this PR that I wrote 3 months ago.

When I wrote that API 10 years ago, I tried hard to not expose the "unit" (1 nanosecond). In practice, it was always 1 nanosecond, even if on Windows, clocks only have a resolution of 100 nanoseconds in the best case.

Maybe it's time to stick to nanoseconds. On Linux, timespec, clock_gettime(), nanosleep() have a resolution of 1 nanosecond. I don't know any operating system API using a better resolution.

So well, we should stick to 1 nanosecond to avoid any confusion. So creating a PyTime_t of 1 nanosecond is just PyTime_t one_ns = 1;. No "from nanoseconds" or "to nanoseconds" are needed. It makes the API simpler to use (so less error prone).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

colesbury and erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

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].


Constants
---------

.. c:var:: PyTime_t PyTime_MIN

Minimum value of the :c:type:`PyTime_t` type.

:c:var:`PyTime_MIN` nanoseconds is around -292.3 years.

.. c:var:: PyTime_t PyTime_MAX

Maximum value of the :c:type:`PyTime_t` type.

:c:var:`PyTime_MAX` nanoseconds is around +292.3 years.


Functions
---------

.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
Comment thread

@serhiy-storchaka serhiy-storchaka Dec 15, 2023

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rounding mode? Do we need an argument for specifying it?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner Dec 15, 2023

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to implement a rounding method on this function. I don't know how numbers are rounded. The function starts to lose precision at 2**53 nanoseconds:

>>> x=2**53+1; y=int(_testcapi.PyTime_AsSecondsDouble(x)*1e9); f"ulp(x)={math.ulp(x)} and {y-x=}"
'ulp(x)=2.0 and y-x=-1'

In short, the function is implemented as:

def PyTime_AsSecondsDouble(t): return float(t) / 1e9

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

Convert a timestamp to a number of seconds as a C :c:expr:`double`.

The function cannot fail.


.. c:function:: PyTime_t PyTime_Monotonic(void)

Return the value in nanoseconds of a monotonic clock, i.e. a clock
that cannot go backwards. Similar to :func:`time.monotonic_ns`; see
:func:`time.monotonic` for details.

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.


.. c:function:: PyTime_t PyTime_PerfCounter(void)

Return the value in nanoseconds of a performance counter, i.e. a
clock with the highest available resolution to measure a short duration.
Similar to :func:`time.perf_counter_ns`; see :func:`time.perf_counter` for
details.

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.


.. c:function:: PyTime_t PyTime_Time(void)

Return the time in nanoseconds since January 1, 1970, 00:00:00 (UTC).
Similar to :func:`time.time_ns`; see :func:`time.time` for details.

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.
1 change: 1 addition & 0 deletions 1 Doc/c-api/utilities.rst
Show comments
View file
Open in desktop
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
13 changes: 8 additions & 5 deletions 13 Doc/conf.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,14 @@
('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'),
Expand Down Expand Up @@ -253,12 +256,12 @@
('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

Expand Down
10 changes: 10 additions & 0 deletions 10 Doc/whatsnew/3.13.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,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 in :gh:`110850`.)


Porting to Python 3.13
----------------------
Expand Down
1 change: 1 addition & 0 deletions 1 Include/Python.h
Show comments
View file
Open in desktop
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
23 changes: 23 additions & 0 deletions 23 Include/cpython/pytime.h
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -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(PyTime_t) PyTime_Monotonic(void);
PyAPI_FUNC(PyTime_t) PyTime_PerfCounter(void);
PyAPI_FUNC(PyTime_t) PyTime_Time(void);

#ifdef __cplusplus
}
#endif
#endif /* Py_PYTIME_H */
#endif /* Py_LIMITED_API */
124 changes: 45 additions & 79 deletions 124 Include/internal/pycore_time.h
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
// 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 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)
// * 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).
//
// 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:
//
// * 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], 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.
// 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
//
// 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
Expand All @@ -56,14 +57,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 {
Expand Down Expand Up @@ -147,7 +141,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).
Expand All @@ -169,10 +163,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,
Expand Down Expand Up @@ -250,7 +240,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()
Expand All @@ -261,36 +251,13 @@ typedef struct {
double resolution;
} _Py_clock_info_t;

// Get the current time from the system clock.
//
// If the internal clock fails, silently ignore the error and return 0.
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetSystemClockWithInfo() to check for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void);

// Get the current time from the system clock.
// On success, set *t and *info (if not NULL), and return 0.
// On error, raise an exception and return -1.
extern int _PyTime_GetSystemClockWithInfo(
_PyTime_t *t,
_Py_clock_info_t *info);

// 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 the internal clock fails, silently ignore the error and return 0.
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetMonotonicClockWithInfo() to check for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(_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
Expand All @@ -315,17 +282,6 @@ PyAPI_FUNC(int) _PyTime_localtime(time_t t, struct tm *tm);
// Export for '_datetime' shared extension.
PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);

// Get the performance counter: clock with the highest available resolution to
// measure a short duration.
//
// If the internal clock fails, silently ignore the error and return 0.
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetPerfCounterWithInfo() 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.
//
Expand All @@ -336,14 +292,24 @@ 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
#define _PyTime_GetMonotonicClock PyTime_Monotonic
#define _PyTime_GetPerfCounter PyTime_PerfCounter
#define _PyTime_GetSystemClock PyTime_Time


// --- _PyDeadline -----------------------------------------------------------

// Create a deadline.
// Pseudo code: _PyTime_GetMonotonicClock() + timeout.
// Pseudo code: PyTime_Monotonic() + timeout.
// Export for '_ssl' shared extension.
PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout);

// Get remaining time from a deadline.
// Pseudo code: deadline - _PyTime_GetMonotonicClock().
// Pseudo code: deadline - PyTime_Monotonic().
// Export for '_ssl' shared extension.
PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline);

Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.