numpy  2.0.0
src/multiarray/datetime_strings.c File Reference
#include <Python.h>
#include <time.h>
#include <numpy/arrayobject.h>
#include "npy_config.h"
#include "npy_pycompat.h"
#include "numpy/arrayscalars.h"
#include "methods.h"
#include "_datetime.h"
#include "datetime_strings.h"

Defines

#define PY_SSIZE_T_CLEAN
#define NPY_NO_DEPRECATED_API   NPY_API_VERSION
#define _MULTIARRAYMODULE

Typedefs

typedef time_t NPY_TIME_T

Functions

static int get_localtime (NPY_TIME_T *ts, struct tm *tms)
static int get_gmtime (NPY_TIME_T *ts, struct tm *tms)
static NPY_TIME_T get_mktime (struct tm *tms)
static int convert_datetimestruct_utc_to_local (npy_datetimestruct *out_dts_local, const npy_datetimestruct *dts_utc, int *out_timezone_offset)
static int convert_datetimestruct_local_to_utc (npy_datetimestruct *out_dts_utc, const npy_datetimestruct *dts_local)
NPY_NO_EXPORT int parse_iso_8601_datetime (char *str, Py_ssize_t len, NPY_DATETIMEUNIT unit, NPY_CASTING casting, npy_datetimestruct *out, npy_bool *out_local, NPY_DATETIMEUNIT *out_bestunit, npy_bool *out_special)
NPY_NO_EXPORT int get_datetime_iso_8601_strlen (int local, NPY_DATETIMEUNIT base)
static NPY_DATETIMEUNIT lossless_unit_from_datetimestruct (npy_datetimestruct *dts)
NPY_NO_EXPORT int make_iso_8601_datetime (npy_datetimestruct *dts, char *outstr, int outlen, int local, NPY_DATETIMEUNIT base, int tzoffset, NPY_CASTING casting)
NPY_NO_EXPORT PyObject * array_datetime_as_string (PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwds)

Define Documentation

#define NPY_NO_DEPRECATED_API   NPY_API_VERSION

Typedef Documentation

typedef time_t NPY_TIME_T
Platform-specific time_t typedef

Function Documentation

NPY_NO_EXPORT PyObject* array_datetime_as_string ( PyObject *  NPY_UNUSEDself,
PyObject *  args,
PyObject *  kwds 
)
This is the Python-exposed datetime_as_string function.
Claim a reference to timezone for later
Get the datetime metadata
Use the metadata's unit for printing by default
Parse the input unit if provided
unit == -1 means to autodetect the unit from the datetime data
Get the input time zone
Convert to ASCII if it's unicode
accept unicode input
Check for the supported string inputs
Otherwise assume it's a Python TZInfo, or acts like one
Get a string size long enough for any datetimes we're given
Create the iteration string data type (always ASCII string)
Get the datetime
Convert it to a struct
Get the tzoffset from the timezone if provided
Zero the destination string completely
Convert that into a string

Referenced by _PyArray_SigintHandler().

static int convert_datetimestruct_local_to_utc ( npy_datetimestruct out_dts_utc,
const npy_datetimestruct dts_local 
) [static]
Converts a datetimestruct in local time to a datetimestruct in UTC.
Returns 0 on success, -1 on failure.
Make a copy of the input 'dts' to modify
HACK: Use a year < 2038 for later years for small time_t
2036 is a leap year
2037 is not a leap year
ISO 8601 states to treat date-times without a timezone offset or 'Z' for UTC as local time. The C standard libary functions mktime and gmtime allow us to do this conversion.
Only do this timezone adjustment for recent and future years. In this case, "recent" is defined to be 1970 and later, because on MS Windows, mktime raises an error when given an earlier date.
mktime converts a local 'struct tm' into a time_t
gmtime converts a 'time_t' into a UTC 'struct tm'
Reapply the year 2038 year correction HACK

References npy_datetimestruct::day, get_gmtime(), get_mktime(), npy_datetimestruct::hour, npy_datetimestruct::min, npy_datetimestruct::month, npy_datetimestruct::sec, and npy_datetimestruct::year.

static int convert_datetimestruct_utc_to_local ( npy_datetimestruct out_dts_local,
const npy_datetimestruct dts_utc,
int *  out_timezone_offset 
) [static]
Converts a datetimestruct in UTC to a datetimestruct in local time, also returning the timezone offset applied.
Returns 0 on success, -1 on failure.
Make a copy of the input 'dts' to modify
HACK: Use a year < 2038 for later years for small time_t
2036 is a leap year
2037 is not a leap year
Convert everything in 'dts' to a time_t, to minutes precision. This is POSIX time, which skips leap-seconds, but because we drop the seconds value from the npy_datetimestruct, everything is ok for this operation.
localtime converts a 'time_t' into a local 'struct tm'
Copy back all the values except seconds
Extract the timezone offset that was applied
Reapply the year 2038 year correction HACK

Referenced by get_datetime_iso_8601_strlen().

Provides a string length to use for converting datetime objects with the given local and unit settings.
If no unit is provided, return the maximum length
Generic units can only be used to represent NaT

<

"###"

<

"###"

<

"###"

<

"###"

<

"###"

<

".###"

<

":##"

<

":##"

<

"T##"

<

"-##"

<

"-##"

<

64-bit year

<

"+####" or "-####"

<

"Z"

<

NULL terminator

References convert_datetimestruct_utc_to_local().

static int get_gmtime ( NPY_TIME_T ts,
struct tm *  tms 
) [static]
Wraps <cite>gmtime</cite> functionality for multiple platforms. This converts a time value to a time structure in UTC.
Returns 0 on success, -1 on failure.

Referenced by convert_datetimestruct_local_to_utc().

static int get_localtime ( NPY_TIME_T ts,
struct tm *  tms 
) [static]
Wraps <cite>localtime</cite> functionality for multiple platforms. This converts a time value to a time structure in the local timezone.
Returns 0 on success, -1 on failure.

Referenced by parse_iso_8601_datetime().

static NPY_TIME_T get_mktime ( struct tm *  tms) [static]
Wraps <cite>mktime</cite> functionality for multiple platforms. This converts a local time struct to an UTC value.
Returns timestamp on success, -1 on failure.

Referenced by convert_datetimestruct_local_to_utc().

Finds the largest unit whose value is nonzero, and for which the remainder for the rest of the units is zero.
NPY_NO_EXPORT int make_iso_8601_datetime ( npy_datetimestruct dts,
char *  outstr,
int  outlen,
int  local,
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, otherwise it uses timezone Z (UTC).
'base' restricts the output to that unit. Set 'base' to -1 to auto-detect a base after which all the values are zero. <blockquote>
'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). </blockquote>
Handle NaT, and treat a datetime with generic units as NaT
Only do local time within a reasonable year range. The years earlier than 1970 are not made local, because the Windows API raises an error when they are attempted. For consistency, this restriction is applied to all platforms.
Note that this only affects how the datetime becomes a string. The result is still completely unambiguous, it only means that datetimes outside this range will not include a time zone when they are printed.
Automatically detect a good unit
If there's a timezone, use at least minutes precision, and never split up hours and minutes by default
Don't split up dates by default
Print weeks with the same precision as days.

TODO: Could print weeks with YYYY-Www format if the week
epoch is a Monday.
Use the C API to convert from UTC to local time
Set dts to point to our local time instead of the UTC time
Use the manually provided tzoffset
Make a copy of the npy_datetimestruct we can modify
Set and apply the required timezone offset
Now the datetimestruct data is in the final form for the string representation, so ensure that the data is being cast according to the casting rule.
Producing a date as a local time is always 'unsafe'
Only 'unsafe' and 'same_kind' allow data loss
YEAR
Can't use PyOS_snprintf, because it always produces a '0' character at the end, and NumPy string types are permitted to have data all the way to the end of the buffer.
If it ran out of space or there isn't space for the NULL terminator
Stop if the unit is years
MONTH
Stop if the unit is months
DAY
Stop if the unit is days
HOUR
Stop if the unit is hours
MINUTE
Stop if the unit is minutes
SECOND
Stop if the unit is seconds
MILLISECOND
Stop if the unit is milliseconds
MICROSECOND
Stop if the unit is microseconds
NANOSECOND
Stop if the unit is nanoseconds
PICOSECOND
Stop if the unit is picoseconds
FEMTOSECOND
Stop if the unit is femtoseconds
ATTOSECOND
Add the +/- sign
Add the timezone offset
UTC "Zulu" time
Add a NULL terminator, and return
NPY_NO_EXPORT int parse_iso_8601_datetime ( char *  str,
Py_ssize_t  len,
NPY_DATETIMEUNIT  unit,
NPY_CASTING  casting,
npy_datetimestruct out,
npy_bool out_local,
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

System Message: ERROR/3 (<string>, line 17) Unexpected indentation.

<blockquote> which will be used if it is.</blockquote>

System Message: WARNING/2 (<string>, line 18) Block quote ends without a blank line; unexpected unindent.
'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_local' gets set to 1 if the parsed time was in local time,

System Message: ERROR/3 (<string>, line 23) Unexpected indentation.

<blockquote> to 0 otherwise. The values 'now' and 'today' don't get counted as local, and neither do UTC +/-#### timezone offsets, because they aren't using the computer's local timezone offset.</blockquote>

System Message: WARNING/2 (<string>, line 26) Block quote ends without a blank line; unexpected unindent.
'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.
Initialize the output to all zeros
Convert the empty string and case-variants of "NaT" to not-a-time. Tried to use PyOS_stricmp, but that function appears to be broken, not even matching the strcmp function signature as it should.
Indicate that this was a special value, and recommend generic units.
The string "today" means take today's date in local time, and convert it to a date representation. This date representation, if forced into a time unit, will be at midnight UTC. This is perhaps a little weird, but done so that the 'datetime64[D]' type produces the date you expect, rather than switching to an adjacent day depending on the current time and your timezone.
Indicate that this was a special value, and is a date (unit 'D').
Check the casting rule
The string "now" resolves to the current UTC time
Set up a dummy metadata for the conversion
Indicate that this was a special value, and use 's' because the time() function has resolution seconds.
Check the casting rule
Anything else isn't a special value
Skip leading whitespace
Leading '-' sign for negative year
PARSE THE YEAR (digits until the '-' character)
Negate the year if necessary
Check whether it's a leap-year
Next character must be a '-' or the end of the string
Can't have a trailing '-'
PARSE THE MONTH (2 digits)
Next character must be a '-' or the end of the string
Can't have a trailing '-'
PARSE THE DAY (2 digits)
Next character must be a 'T', ' ', or end of string
PARSE THE HOURS (2 digits)
Next character must be a ':' or the end of the string
Can't have a trailing ':'
PARSE THE MINUTES (2 digits)
Next character must be a ':' or the end of the string
Can't have a trailing ':'
PARSE THE SECONDS (2 digits)
Next character may be a '.' indicating fractional seconds
PARSE THE MICROSECONDS (0 to 6 digits)
PARSE THE PICOSECONDS (0 to 6 digits)
PARSE THE ATTOSECONDS (0 to 6 digits)
Since neither "Z" nor a time-zone was specified, it's local
UTC specifier
"Z" means not local
Time zone offset
Since "local" means local with respect to the current machine, we say this is non-local.
The hours offset
The minutes offset is optional
Optional ':'
The minutes offset (at the end of the string)
Apply the time zone offset
Skip trailing whitespace
Check the casting rule

References _datetime_strings, can_cast_datetime64_units(), npy_datetimestruct::day, get_localtime(), npy_datetimestruct::month, npy_casting_to_string(), NPY_FR_D, and npy_datetimestruct::year.

Referenced by _strided_to_strided_datetime_general_cast().