Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3244257
ENH: expose datetime.c functions to cython
jbrockmendel Mar 15, 2022
843cf59
tests
jbrockmendel Mar 15, 2022
0e9ef3b
salvage bis of #16364
jbrockmendel Mar 23, 2022
9ccf321
add NUMPY_API to comments, revert includes
jbrockmendel Jul 14, 2022
62dfdfe
try suggestion
jbrockmendel Jul 15, 2022
45a3381
fix annotation
jbrockmendel Feb 11, 2023
af57ab0
Merge branch 'main' into npy_yes_export
jbrockmendel Feb 21, 2023
16ce84f
Fixup missing self
jbrockmendel Feb 22, 2023
d5f2376
fix tests
jbrockmendel Feb 28, 2023
fcc4b5e
lint fixup
jbrockmendel Feb 28, 2023
3aa2fa4
Merge branch 'main' into npy_yes_export
jbrockmendel Feb 28, 2023
ceb785f
revert accident, update cversions.txt
jbrockmendel Feb 28, 2023
e3beb2e
update C_API_VERSION
jbrockmendel Feb 28, 2023
bfa2f91
lint fixup
jbrockmendel Mar 1, 2023
3f8104d
add NpyDatetime_ to names
jbrockmendel Mar 7, 2023
c335e71
whitespace fixup
jbrockmendel Mar 13, 2023
4bd253b
MAINT: Modify style, change spelling a bit and fix Cython except
seberg Mar 15, 2023
8adcf57
MAINT: Remove noexcept again
seberg Mar 15, 2023
b782160
Merge branch 'main' into npy_yes_export
seberg May 16, 2023
67eb41e
MAINT: Add MinVersion to API additions
seberg May 16, 2023
372771b
TST: Make sure cython tests define NPY_TARGET_VERSION to >=1.25
seberg May 16, 2023
b355bc2
Merge branch 'main' into npy_yes_export
ngoldbaum Jun 30, 2023
e9a92f6
MAINT: remove one more unnecessary header prototype
ngoldbaum Jun 30, 2023
0352c57
DOC: add release note
ngoldbaum Jun 30, 2023
3e346d6
API: Add NpyDatetime_ParseISO8601Datetime and update target version
ngoldbaum Jun 30, 2023
9cf2b30
DOC: add docs for the datetime C API
ngoldbaum Jun 30, 2023
500ddfd
MAINT: fix name of release note
ngoldbaum Jun 30, 2023
9d97cc2
MAINT: remove comments from cversions.txt for numpy 2.0
ngoldbaum Jun 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/release/upcoming_changes/21199.c_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Datetime functionality exposed in the C API and Cython bindings
---------------------------------------------------------------

The functions ``NpyDatetime_ConvertDatetime64ToDatetimeStruct``,
``NpyDatetime_ConvertDatetimeStructToDatetime64``,
``NpyDatetime_ConvertPyDateTimeToDatetimeStruct``,
``NpyDatetime_GetDatetimeISO8601StrLen``, ``NpyDatetime_MakeISO8601Datetime``,
and ``NpyDatetime_ParseISO8601Datetime`` have been added to the C API to
facilitate converting between strings, Python datetimes, and NumPy datetimes in
external libraries.
225 changes: 225 additions & 0 deletions doc/source/reference/c-api/datetimes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
Datetime API
============

NumPy represents dates internally using an int64 counter and a unit metadata
struct. Time differences are represented similarly using an int64 and a unit
metadata struct. The functions described below are available to to facilitate
converting between ISO 8601 date strings, NumPy datetimes, and Python datetime
objects in C.

Data types
----------

In addition to the `npy_datetime` and `npy_timedelta` typedefs for `npy_int64`,
NumPy defines two additional structs that represent time unit metadata and
an "exploded" view of a datetime.

.. c:type:: PyArray_DatetimeMetaData

Represents datetime unit metadata.

.. code-block:: c

typedef struct {
NPY_DATETIMEUNIT base;
int num;
} PyArray_DatetimeMetaData;

.. c:member:: NPY_DATETIMEUNIT base

The unit of the datetime.

.. c:member:: num

A multiplier for the unit.

.. c:type:: npy_datetimestruct

An "exploded" view of a datetime value

.. code-block:: c

typedef struct {
npy_int64 year;
npy_int32 month, day, hour, min, sec, us, ps, as;
} npy_datetimestruct;

.. c:enum:: NPY_DATETIMEUNIT

Time units supported by NumPy. The "FR" in the names of the enum variants
is short for frequency.

.. c:enumerator:: NPY_FR_ERROR

Error or undetermined units.

.. c::enumerator:: NPY_FR_Y

Years

.. c::enumerator:: NPY_FR_M

Months

.. c::enumerator:: NPY_FR_W

Weeks

.. c::enumerator:: NPY_FR_D

Days

.. c::enumerator:: NPY_FR_h

Hours

.. c::enumerator:: NPY_FR_m

Minutes

.. c::enumerator:: NPY_FR_s

Seconds

.. c::enumerator:: NPY_FR_ms

Milliseconds

.. c::enumerator:: NPY_FR_us

Microseconds

.. c::enumerator:: NPY_FR_ns

Nanoseconds

.. c::enumerator:: NPY_FR_ps

Picoseconds

.. c::enumerator:: NPY_FR_fs

Femtoseconds

.. c::enumerator:: NPY_FR_as

Attoseconds

.. c::enumerator:: NPY_FR_GENERIC

Unbound units, can convert to anything


Conversion functions
--------------------

.. c:function:: int NpyDatetime_ConvertDatetimeStructToDatetime64( \
PyArray_DatetimeMetaData *meta, const npy_datetimestruct *dts, \
npy_datetime *out)

Converts a datetime from a datetimestruct to a datetime in the units
specified by the unit metadata. The date is assumed to be valid.

If the ``num`` member of the metadata struct is large, there may
be integer overflow in this function.

Returns 0 on success and -1 on failure.

.. c:function:: int NpyDatetime_ConvertDatetime64ToDatetimeStruct( \
PyArray_DatetimeMetaData *meta, npy_datetime dt, \
npy_datetimestruct *out)

Converts a datetime with units specified by the unit metadata to an
exploded datetime struct.

Returns 0 on success and -1 on failure.

.. c:function:: int NpyDatetime_ConvertPyDateTimeToDatetimeStruct( \
PyObject *obj, npy_datetimestruct *out, \
NPY_DATETIMEUNIT *out_bestunit, int apply_tzinfo)

Tests for and converts a Python ``datetime.datetime`` or ``datetime.date``
object into a NumPy ``npy_datetimestruct``.

``out_bestunit`` gives a suggested unit based on whether the object
was a ``datetime.date`` or ``datetime.datetime`` object.

If ``apply_tzinfo`` is 1, this function uses the tzinfo to convert
to UTC time, otherwise it returns the struct with the local time.

Returns -1 on error, 0 on success, and 1 (with no error set)
if obj doesn't have the needed date or datetime attributes.

.. c:function:: NpyDatetime_ParseISO8601Datetime( \
char const *str, Py_ssize_t len, NPY_DATETIMEUNIT unit, \
NPY_CASTING casting, npy_datetimestruct *out, \
NPY_DATETIMEUNIT *out_bestunit, npy_bool *out_special)

Parses (almost) standard ISO 8601 date strings. The differences are:

* The date "20100312" is parsed as the year 20100312, not as
equivalent to "2010-03-12". The '-' in the dates are not optional.
* Only seconds may have a decimal point, with up to 18 digits after it
(maximum attoseconds precision).
* Either a 'T' as in ISO 8601 or a ' ' may be used to separate
the date and the time. Both are treated equivalently.
* Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats.
* Doesn't handle leap seconds (seconds value has 60 in these cases).
* Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow
* Accepts special values "NaT" (not a time), "Today", (current
day according to local time) and "Now" (current time in UTC).

``str`` must be a NULL-terminated string, and ``len`` must be its length.
``unit`` should contain -1 if the unit is unknown, or the unit
which will be used if it is.
``casting`` controls how the detected unit from the string is allowed
to be cast to the 'unit' parameter.
``out`` gets filled with the parsed date-time.
``out_bestunit`` gives a suggested unit based on the amount of
resolution provided in the string, or -1 for NaT.
``out_special`` gets set to 1 if the parsed time was 'today',
'now', or ''/'NaT'. For 'today', the unit recommended is
'D', for 'now', the unit recommended is 's', and for 'NaT'
the unit recommended is 'Y'.

Returns 0 on success, -1 on failure.

.. c:function:: NpyDatetime_GetDatetimeISO8601StrLen(\
int local, NPY_DATETIMEUNIT base)

Returns the string length to use for converting datetime
objects with the given local time and unit settings to strings.
Use this when constructings strings to supply to
``NpyDatetime_MakeISO8601Datetime``.

.. c:function:: NpyDatetime_MakeISO8601Datetime(\
npy_datetimestruct *dts, char *outstr, npy_intp outlen, \
int local, int utc, NPY_DATETIMEUNIT base, int tzoffset, \
NPY_CASTING casting)

Converts an ``npy_datetimestruct`` to an (almost) ISO 8601
NULL-terminated string. If the string fits in the space exactly,
it leaves out the NULL terminator and returns success.

The differences from ISO 8601 are the 'NaT' string, and
the number of year digits is >= 4 instead of strictly 4.

If ``local`` is non-zero, it produces a string in local time with
a +-#### timezone offset. If ``local`` is zero and ``utc`` is non-zero,
produce a string ending with 'Z' to denote UTC. By default, no time
zone information is attached.

``base`` restricts the output to that unit. Set ``base`` to
-1 to auto-detect a base after which all the values are zero.

``tzoffset`` is used if ``local`` is enabled, and ``tzoffset`` is
set to a value other than -1. This is a manual override for
the local time zone to use, as an offset in minutes.

``casting`` controls whether data loss is allowed by truncating
the data to a coarser unit. This interacts with ``local``, slightly,
in order to form a date unit string as a local time, the casting
must be unsafe.

Returns 0 on success, -1 on failure (for example if the output
string was too short).
1 change: 1 addition & 0 deletions doc/source/reference/c-api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ code.
ufunc
generalized-ufuncs
coremath
datetimes
deprecations
data_memory
31 changes: 31 additions & 0 deletions numpy/__init__.cython-30.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,8 @@ cdef extern from "numpy/arrayobject.h":
int PyArray_CompareString (char *, char *, size_t)
int PyArray_SetBaseObject(ndarray, base) except -1 # NOTE: steals a reference to base! Use "set_array_base()" instead.

# additional datetime related functions are defined below


# Typedefs that matches the runtime dtype objects in
# the numpy module.
Expand Down Expand Up @@ -795,6 +797,10 @@ cdef extern from "numpy/ndarraytypes.h":
NPY_DATETIMEUNIT base
int64_t num

ctypedef struct npy_datetimestruct:
int64_t year
int32_t month, day, hour, min, sec, us, ps, as

cdef extern from "numpy/arrayscalars.h":

# abstract types
Expand Down Expand Up @@ -846,6 +852,31 @@ cdef extern from "numpy/arrayscalars.h":
NPY_FR_as


cdef extern from "numpy/arrayobject.h":
# These are part of the C-API defined in `__multiarray_api.h`

# NumPy internal definitions in datetime_strings.c:
int get_datetime_iso_8601_strlen "NpyDatetime_GetDatetimeISO8601StrLen" (
int local, NPY_DATETIMEUNIT base)
int make_iso_8601_datetime "NpyDatetime_MakeISO8601Datetime" (
npy_datetimestruct *dts, char *outstr, npy_intp outlen,
int local, int utc, NPY_DATETIMEUNIT base, int tzoffset,
NPY_CASTING casting) except -1

# NumPy internal definition in datetime.c:
# May return 1 to indicate that object does not appear to be a datetime
# (returns 0 on success).
int convert_pydatetime_to_datetimestruct "NpyDatetime_ConvertPyDateTimeToDatetimeStruct" (
Copy link
Member

Choose a reason for hiding this comment

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

I changed the capitalization of this to camel case the PyDateTime.

Honestly, camel case or not is a bit wonky anyway for our datetimes, I am happy to change it back or ignore...

PyObject *obj, npy_datetimestruct *out,
NPY_DATETIMEUNIT *out_bestunit, int apply_tzinfo) except -1
int convert_datetime64_to_datetimestruct "NpyDatetime_ConvertDatetime64ToDatetimeStruct" (
PyArray_DatetimeMetaData *meta, npy_datetime dt,
npy_datetimestruct *out) except -1
int convert_datetimestruct_to_datetime64 "NpyDatetime_ConvertDatetimeStructToDatetime64"(
PyArray_DatetimeMetaData *meta, const npy_datetimestruct *dts,
npy_datetime *out) except -1
Copy link
Member

Choose a reason for hiding this comment

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

The except -1 was missing (and noexcept in the one case to be explicit). So added those (and prefer this formatting, but don't want to bike-shed it).



#
# ufunc API
#
Expand Down
32 changes: 32 additions & 0 deletions numpy/__init__.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ cdef extern from "numpy/arrayobject.h":
int PyArray_CompareString (char *, char *, size_t)
int PyArray_SetBaseObject(ndarray, base) except -1 # NOTE: steals a reference to base! Use "set_array_base()" instead.

# additional datetime related functions are defined below


# Typedefs that matches the runtime dtype objects in
# the numpy module.
Expand Down Expand Up @@ -753,6 +755,11 @@ cdef extern from "numpy/ndarraytypes.h":
NPY_DATETIMEUNIT base
int64_t num

ctypedef struct npy_datetimestruct:
int64_t year
int32_t month, day, hour, min, sec, us, ps, as


cdef extern from "numpy/arrayscalars.h":

# abstract types
Expand Down Expand Up @@ -804,6 +811,31 @@ cdef extern from "numpy/arrayscalars.h":
NPY_FR_as


cdef extern from "numpy/arrayobject.h":
# These are part of the C-API defined in `__multiarray_api.h`

# NumPy internal definitions in datetime_strings.c:
int get_datetime_iso_8601_strlen "NpyDatetime_GetDatetimeISO8601StrLen" (
int local, NPY_DATETIMEUNIT base)
int make_iso_8601_datetime "NpyDatetime_MakeISO8601Datetime" (
npy_datetimestruct *dts, char *outstr, npy_intp outlen,
int local, int utc, NPY_DATETIMEUNIT base, int tzoffset,
NPY_CASTING casting) except -1

# NumPy internal definition in datetime.c:
# May return 1 to indicate that object does not appear to be a datetime
# (returns 0 on success).
int convert_pydatetime_to_datetimestruct "NpyDatetime_ConvertPyDateTimeToDatetimeStruct" (
PyObject *obj, npy_datetimestruct *out,
NPY_DATETIMEUNIT *out_bestunit, int apply_tzinfo) except -1
int convert_datetime64_to_datetimestruct "NpyDatetime_ConvertDatetime64ToDatetimeStruct" (
PyArray_DatetimeMetaData *meta, npy_datetime dt,
npy_datetimestruct *out) except -1
int convert_datetimestruct_to_datetime64 "NpyDatetime_ConvertDatetimeStructToDatetime64"(
PyArray_DatetimeMetaData *meta, const npy_datetimestruct *dts,
npy_datetime *out) except -1


#
# ufunc API
#
Expand Down
2 changes: 1 addition & 1 deletion numpy/core/code_generators/cversions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@
0x00000011 = ca1aebdad799358149567d9d93cbca09

# Version 18 (NumPy 2.0.0)
0x00000012 = 5af92e858ce8e95409ae1fcc8f508ddd
0x00000012 = 52eabc83680fd0d381c08d63e264e72e
6 changes: 6 additions & 0 deletions numpy/core/code_generators/numpy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ def get_annotations():
'PyDataMem_SetHandler': (304, MinVersion("1.22")),
'PyDataMem_GetHandler': (305, MinVersion("1.22")),
# End 1.22 API
'NpyDatetime_ConvertDatetime64ToDatetimeStruct': (307, MinVersion("2.0")),
'NpyDatetime_ConvertDatetimeStructToDatetime64': (308, MinVersion("2.0")),
'NpyDatetime_ConvertPyDateTimeToDatetimeStruct': (309, MinVersion("2.0")),
'NpyDatetime_GetDatetimeISO8601StrLen': (310, MinVersion("2.0")),
'NpyDatetime_MakeISO8601Datetime': (311, MinVersion("2.0")),
'NpyDatetime_ParseISO8601Datetime': (312, MinVersion("2.0")),
}

ufunc_types_api = {
Expand Down
2 changes: 1 addition & 1 deletion numpy/core/setup_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
# 0x00000012 - 2.0.x
C_API_VERSION = 0x00000012

# By default, when compiling downstream libraries against NumPy,```
# By default, when compiling downstream libraries against NumPy,
# pick an older feature version. For example, for 1.25.x we default to the
# 1.19 API and support going back all the way to 1.15.x (if so desired).
# This is set up in `numpyconfig.h`.
Expand Down
Loading