blob: a8550d95857066df7a557db9eaf3635eb80e146d [file] [log] [blame]
/*
* This file implements core functionality for NumPy datetime.
*
* Written by Mark Wiebe (mwwiebe@gmail.com)
* Copyright (c) 2011 by Enthought, Inc.
*
* See LICENSE.txt for the license.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <datetime.h>
#include <time.h>
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
#include <numpy/arrayobject.h>
#include "npy_config.h"
#include "npy_pycompat.h"
#include "common.h"
#include "numpy/arrayscalars.h"
#include "methods.h"
#include "_datetime.h"
#include "datetime_strings.h"
/*
* Imports the PyDateTime functions so we can create these objects.
* This is called during module initialization
*/
NPY_NO_EXPORT void
numpy_pydatetime_import(void)
{
PyDateTime_IMPORT;
}
/* Exported as DATETIMEUNITS in multiarraymodule.c */
NPY_NO_EXPORT char *_datetime_strings[NPY_DATETIME_NUMUNITS] = {
"Y",
"M",
"W",
"<invalid>",
"D",
"h",
"m",
"s",
"ms",
"us",
"ns",
"ps",
"fs",
"as",
"generic"
};
/* Days per month, regular year and leap year */
NPY_NO_EXPORT int _days_per_month_table[2][12] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
/*
* Returns 1 if the given year is a leap year, 0 otherwise.
*/
NPY_NO_EXPORT int
is_leapyear(npy_int64 year)
{
return (year & 0x3) == 0 && /* year % 4 == 0 */
((year % 100) != 0 ||
(year % 400) == 0);
}
/*
* Calculates the days offset from the 1970 epoch.
*/
NPY_NO_EXPORT npy_int64
get_datetimestruct_days(const npy_datetimestruct *dts)
{
int i, month;
npy_int64 year, days = 0;
int *month_lengths;
year = dts->year - 1970;
days = year * 365;
/* Adjust for leap years */
if (days >= 0) {
/*
* 1968 is the closest leap year before 1970.
* Exclude the current year, so add 1.
*/
year += 1;
/* Add one day for each 4 years */
days += year / 4;
/* 1900 is the closest previous year divisible by 100 */
year += 68;
/* Subtract one day for each 100 years */
days -= year / 100;
/* 1600 is the closest previous year divisible by 400 */
year += 300;
/* Add one day for each 400 years */
days += year / 400;
}
else {
/*
* 1972 is the closest later year after 1970.
* Include the current year, so subtract 2.
*/
year -= 2;
/* Subtract one day for each 4 years */
days += year / 4;
/* 2000 is the closest later year divisible by 100 */
year -= 28;
/* Add one day for each 100 years */
days -= year / 100;
/* 2000 is also the closest later year divisible by 400 */
/* Subtract one day for each 400 years */
days += year / 400;
}
month_lengths = _days_per_month_table[is_leapyear(dts->year)];
month = dts->month - 1;
/* Add the months */
for (i = 0; i < month; ++i) {
days += month_lengths[i];
}
/* Add the days */
days += dts->day - 1;
return days;
}
/*
* Calculates the minutes offset from the 1970 epoch.
*/
NPY_NO_EXPORT npy_int64
get_datetimestruct_minutes(const npy_datetimestruct *dts)
{
npy_int64 days = get_datetimestruct_days(dts) * 24 * 60;
days += dts->hour * 60;
days += dts->min;
return days;
}
/*
* Modifies '*days_' to be the day offset within the year,
* and returns the year.
*/
static npy_int64
days_to_yearsdays(npy_int64 *days_)
{
const npy_int64 days_per_400years = (400*365 + 100 - 4 + 1);
/* Adjust so it's relative to the year 2000 (divisible by 400) */
npy_int64 days = (*days_) - (365*30 + 7);
npy_int64 year;
/* Break down the 400 year cycle to get the year and day within the year */
if (days >= 0) {
year = 400 * (days / days_per_400years);
days = days % days_per_400years;
}
else {
year = 400 * ((days - (days_per_400years - 1)) / days_per_400years);
days = days % days_per_400years;
if (days < 0) {
days += days_per_400years;
}
}
/* Work out the year/day within the 400 year cycle */
if (days >= 366) {
year += 100 * ((days-1) / (100*365 + 25 - 1));
days = (days-1) % (100*365 + 25 - 1);
if (days >= 365) {
year += 4 * ((days+1) / (4*365 + 1));
days = (days+1) % (4*365 + 1);
if (days >= 366) {
year += (days-1) / 365;
days = (days-1) % 365;
}
}
}
*days_ = days;
return year + 2000;
}
/* Extracts the month number from a 'datetime64[D]' value */
NPY_NO_EXPORT int
days_to_month_number(npy_datetime days)
{
npy_int64 year;
int *month_lengths, i;
year = days_to_yearsdays(&days);
month_lengths = _days_per_month_table[is_leapyear(year)];
for (i = 0; i < 12; ++i) {
if (days < month_lengths[i]) {
return i + 1;
}
else {
days -= month_lengths[i];
}
}
/* Should never get here */
return 1;
}
/*
* Fills in the year, month, day in 'dts' based on the days
* offset from 1970.
*/
static void
set_datetimestruct_days(npy_int64 days, npy_datetimestruct *dts)
{
int *month_lengths, i;
dts->year = days_to_yearsdays(&days);
month_lengths = _days_per_month_table[is_leapyear(dts->year)];
for (i = 0; i < 12; ++i) {
if (days < month_lengths[i]) {
dts->month = i + 1;
dts->day = (int)days + 1;
return;
}
else {
days -= month_lengths[i];
}
}
}
/*
* Converts a datetime from a datetimestruct to a datetime based
* on some metadata. The date is assumed to be valid.
*
* TODO: If meta->num is really big, there could be overflow
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta,
const npy_datetimestruct *dts,
npy_datetime *out)
{
npy_datetime ret;
NPY_DATETIMEUNIT base = meta->base;
/* If the datetimestruct is NaT, return NaT */
if (dts->year == NPY_DATETIME_NAT) {
*out = NPY_DATETIME_NAT;
return 0;
}
/* Cannot instantiate a datetime with generic units */
if (meta->base == NPY_FR_GENERIC) {
PyErr_SetString(PyExc_ValueError,
"Cannot create a NumPy datetime other than NaT "
"with generic units");
return -1;
}
if (base == NPY_FR_Y) {
/* Truncate to the year */
ret = dts->year - 1970;
}
else if (base == NPY_FR_M) {
/* Truncate to the month */
ret = 12 * (dts->year - 1970) + (dts->month - 1);
}
else {
/* Otherwise calculate the number of days to start */
npy_int64 days = get_datetimestruct_days(dts);
switch (base) {
case NPY_FR_W:
/* Truncate to weeks */
if (days >= 0) {
ret = days / 7;
}
else {
ret = (days - 6) / 7;
}
break;
case NPY_FR_D:
ret = days;
break;
case NPY_FR_h:
ret = days * 24 +
dts->hour;
break;
case NPY_FR_m:
ret = (days * 24 +
dts->hour) * 60 +
dts->min;
break;
case NPY_FR_s:
ret = ((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec;
break;
case NPY_FR_ms:
ret = (((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000 +
dts->us / 1000;
break;
case NPY_FR_us:
ret = (((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000000 +
dts->us;
break;
case NPY_FR_ns:
ret = ((((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000000 +
dts->us) * 1000 +
dts->ps / 1000;
break;
case NPY_FR_ps:
ret = ((((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000000 +
dts->us) * 1000000 +
dts->ps;
break;
case NPY_FR_fs:
/* only 2.6 hours */
ret = (((((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000000 +
dts->us) * 1000000 +
dts->ps) * 1000 +
dts->as / 1000;
break;
case NPY_FR_as:
/* only 9.2 secs */
ret = (((((days * 24 +
dts->hour) * 60 +
dts->min) * 60 +
dts->sec) * 1000000 +
dts->us) * 1000000 +
dts->ps) * 1000000 +
dts->as;
break;
default:
/* Something got corrupted */
PyErr_SetString(PyExc_ValueError,
"NumPy datetime metadata with corrupt unit value");
return -1;
}
}
/* Divide by the multiplier */
if (meta->num > 1) {
if (ret >= 0) {
ret /= meta->num;
}
else {
ret = (ret - meta->num + 1) / meta->num;
}
}
*out = ret;
return 0;
}
/*NUMPY_API
* Create a datetime value from a filled datetime struct and resolution unit.
*
* TO BE REMOVED - NOT USED INTERNALLY.
*/
NPY_NO_EXPORT npy_datetime
PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d)
{
PyErr_SetString(PyExc_RuntimeError,
"The NumPy PyArray_DatetimeStructToDatetime function has "
"been removed");
return -1;
}
/*NUMPY_API
* Create a timdelta value from a filled timedelta struct and resolution unit.
*
* TO BE REMOVED - NOT USED INTERNALLY.
*/
NPY_NO_EXPORT npy_datetime
PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d)
{
PyErr_SetString(PyExc_RuntimeError,
"The NumPy PyArray_TimedeltaStructToTimedelta function has "
"been removed");
return -1;
}
/*
* Converts a datetime based on the given metadata into a datetimestruct
*/
NPY_NO_EXPORT int
convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta,
npy_datetime dt,
npy_datetimestruct *out)
{
npy_int64 perday;
/* Initialize the output to all zeros */
memset(out, 0, sizeof(npy_datetimestruct));
out->year = 1970;
out->month = 1;
out->day = 1;
/* NaT is signaled in the year */
if (dt == NPY_DATETIME_NAT) {
out->year = NPY_DATETIME_NAT;
return 0;
}
/* Datetimes can't be in generic units */
if (meta->base == NPY_FR_GENERIC) {
PyErr_SetString(PyExc_ValueError,
"Cannot convert a NumPy datetime value other than NaT "
"with generic units");
return -1;
}
/* TODO: Change to a mechanism that avoids the potential overflow */
dt *= meta->num;
/*
* Note that care must be taken with the / and % operators
* for negative values.
*/
switch (meta->base) {
case NPY_FR_Y:
out->year = 1970 + dt;
break;
case NPY_FR_M:
if (dt >= 0) {
out->year = 1970 + dt / 12;
out->month = dt % 12 + 1;
}
else {
out->year = 1969 + (dt + 1) / 12;
out->month = 12 + (dt + 1)% 12;
}
break;
case NPY_FR_W:
/* A week is 7 days */
set_datetimestruct_days(dt * 7, out);
break;
case NPY_FR_D:
set_datetimestruct_days(dt, out);
break;
case NPY_FR_h:
perday = 24LL;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)dt;
break;
case NPY_FR_m:
perday = 24LL * 60;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / 60);
out->min = (int)(dt % 60);
break;
case NPY_FR_s:
perday = 24LL * 60 * 60;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / (60*60));
out->min = (int)((dt / 60) % 60);
out->sec = (int)(dt % 60);
break;
case NPY_FR_ms:
perday = 24LL * 60 * 60 * 1000;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / (60*60*1000LL));
out->min = (int)((dt / (60*1000LL)) % 60);
out->sec = (int)((dt / 1000LL) % 60);
out->us = (int)((dt % 1000LL) * 1000);
break;
case NPY_FR_us:
perday = 24LL * 60LL * 60LL * 1000LL * 1000LL;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / (60*60*1000000LL));
out->min = (int)((dt / (60*1000000LL)) % 60);
out->sec = (int)((dt / 1000000LL) % 60);
out->us = (int)(dt % 1000000LL);
break;
case NPY_FR_ns:
perday = 24LL * 60LL * 60LL * 1000LL * 1000LL * 1000LL;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / (60*60*1000000000LL));
out->min = (int)((dt / (60*1000000000LL)) % 60);
out->sec = (int)((dt / 1000000000LL) % 60);
out->us = (int)((dt / 1000LL) % 1000000LL);
out->ps = (int)((dt % 1000LL) * 1000);
break;
case NPY_FR_ps:
perday = 24LL * 60 * 60 * 1000 * 1000 * 1000 * 1000;
if (dt >= 0) {
set_datetimestruct_days(dt / perday, out);
dt = dt % perday;
}
else {
set_datetimestruct_days((dt - (perday-1)) / perday, out);
dt = (perday-1) + (dt + 1) % perday;
}
out->hour = (int)(dt / (60*60*1000000000000LL));
out->min = (int)((dt / (60*1000000000000LL)) % 60);
out->sec = (int)((dt / 1000000000000LL) % 60);
out->us = (int)((dt / 1000000LL) % 1000000LL);
out->ps = (int)(dt % 1000000LL);
break;
case NPY_FR_fs:
/* entire range is only +- 2.6 hours */
if (dt >= 0) {
out->hour = (int)(dt / (60*60*1000000000000000LL));
out->min = (int)((dt / (60*1000000000000000LL)) % 60);
out->sec = (int)((dt / 1000000000000000LL) % 60);
out->us = (int)((dt / 1000000000LL) % 1000000LL);
out->ps = (int)((dt / 1000LL) % 1000000LL);
out->as = (int)((dt % 1000LL) * 1000);
}
else {
npy_datetime minutes;
minutes = dt / (60*1000000000000000LL);
dt = dt % (60*1000000000000000LL);
if (dt < 0) {
dt += (60*1000000000000000LL);
--minutes;
}
/* Offset the negative minutes */
add_minutes_to_datetimestruct(out, minutes);
out->sec = (int)((dt / 1000000000000000LL) % 60);
out->us = (int)((dt / 1000000000LL) % 1000000LL);
out->ps = (int)((dt / 1000LL) % 1000000LL);
out->as = (int)((dt % 1000LL) * 1000);
}
break;
case NPY_FR_as:
/* entire range is only +- 9.2 seconds */
if (dt >= 0) {
out->sec = (int)((dt / 1000000000000000000LL) % 60);
out->us = (int)((dt / 1000000000000LL) % 1000000LL);
out->ps = (int)((dt / 1000000LL) % 1000000LL);
out->as = (int)(dt % 1000000LL);
}
else {
npy_datetime seconds;
seconds = dt / 1000000000000000000LL;
dt = dt % 1000000000000000000LL;
if (dt < 0) {
dt += 1000000000000000000LL;
--seconds;
}
/* Offset the negative seconds */
add_seconds_to_datetimestruct(out, seconds);
out->us = (int)((dt / 1000000000000LL) % 1000000LL);
out->ps = (int)((dt / 1000000LL) % 1000000LL);
out->as = (int)(dt % 1000000LL);
}
break;
default:
PyErr_SetString(PyExc_RuntimeError,
"NumPy datetime metadata is corrupted with invalid "
"base unit");
return -1;
}
return 0;
}
/*NUMPY_API
* Fill the datetime struct from the value and resolution unit.
*
* TO BE REMOVED - NOT USED INTERNALLY.
*/
NPY_NO_EXPORT void
PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr,
npy_datetimestruct *result)
{
PyErr_SetString(PyExc_RuntimeError,
"The NumPy PyArray_DatetimeToDatetimeStruct function has "
"been removed");
memset(result, -1, sizeof(npy_datetimestruct));
}
/*
* FIXME: Overflow is not handled at all
* To convert from Years or Months,
* multiplication by the average is done
*/
/*NUMPY_API
* Fill the timedelta struct from the timedelta value and resolution unit.
*
* TO BE REMOVED - NOT USED INTERNALLY.
*/
NPY_NO_EXPORT void
PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr,
npy_timedeltastruct *result)
{
PyErr_SetString(PyExc_RuntimeError,
"The NumPy PyArray_TimedeltaToTimedeltaStruct function has "
"been removed");
memset(result, -1, sizeof(npy_timedeltastruct));
}
/*
* Creates a datetime or timedelta dtype using a copy of the provided metadata.
*/
NPY_NO_EXPORT PyArray_Descr *
create_datetime_dtype(int type_num, PyArray_DatetimeMetaData *meta)
{
PyArray_Descr *dtype = NULL;
PyArray_DatetimeMetaData *dt_data;
/* Create a default datetime or timedelta */
if (type_num == NPY_DATETIME || type_num == NPY_TIMEDELTA) {
dtype = PyArray_DescrNewFromType(type_num);
}
else {
PyErr_SetString(PyExc_RuntimeError,
"Asked to create a datetime type with a non-datetime "
"type number");
return NULL;
}
if (dtype == NULL) {
return NULL;
}
dt_data = &(((PyArray_DatetimeDTypeMetaData *)dtype->c_metadata)->meta);
/* Copy the metadata */
*dt_data = *meta;
return dtype;
}
/*
* Creates a datetime or timedelta dtype using the given unit.
*/
NPY_NO_EXPORT PyArray_Descr *
create_datetime_dtype_with_unit(int type_num, NPY_DATETIMEUNIT unit)
{
PyArray_DatetimeMetaData meta;
meta.base = unit;
meta.num = 1;
return create_datetime_dtype(type_num, &meta);
}
/*
* This function returns a pointer to the DateTimeMetaData
* contained within the provided datetime dtype.
*/
NPY_NO_EXPORT PyArray_DatetimeMetaData *
get_datetime_metadata_from_dtype(PyArray_Descr *dtype)
{
if (!PyDataType_ISDATETIME(dtype)) {
PyErr_SetString(PyExc_TypeError,
"cannot get datetime metadata from non-datetime type");
return NULL;
}
return &(((PyArray_DatetimeDTypeMetaData *)dtype->c_metadata)->meta);
}
/*
* Converts a substring given by 'str' and 'len' into
* a date time unit multiplier + enum value, which are populated
* into out_meta. Other metadata is left along.
*
* 'metastr' is only used in the error message, and may be NULL.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
parse_datetime_extended_unit_from_string(char *str, Py_ssize_t len,
char *metastr,
PyArray_DatetimeMetaData *out_meta)
{
char *substr = str, *substrend = NULL;
int den = 1;
/* First comes an optional integer multiplier */
out_meta->num = (int)strtol(substr, &substrend, 10);
if (substr == substrend) {
out_meta->num = 1;
}
substr = substrend;
/* Next comes the unit itself, followed by either '/' or the string end */
substrend = substr;
while (substrend-str < len && *substrend != '/') {
++substrend;
}
if (substr == substrend) {
goto bad_input;
}
out_meta->base = parse_datetime_unit_from_string(substr,
substrend - substr,
metastr);
if (out_meta->base == NPY_FR_ERROR ) {
return -1;
}
substr = substrend;
/* Next comes an optional integer denominator */
if (substr-str < len && *substr == '/') {
substr++;
den = (int)strtol(substr, &substrend, 10);
/* If the '/' exists, there must be a number followed by ']' */
if (substr == substrend || *substrend != ']') {
goto bad_input;
}
substr = substrend + 1;
}
else if (substr-str != len) {
goto bad_input;
}
if (den != 1) {
if (convert_datetime_divisor_to_multiple(
out_meta, den, metastr) < 0) {
return -1;
}
}
return 0;
bad_input:
if (metastr != NULL) {
PyErr_Format(PyExc_TypeError,
"Invalid datetime metadata string \"%s\" at position %d",
metastr, (int)(substr-metastr));
}
else {
PyErr_Format(PyExc_TypeError,
"Invalid datetime metadata string \"%s\"",
str);
}
return -1;
}
/*
* Parses the metadata string into the metadata C structure.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
parse_datetime_metadata_from_metastr(char *metastr, Py_ssize_t len,
PyArray_DatetimeMetaData *out_meta)
{
char *substr = metastr, *substrend = NULL;
/* Treat the empty string as generic units */
if (len == 0) {
out_meta->base = NPY_FR_GENERIC;
out_meta->num = 1;
return 0;
}
/* The metadata string must start with a '[' */
if (len < 3 || *substr++ != '[') {
goto bad_input;
}
substrend = substr;
while (substrend - metastr < len && *substrend != ']') {
++substrend;
}
if (substrend - metastr == len || substr == substrend) {
substr = substrend;
goto bad_input;
}
/* Parse the extended unit inside the [] */
if (parse_datetime_extended_unit_from_string(substr, substrend-substr,
metastr, out_meta) < 0) {
return -1;
}
substr = substrend+1;
if (substr - metastr != len) {
goto bad_input;
}
return 0;
bad_input:
if (substr != metastr) {
PyErr_Format(PyExc_TypeError,
"Invalid datetime metadata string \"%s\" at position %d",
metastr, (int)(substr-metastr));
}
else {
PyErr_Format(PyExc_TypeError,
"Invalid datetime metadata string \"%s\"",
metastr);
}
return -1;
}
/*
* Converts a datetype dtype string into a dtype descr object.
* The "type" string should be NULL-terminated.
*/
NPY_NO_EXPORT PyArray_Descr *
parse_dtype_from_datetime_typestr(char *typestr, Py_ssize_t len)
{
PyArray_DatetimeMetaData meta;
char *metastr = NULL;
int is_timedelta = 0;
Py_ssize_t metalen = 0;
if (len < 2) {
PyErr_Format(PyExc_TypeError,
"Invalid datetime typestr \"%s\"",
typestr);
return NULL;
}
/*
* First validate that the root is correct,
* and get the metadata string address
*/
if (typestr[0] == 'm' && typestr[1] == '8') {
is_timedelta = 1;
metastr = typestr + 2;
metalen = len - 2;
}
else if (typestr[0] == 'M' && typestr[1] == '8') {
is_timedelta = 0;
metastr = typestr + 2;
metalen = len - 2;
}
else if (len >= 11 && strncmp(typestr, "timedelta64", 11) == 0) {
is_timedelta = 1;
metastr = typestr + 11;
metalen = len - 11;
}
else if (len >= 10 && strncmp(typestr, "datetime64", 10) == 0) {
is_timedelta = 0;
metastr = typestr + 10;
metalen = len - 10;
}
else {
PyErr_Format(PyExc_TypeError,
"Invalid datetime typestr \"%s\"",
typestr);
return NULL;
}
/* Parse the metadata string into a metadata struct */
if (parse_datetime_metadata_from_metastr(metastr, metalen, &meta) < 0) {
return NULL;
}
return create_datetime_dtype(is_timedelta ? NPY_TIMEDELTA : NPY_DATETIME,
&meta);
}
static NPY_DATETIMEUNIT _multiples_table[16][4] = {
{12, 52, 365}, /* NPY_FR_Y */
{NPY_FR_M, NPY_FR_W, NPY_FR_D},
{4, 30, 720}, /* NPY_FR_M */
{NPY_FR_W, NPY_FR_D, NPY_FR_h},
{7, 168, 10080}, /* NPY_FR_W */
{NPY_FR_D, NPY_FR_h, NPY_FR_m},
{0}, /* Gap for removed NPY_FR_B */
{0},
{24, 1440, 86400}, /* NPY_FR_D */
{NPY_FR_h, NPY_FR_m, NPY_FR_s},
{60, 3600}, /* NPY_FR_h */
{NPY_FR_m, NPY_FR_s},
{60, 60000}, /* NPY_FR_m */
{NPY_FR_s, NPY_FR_ms},
{1000, 1000000}, /* >=NPY_FR_s */
{0, 0}
};
/*
* Translate divisors into multiples of smaller units.
* 'metastr' is used for the error message if the divisor doesn't work,
* and can be NULL if the metadata didn't come from a string.
*
* This function only affects the 'base' and 'num' values in the metadata.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta,
int den, char *metastr)
{
int i, num, ind;
NPY_DATETIMEUNIT *totry;
NPY_DATETIMEUNIT *baseunit;
int q, r;
if (meta->base == NPY_FR_GENERIC) {
PyErr_SetString(PyExc_ValueError,
"Can't use 'den' divisor with generic units");
return -1;
}
ind = ((int)meta->base - (int)NPY_FR_Y)*2;
totry = _multiples_table[ind];
baseunit = _multiples_table[ind + 1];
num = 3;
if (meta->base == NPY_FR_W) {
num = 4;
}
else if (meta->base > NPY_FR_D) {
num = 2;
}
if (meta->base >= NPY_FR_s) {
ind = ((int)NPY_FR_s - (int)NPY_FR_Y)*2;
totry = _multiples_table[ind];
baseunit = _multiples_table[ind + 1];
baseunit[0] = meta->base + 1;
baseunit[1] = meta->base + 2;
if (meta->base == NPY_FR_as - 1) {
num = 1;
}
if (meta->base == NPY_FR_as) {
num = 0;
}
}
for (i = 0; i < num; i++) {
q = totry[i] / den;
r = totry[i] % den;
if (r == 0) {
break;
}
}
if (i == num) {
if (metastr == NULL) {
PyErr_Format(PyExc_ValueError,
"divisor (%d) is not a multiple of a lower-unit "
"in datetime metadata", den);
}
else {
PyErr_Format(PyExc_ValueError,
"divisor (%d) is not a multiple of a lower-unit "
"in datetime metadata \"%s\"", den, metastr);
}
return -1;
}
meta->base = baseunit[i];
meta->num *= q;
return 0;
}
/*
* Lookup table for factors between datetime units, except
* for years and months.
*/
static npy_uint32
_datetime_factors[] = {
1, /* Years - not used */
1, /* Months - not used */
7, /* Weeks -> Days */
1, /* Business Days - was removed but a gap still exists in the enum */
24, /* Days -> Hours */
60, /* Hours -> Minutes */
60, /* Minutes -> Seconds */
1000,
1000,
1000,
1000,
1000,
1000,
1, /* Attoseconds are the smallest base unit */
0 /* Generic units don't have a conversion */
};
/*
* Returns the scale factor between the units. Does not validate
* that bigbase represents larger units than littlebase, or that
* the units are not generic.
*
* Returns 0 if there is an overflow.
*/
static npy_uint64
get_datetime_units_factor(NPY_DATETIMEUNIT bigbase, NPY_DATETIMEUNIT littlebase)
{
npy_uint64 factor = 1;
NPY_DATETIMEUNIT unit = bigbase;
while (unit < littlebase) {
factor *= _datetime_factors[unit];
/*
* Detect overflow by disallowing the top 16 bits to be 1.
* That allows a margin of error much bigger than any of
* the datetime factors.
*/
if (factor&0xff00000000000000ULL) {
return 0;
}
++unit;
}
return factor;
}
/* Euclidean algorithm on two positive numbers */
static npy_uint64
_uint64_euclidean_gcd(npy_uint64 x, npy_uint64 y)
{
npy_uint64 tmp;
if (x > y) {
tmp = x;
x = y;
y = tmp;
}
while (x != y && y != 0) {
tmp = x % y;
x = y;
y = tmp;
}
return x;
}
/*
* Computes the conversion factor to convert data with 'src_meta' metadata
* into data with 'dst_meta' metadata.
*
* If overflow occurs, both out_num and out_denom are set to 0, but
* no error is set.
*/
NPY_NO_EXPORT void
get_datetime_conversion_factor(PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
npy_int64 *out_num, npy_int64 *out_denom)
{
int src_base, dst_base, swapped;
npy_uint64 num = 1, denom = 1, tmp, gcd;
/* Generic units change to the destination with no conversion factor */
if (src_meta->base == NPY_FR_GENERIC) {
*out_num = 1;
*out_denom = 1;
return;
}
/*
* Converting to a generic unit from something other than a generic
* unit is an error.
*/
else if (dst_meta->base == NPY_FR_GENERIC) {
PyErr_SetString(PyExc_ValueError,
"Cannot convert from specific units to generic "
"units in NumPy datetimes or timedeltas");
*out_num = 0;
*out_denom = 0;
return;
}
if (src_meta->base <= dst_meta->base) {
src_base = src_meta->base;
dst_base = dst_meta->base;
swapped = 0;
}
else {
src_base = dst_meta->base;
dst_base = src_meta->base;
swapped = 1;
}
if (src_base != dst_base) {
/*
* Conversions between years/months and other units use
* the factor averaged over the 400 year leap year cycle.
*/
if (src_base == NPY_FR_Y) {
if (dst_base == NPY_FR_M) {
num *= 12;
}
else if (dst_base == NPY_FR_W) {
num *= (97 + 400*365);
denom *= 400*7;
}
else {
/* Year -> Day */
num *= (97 + 400*365);
denom *= 400;
/* Day -> dst_base */
num *= get_datetime_units_factor(NPY_FR_D, dst_base);
}
}
else if (src_base == NPY_FR_M) {
if (dst_base == NPY_FR_W) {
num *= (97 + 400*365);
denom *= 400*12*7;
}
else {
/* Month -> Day */
num *= (97 + 400*365);
denom *= 400*12;
/* Day -> dst_base */
num *= get_datetime_units_factor(NPY_FR_D, dst_base);
}
}
else {
num *= get_datetime_units_factor(src_base, dst_base);
}
}
/* If something overflowed, make both num and denom 0 */
if (denom == 0 || num == 0) {
PyErr_Format(PyExc_OverflowError,
"Integer overflow while computing the conversion "
"factor between NumPy datetime units %s and %s",
_datetime_strings[src_base],
_datetime_strings[dst_base]);
*out_num = 0;
*out_denom = 0;
return;
}
/* Swap the numerator and denominator if necessary */
if (swapped) {
tmp = num;
num = denom;
denom = tmp;
}
num *= src_meta->num;
denom *= dst_meta->num;
/* Return as a fraction in reduced form */
gcd = _uint64_euclidean_gcd(num, denom);
*out_num = (npy_int64)(num / gcd);
*out_denom = (npy_int64)(denom / gcd);
}
/*
* Determines whether the 'divisor' metadata divides evenly into
* the 'dividend' metadata.
*/
NPY_NO_EXPORT npy_bool
datetime_metadata_divides(
PyArray_DatetimeMetaData *dividend,
PyArray_DatetimeMetaData *divisor,
int strict_with_nonlinear_units)
{
npy_uint64 num1, num2;
/*
* Any unit can always divide into generic units. In other words, we
* should be able to convert generic units into any more specific unit.
*/
if (dividend->base == NPY_FR_GENERIC) {
return 1;
}
/*
* However, generic units cannot always divide into more specific units.
* We cannot safely convert datetimes with units back into generic units.
*/
else if (divisor->base == NPY_FR_GENERIC) {
return 0;
}
num1 = (npy_uint64)dividend->num;
num2 = (npy_uint64)divisor->num;
/* If the bases are different, factor in a conversion */
if (dividend->base != divisor->base) {
/*
* Years and Months are incompatible with
* all other units (except years and months are compatible
* with each other).
*/
if (dividend->base == NPY_FR_Y) {
if (divisor->base == NPY_FR_M) {
num1 *= 12;
}
else if (strict_with_nonlinear_units) {
return 0;
}
else {
/* Could do something complicated here */
return 1;
}
}
else if (divisor->base == NPY_FR_Y) {
if (dividend->base == NPY_FR_M) {
num2 *= 12;
}
else if (strict_with_nonlinear_units) {
return 0;
}
else {
/* Could do something complicated here */
return 1;
}
}
else if (dividend->base == NPY_FR_M || divisor->base == NPY_FR_M) {
if (strict_with_nonlinear_units) {
return 0;
}
else {
/* Could do something complicated here */
return 1;
}
}
/* Take the greater base (unit sizes are decreasing in enum) */
if (dividend->base > divisor->base) {
num2 *= get_datetime_units_factor(divisor->base, dividend->base);
if (num2 == 0) {
return 0;
}
}
else {
num1 *= get_datetime_units_factor(dividend->base, divisor->base);
if (num1 == 0) {
return 0;
}
}
}
/* Crude, incomplete check for overflow */
if (num1&0xff00000000000000LL || num2&0xff00000000000000LL ) {
return 0;
}
return (num1 % num2) == 0;
}
/*
* This provides the casting rules for the DATETIME data type units.
*/
NPY_NO_EXPORT npy_bool
can_cast_datetime64_units(NPY_DATETIMEUNIT src_unit,
NPY_DATETIMEUNIT dst_unit,
NPY_CASTING casting)
{
switch (casting) {
/* Allow anything with unsafe casting */
case NPY_UNSAFE_CASTING:
return 1;
/*
* Can cast between all units with 'same_kind' casting.
*/
case NPY_SAME_KIND_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
return src_unit == NPY_FR_GENERIC;
}
else {
return 1;
}
/*
* Casting is only allowed towards more precise units with 'safe'
* casting.
*/
case NPY_SAFE_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= dst_unit);
}
/* Enforce equality with 'no' or 'equiv' casting */
default:
return src_unit == dst_unit;
}
}
/*
* This provides the casting rules for the TIMEDELTA data type units.
*
* Notably, there is a barrier between the nonlinear years and
* months units, and all the other units.
*/
NPY_NO_EXPORT npy_bool
can_cast_timedelta64_units(NPY_DATETIMEUNIT src_unit,
NPY_DATETIMEUNIT dst_unit,
NPY_CASTING casting)
{
switch (casting) {
/* Allow anything with unsafe casting */
case NPY_UNSAFE_CASTING:
return 1;
/*
* Only enforce the 'date units' vs 'time units' barrier with
* 'same_kind' casting.
*/
case NPY_SAME_KIND_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= NPY_FR_M && dst_unit <= NPY_FR_M) ||
(src_unit > NPY_FR_M && dst_unit > NPY_FR_M);
}
/*
* Enforce the 'date units' vs 'time units' barrier and that
* casting is only allowed towards more precise units with
* 'safe' casting.
*/
case NPY_SAFE_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= dst_unit) &&
((src_unit <= NPY_FR_M && dst_unit <= NPY_FR_M) ||
(src_unit > NPY_FR_M && dst_unit > NPY_FR_M));
}
/* Enforce equality with 'no' or 'equiv' casting */
default:
return src_unit == dst_unit;
}
}
/*
* This provides the casting rules for the DATETIME data type metadata.
*/
NPY_NO_EXPORT npy_bool
can_cast_datetime64_metadata(PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
NPY_CASTING casting)
{
switch (casting) {
case NPY_UNSAFE_CASTING:
return 1;
case NPY_SAME_KIND_CASTING:
return can_cast_datetime64_units(src_meta->base, dst_meta->base,
casting);
case NPY_SAFE_CASTING:
return can_cast_datetime64_units(src_meta->base, dst_meta->base,
casting) &&
datetime_metadata_divides(src_meta, dst_meta, 0);
default:
return src_meta->base == dst_meta->base &&
src_meta->num == dst_meta->num;
}
}
/*
* This provides the casting rules for the TIMEDELTA data type metadata.
*/
NPY_NO_EXPORT npy_bool
can_cast_timedelta64_metadata(PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
NPY_CASTING casting)
{
switch (casting) {
case NPY_UNSAFE_CASTING:
return 1;
case NPY_SAME_KIND_CASTING:
return can_cast_timedelta64_units(src_meta->base, dst_meta->base,
casting);
case NPY_SAFE_CASTING:
return can_cast_timedelta64_units(src_meta->base, dst_meta->base,
casting) &&
datetime_metadata_divides(src_meta, dst_meta, 1);
default:
return src_meta->base == dst_meta->base &&
src_meta->num == dst_meta->num;
}
}
/*
* Tests whether a datetime64 can be cast from the source metadata
* to the destination metadata according to the specified casting rule.
*
* Returns -1 if an exception was raised, 0 otherwise.
*/
NPY_NO_EXPORT int
raise_if_datetime64_metadata_cast_error(char *object_type,
PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
NPY_CASTING casting)
{
if (can_cast_datetime64_metadata(src_meta, dst_meta, casting)) {
return 0;
}
else {
PyObject *errmsg;
errmsg = PyUString_FromFormat("Cannot cast %s "
"from metadata ", object_type);
errmsg = append_metastr_to_string(src_meta, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromString(" to "));
errmsg = append_metastr_to_string(dst_meta, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromFormat(" according to the rule %s",
npy_casting_to_string(casting)));
PyErr_SetObject(PyExc_TypeError, errmsg);
Py_DECREF(errmsg);
return -1;
}
}
/*
* Tests whether a timedelta64 can be cast from the source metadata
* to the destination metadata according to the specified casting rule.
*
* Returns -1 if an exception was raised, 0 otherwise.
*/
NPY_NO_EXPORT int
raise_if_timedelta64_metadata_cast_error(char *object_type,
PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
NPY_CASTING casting)
{
if (can_cast_timedelta64_metadata(src_meta, dst_meta, casting)) {
return 0;
}
else {
PyObject *errmsg;
errmsg = PyUString_FromFormat("Cannot cast %s "
"from metadata ", object_type);
errmsg = append_metastr_to_string(src_meta, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromString(" to "));
errmsg = append_metastr_to_string(dst_meta, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromFormat(" according to the rule %s",
npy_casting_to_string(casting)));
PyErr_SetObject(PyExc_TypeError, errmsg);
Py_DECREF(errmsg);
return -1;
}
}
/*
* Computes the GCD of the two date-time metadata values. Raises
* an exception if there is no reasonable GCD, such as with
* years and days.
*
* The result is placed in 'out_meta'.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
compute_datetime_metadata_greatest_common_divisor(
PyArray_DatetimeMetaData *meta1,
PyArray_DatetimeMetaData *meta2,
PyArray_DatetimeMetaData *out_meta,
int strict_with_nonlinear_units1,
int strict_with_nonlinear_units2)
{
NPY_DATETIMEUNIT base;
npy_uint64 num1, num2, num;
/* If either unit is generic, adopt the metadata from the other one */
if (meta1->base == NPY_FR_GENERIC) {
*out_meta = *meta2;
return 0;
}
else if (meta2->base == NPY_FR_GENERIC) {
*out_meta = *meta1;
return 0;
}
num1 = (npy_uint64)meta1->num;
num2 = (npy_uint64)meta2->num;
/* First validate that the units have a reasonable GCD */
if (meta1->base == meta2->base) {
base = meta1->base;
}
else {
/*
* Years and Months are incompatible with
* all other units (except years and months are compatible
* with each other).
*/
if (meta1->base == NPY_FR_Y) {
if (meta2->base == NPY_FR_M) {
base = NPY_FR_M;
num1 *= 12;
}
else if (strict_with_nonlinear_units1) {
goto incompatible_units;
}
else {
base = meta2->base;
/* Don't multiply num1 since there is no even factor */
}
}
else if (meta2->base == NPY_FR_Y) {
if (meta1->base == NPY_FR_M) {
base = NPY_FR_M;
num2 *= 12;
}
else if (strict_with_nonlinear_units2) {
goto incompatible_units;
}
else {
base = meta1->base;
/* Don't multiply num2 since there is no even factor */
}
}
else if (meta1->base == NPY_FR_M) {
if (strict_with_nonlinear_units1) {
goto incompatible_units;
}
else {
base = meta2->base;
/* Don't multiply num1 since there is no even factor */
}
}
else if (meta2->base == NPY_FR_M) {
if (strict_with_nonlinear_units2) {
goto incompatible_units;
}
else {
base = meta1->base;
/* Don't multiply num2 since there is no even factor */
}
}
/* Take the greater base (unit sizes are decreasing in enum) */
if (meta1->base > meta2->base) {
base = meta1->base;
num2 *= get_datetime_units_factor(meta2->base, meta1->base);
if (num2 == 0) {
goto units_overflow;
}
}
else {
base = meta2->base;
num1 *= get_datetime_units_factor(meta1->base, meta2->base);
if (num1 == 0) {
goto units_overflow;
}
}
}
/* Compute the GCD of the resulting multipliers */
num = _uint64_euclidean_gcd(num1, num2);
/* Fill the 'out_meta' values */
out_meta->base = base;
out_meta->num = (int)num;
if (out_meta->num <= 0 || num != (npy_uint64)out_meta->num) {
goto units_overflow;
}
return 0;
incompatible_units: {
PyObject *errmsg;
errmsg = PyUString_FromString("Cannot get "
"a common metadata divisor for "
"NumPy datetime metadata ");
errmsg = append_metastr_to_string(meta1, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromString(" and "));
errmsg = append_metastr_to_string(meta2, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromString(" because they have "
"incompatible nonlinear base time units"));
PyErr_SetObject(PyExc_TypeError, errmsg);
Py_DECREF(errmsg);
return -1;
}
units_overflow: {
PyObject *errmsg;
errmsg = PyUString_FromString("Integer overflow "
"getting a common metadata divisor for "
"NumPy datetime metadata ");
errmsg = append_metastr_to_string(meta1, 0, errmsg);
PyUString_ConcatAndDel(&errmsg,
PyUString_FromString(" and "));
errmsg = append_metastr_to_string(meta2, 0, errmsg);
PyErr_SetObject(PyExc_OverflowError, errmsg);
Py_DECREF(errmsg);
return -1;
}
}
/*
* Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA.
* Applies the type promotion rules between the two types, returning
* the promoted type.
*/
NPY_NO_EXPORT PyArray_Descr *
datetime_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2)
{
int type_num1, type_num2;
PyArray_Descr *dtype;
int is_datetime;
type_num1 = type1->type_num;
type_num2 = type2->type_num;
is_datetime = (type_num1 == NPY_DATETIME || type_num2 == NPY_DATETIME);
/* Create a DATETIME or TIMEDELTA dtype */
dtype = PyArray_DescrNewFromType(is_datetime ? NPY_DATETIME :
NPY_TIMEDELTA);
if (dtype == NULL) {
return NULL;
}
/*
* Get the metadata GCD, being strict about nonlinear units for
* timedelta and relaxed for datetime.
*/
if (compute_datetime_metadata_greatest_common_divisor(
get_datetime_metadata_from_dtype(type1),
get_datetime_metadata_from_dtype(type2),
get_datetime_metadata_from_dtype(dtype),
type_num1 == NPY_TIMEDELTA,
type_num2 == NPY_TIMEDELTA) < 0) {
Py_DECREF(dtype);
return NULL;
}
return dtype;
}
/*
* Converts a substring given by 'str' and 'len' into
* a date time unit enum value. The 'metastr' parameter
* is used for error messages, and may be NULL.
*
* Returns NPY_DATETIMEUNIT on success, NPY_FR_ERROR on failure.
*/
NPY_NO_EXPORT NPY_DATETIMEUNIT
parse_datetime_unit_from_string(char *str, Py_ssize_t len, char *metastr)
{
/* Use switch statements so the compiler can make it fast */
if (len == 1) {
switch (str[0]) {
case 'Y':
return NPY_FR_Y;
case 'M':
return NPY_FR_M;
case 'W':
return NPY_FR_W;
case 'D':
return NPY_FR_D;
case 'h':
return NPY_FR_h;
case 'm':
return NPY_FR_m;
case 's':
return NPY_FR_s;
}
}
/* All the two-letter units are variants of seconds */
else if (len == 2 && str[1] == 's') {
switch (str[0]) {
case 'm':
return NPY_FR_ms;
case 'u':
return NPY_FR_us;
case 'n':
return NPY_FR_ns;
case 'p':
return NPY_FR_ps;
case 'f':
return NPY_FR_fs;
case 'a':
return NPY_FR_as;
}
}
else if (len == 7 && !strncmp(str, "generic", 7)) {
return NPY_FR_GENERIC;
}
/* If nothing matched, it's an error */
if (metastr == NULL) {
PyErr_Format(PyExc_TypeError,
"Invalid datetime unit \"%s\" in metadata",
str);
}
else {
PyErr_Format(PyExc_TypeError,
"Invalid datetime unit in metadata string \"%s\"",
metastr);
}
return NPY_FR_ERROR;
}
NPY_NO_EXPORT PyObject *
convert_datetime_metadata_to_tuple(PyArray_DatetimeMetaData *meta)
{
PyObject *dt_tuple;
dt_tuple = PyTuple_New(2);
if (dt_tuple == NULL) {
return NULL;
}
PyTuple_SET_ITEM(dt_tuple, 0,
PyUString_FromString(_datetime_strings[meta->base]));
PyTuple_SET_ITEM(dt_tuple, 1,
PyInt_FromLong(meta->num));
return dt_tuple;
}
/*
* Converts a metadata tuple into a datetime metadata C struct.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
convert_datetime_metadata_tuple_to_datetime_metadata(PyObject *tuple,
PyArray_DatetimeMetaData *out_meta,
npy_bool from_pickle)
{
char *basestr = NULL;
Py_ssize_t len = 0, tuple_size;
int den = 1;
PyObject *unit_str = NULL;
if (!PyTuple_Check(tuple)) {
PyObject *errmsg;
errmsg = PyUString_FromString("Require tuple for tuple to NumPy "
"datetime metadata conversion, not ");
PyUString_ConcatAndDel(&errmsg, PyObject_Repr(tuple));
PyErr_SetObject(PyExc_TypeError, errmsg);
Py_DECREF(errmsg);
return -1;
}
tuple_size = PyTuple_GET_SIZE(tuple);
if (tuple_size < 2 || tuple_size > 4) {
PyErr_SetString(PyExc_TypeError,
"Require tuple of size 2 to 4 for "
"tuple to NumPy datetime metadata conversion");
return -1;
}
unit_str = PyTuple_GET_ITEM(tuple, 0);
Py_INCREF(unit_str);
if (PyUnicode_Check(unit_str)) {
/* Allow unicode format strings: convert to bytes */
PyObject *tmp = PyUnicode_AsASCIIString(unit_str);
Py_DECREF(unit_str);
if (tmp == NULL) {
return -1;
}
unit_str = tmp;
}
if (PyBytes_AsStringAndSize(unit_str, &basestr, &len) < 0) {
Py_DECREF(unit_str);
return -1;
}
out_meta->base = parse_datetime_unit_from_string(basestr, len, NULL);
if (out_meta->base == NPY_FR_ERROR) {
Py_DECREF(unit_str);
return -1;
}
Py_DECREF(unit_str);
/* Convert the values to longs */
out_meta->num = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1));
if (error_converting(out_meta->num)) {
return -1;
}
/*
* The event metadata was removed way back in numpy 1.7 (cb4545), but was
* not deprecated at the time.
*/
/* (unit, num, event) */
if (tuple_size == 3) {
/* Numpy 1.14, 2017-08-11 */
if (DEPRECATE(
"When passing a 3-tuple as (unit, num, event), the event "
"is ignored (since 1.7) - use (unit, num) instead") < 0) {
return -1;
}
}
/* (unit, num, den, event) */
else if (tuple_size == 4) {
PyObject *event = PyTuple_GET_ITEM(tuple, 3);
if (from_pickle) {
/* if (event == 1) */
PyObject *one = PyLong_FromLong(1);
int equal_one;
if (one == NULL) {
return -1;
}
equal_one = PyObject_RichCompareBool(event, one, Py_EQ);
if (equal_one == -1) {
return -1;
}
/* if the event data is not 1, it had semantics different to how
* datetime types now behave, which are no longer respected.
*/
if (!equal_one) {
if (PyErr_WarnEx(PyExc_UserWarning,
"Loaded pickle file contains non-default event data "
"for a datetime type, which has been ignored since 1.7",
1) < 0) {
return -1;
}
}
}
else if (event != Py_None) {
/* Numpy 1.14, 2017-08-11 */
if (DEPRECATE(
"When passing a 4-tuple as (unit, num, den, event), the "
"event argument is ignored (since 1.7), so should be None"
) < 0) {
return -1;
}
}
den = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 2));
if (error_converting(den)) {
return -1;
}
}
if (out_meta->num <= 0 || den <= 0) {
PyErr_SetString(PyExc_TypeError,
"Invalid tuple values for "
"tuple to NumPy datetime metadata conversion");
return -1;
}
if (den != 1) {
if (convert_datetime_divisor_to_multiple(out_meta, den, NULL) < 0) {
return -1;
}
}
return 0;
}
/*
* Converts an input object into datetime metadata. The input
* may be either a string or a tuple.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
convert_pyobject_to_datetime_metadata(PyObject *obj,
PyArray_DatetimeMetaData *out_meta)
{
PyObject *ascii = NULL;
char *str = NULL;
Py_ssize_t len = 0;
if (PyTuple_Check(obj)) {
return convert_datetime_metadata_tuple_to_datetime_metadata(
obj, out_meta, NPY_FALSE);
}
/* Get an ASCII string */
if (PyUnicode_Check(obj)) {
/* Allow unicode format strings: convert to bytes */
ascii = PyUnicode_AsASCIIString(obj);
if (ascii == NULL) {
return -1;
}
}
else if (PyBytes_Check(obj)) {
ascii = obj;
Py_INCREF(ascii);
}
else {
PyErr_SetString(PyExc_TypeError,
"Invalid object for specifying NumPy datetime metadata");
return -1;
}
if (PyBytes_AsStringAndSize(ascii, &str, &len) < 0) {
Py_DECREF(ascii);
return -1;
}
if (len > 0 && str[0] == '[') {
int r = parse_datetime_metadata_from_metastr(str, len, out_meta);
Py_DECREF(ascii);
return r;
}
else {
if (parse_datetime_extended_unit_from_string(str, len,
NULL, out_meta) < 0) {
Py_DECREF(ascii);
return -1;
}
Py_DECREF(ascii);
return 0;
}
}
/*
* 'ret' is a PyUString containing the datetime string, and this
* function appends the metadata string to it.
*
* If 'skip_brackets' is true, skips the '[]'.
*
* This function steals the reference 'ret'
*/
NPY_NO_EXPORT PyObject *
append_metastr_to_string(PyArray_DatetimeMetaData *meta,
int skip_brackets,
PyObject *ret)
{
PyObject *res;
int num;
char *basestr;
if (ret == NULL) {
return NULL;
}
if (meta->base == NPY_FR_GENERIC) {
/* Without brackets, give a string "generic" */
if (skip_brackets) {
PyUString_ConcatAndDel(&ret, PyUString_FromString("generic"));
return ret;
}
/* But with brackets, append nothing */
else {
return ret;
}
}
num = meta->num;
if (meta->base >= 0 && meta->base < NPY_DATETIME_NUMUNITS) {
basestr = _datetime_strings[meta->base];
}
else {
PyErr_SetString(PyExc_RuntimeError,
"NumPy datetime metadata is corrupted");
return NULL;
}
if (num == 1) {
if (skip_brackets) {
res = PyUString_FromFormat("%s", basestr);
}
else {
res = PyUString_FromFormat("[%s]", basestr);
}
}
else {
if (skip_brackets) {
res = PyUString_FromFormat("%d%s", num, basestr);
}
else {
res = PyUString_FromFormat("[%d%s]", num, basestr);
}
}
PyUString_ConcatAndDel(&ret, res);
return ret;
}
/*
* Adjusts a datetimestruct based on a seconds offset. Assumes
* the current values are valid.
*/
NPY_NO_EXPORT void
add_seconds_to_datetimestruct(npy_datetimestruct *dts, int seconds)
{
int minutes;
dts->sec += seconds;
if (dts->sec < 0) {
minutes = dts->sec / 60;
dts->sec = dts->sec % 60;
if (dts->sec < 0) {
--minutes;
dts->sec += 60;
}
add_minutes_to_datetimestruct(dts, minutes);
}
else if (dts->sec >= 60) {
minutes = dts->sec / 60;
dts->sec = dts->sec % 60;
add_minutes_to_datetimestruct(dts, minutes);
}
}
/*
* Adjusts a datetimestruct based on a minutes offset. Assumes
* the current values are valid.
*/
NPY_NO_EXPORT void
add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes)
{
int isleap;
/* MINUTES */
dts->min += minutes;
while (dts->min < 0) {
dts->min += 60;
dts->hour--;
}
while (dts->min >= 60) {
dts->min -= 60;
dts->hour++;
}
/* HOURS */
while (dts->hour < 0) {
dts->hour += 24;
dts->day--;
}
while (dts->hour >= 24) {
dts->hour -= 24;
dts->day++;
}
/* DAYS */
if (dts->day < 1) {
dts->month--;
if (dts->month < 1) {
dts->year--;
dts->month = 12;
}
isleap = is_leapyear(dts->year);
dts->day += _days_per_month_table[isleap][dts->month-1];
}
else if (dts->day > 28) {
isleap = is_leapyear(dts->year);
if (dts->day > _days_per_month_table[isleap][dts->month-1]) {
dts->day -= _days_per_month_table[isleap][dts->month-1];
dts->month++;
if (dts->month > 12) {
dts->year++;
dts->month = 1;
}
}
}
}
/*
* Tests for and converts a Python datetime.datetime or datetime.date
* object into a NumPy npy_datetimestruct.
*
* While the C API has PyDate_* and PyDateTime_* functions, the following
* implementation just asks for attributes, and thus supports
* datetime duck typing. The tzinfo time zone conversion would require
* this style of access anyway.
*
* '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.
*/
NPY_NO_EXPORT int
convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out,
NPY_DATETIMEUNIT *out_bestunit,
int apply_tzinfo)
{
PyObject *tmp;
int isleap;
/* Initialize the output to all zeros */
memset(out, 0, sizeof(npy_datetimestruct));
out->month = 1;
out->day = 1;
/* Need at least year/month/day attributes */
if (!PyObject_HasAttrString(obj, "year") ||
!PyObject_HasAttrString(obj, "month") ||
!PyObject_HasAttrString(obj, "day")) {
return 1;
}
/* Get the year */
tmp = PyObject_GetAttrString(obj, "year");
if (tmp == NULL) {
return -1;
}
out->year = PyInt_AsLong(tmp);
if (error_converting(out->year)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the month */
tmp = PyObject_GetAttrString(obj, "month");
if (tmp == NULL) {
return -1;
}
out->month = PyInt_AsLong(tmp);
if (error_converting(out->month)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the day */
tmp = PyObject_GetAttrString(obj, "day");
if (tmp == NULL) {
return -1;
}
out->day = PyInt_AsLong(tmp);
if (error_converting(out->day)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Validate that the month and day are valid for the year */
if (out->month < 1 || out->month > 12) {
goto invalid_date;
}
isleap = is_leapyear(out->year);
if (out->day < 1 ||
out->day > _days_per_month_table[isleap][out->month-1]) {
goto invalid_date;
}
/* Check for time attributes (if not there, return success as a date) */
if (!PyObject_HasAttrString(obj, "hour") ||
!PyObject_HasAttrString(obj, "minute") ||
!PyObject_HasAttrString(obj, "second") ||
!PyObject_HasAttrString(obj, "microsecond")) {
/* The best unit for date is 'D' */
if (out_bestunit != NULL) {
*out_bestunit = NPY_FR_D;
}
return 0;
}
/* Get the hour */
tmp = PyObject_GetAttrString(obj, "hour");
if (tmp == NULL) {
return -1;
}
out->hour = PyInt_AsLong(tmp);
if (error_converting(out->hour)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the minute */
tmp = PyObject_GetAttrString(obj, "minute");
if (tmp == NULL) {
return -1;
}
out->min = PyInt_AsLong(tmp);
if (error_converting(out->min)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the second */
tmp = PyObject_GetAttrString(obj, "second");
if (tmp == NULL) {
return -1;
}
out->sec = PyInt_AsLong(tmp);
if (error_converting(out->sec)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the microsecond */
tmp = PyObject_GetAttrString(obj, "microsecond");
if (tmp == NULL) {
return -1;
}
out->us = PyInt_AsLong(tmp);
if (error_converting(out->us)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
if (out->hour < 0 || out->hour >= 24 ||
out->min < 0 || out->min >= 60 ||
out->sec < 0 || out->sec >= 60 ||
out->us < 0 || out->us >= 1000000) {
goto invalid_time;
}
/* Apply the time zone offset if it exists */
if (apply_tzinfo && PyObject_HasAttrString(obj, "tzinfo")) {
tmp = PyObject_GetAttrString(obj, "tzinfo");
if (tmp == NULL) {
return -1;
}
if (tmp == Py_None) {
Py_DECREF(tmp);
}
else {
PyObject *offset;
int seconds_offset, minutes_offset;
/* 2016-01-14, 1.11 */
PyErr_Clear();
if (DEPRECATE(
"parsing timezone aware datetimes is deprecated; "
"this will raise an error in the future") < 0) {
return -1;
}
/* The utcoffset function should return a timedelta */
offset = PyObject_CallMethod(tmp, "utcoffset", "O", obj);
if (offset == NULL) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/*
* The timedelta should have a function "total_seconds"
* which contains the value we want.
*/
tmp = PyObject_CallMethod(offset, "total_seconds", "");
if (tmp == NULL) {
return -1;
}
seconds_offset = PyInt_AsLong(tmp);
if (error_converting(seconds_offset)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Convert to a minutes offset and apply it */
minutes_offset = seconds_offset / 60;
add_minutes_to_datetimestruct(out, -minutes_offset);
}
}
/* The resolution of Python's datetime is 'us' */
if (out_bestunit != NULL) {
*out_bestunit = NPY_FR_us;
}
return 0;
invalid_date:
PyErr_Format(PyExc_ValueError,
"Invalid date (%d,%d,%d) when converting to NumPy datetime",
(int)out->year, (int)out->month, (int)out->day);
return -1;
invalid_time:
PyErr_Format(PyExc_ValueError,
"Invalid time (%d,%d,%d,%d) when converting "
"to NumPy datetime",
(int)out->hour, (int)out->min, (int)out->sec, (int)out->us);
return -1;
}
/*
* Gets a tzoffset in minutes by calling the fromutc() function on
* the Python datetime.tzinfo object.
*/
NPY_NO_EXPORT int
get_tzoffset_from_pytzinfo(PyObject *timezone_obj, npy_datetimestruct *dts)
{
PyObject *dt, *loc_dt;
npy_datetimestruct loc_dts;
/* Create a Python datetime to give to the timezone object */
dt = PyDateTime_FromDateAndTime((int)dts->year, dts->month, dts->day,
dts->hour, dts->min, 0, 0);
if (dt == NULL) {
return -1;
}
/* Convert the datetime from UTC to local time */
loc_dt = PyObject_CallMethod(timezone_obj, "fromutc", "O", dt);
Py_DECREF(dt);
if (loc_dt == NULL) {
return -1;
}
/* Convert the local datetime into a datetimestruct */
if (convert_pydatetime_to_datetimestruct(loc_dt, &loc_dts, NULL, 0) < 0) {
Py_DECREF(loc_dt);
return -1;
}
Py_DECREF(loc_dt);
/* Calculate the tzoffset as the difference between the datetimes */
return (int)(get_datetimestruct_minutes(&loc_dts) -
get_datetimestruct_minutes(dts));
}
/*
* Converts a PyObject * into a datetime, in any of the forms supported.
*
* If the units metadata isn't known ahead of time, set meta->base
* to -1, and this function will populate meta with either default
* values or values from the input object.
*
* The 'casting' parameter is used to control what kinds of inputs
* are accepted, and what happens. For example, with 'unsafe' casting,
* unrecognized inputs are converted to 'NaT' instead of throwing an error,
* while with 'safe' casting an error will be thrown if any precision
* from the input will be thrown away.
*
* Returns -1 on error, 0 on success.
*/
NPY_NO_EXPORT int
convert_pyobject_to_datetime(PyArray_DatetimeMetaData *meta, PyObject *obj,
NPY_CASTING casting, npy_datetime *out)
{
if (PyBytes_Check(obj) || PyUnicode_Check(obj)) {
PyObject *bytes = NULL;
char *str = NULL;
Py_ssize_t len = 0;
npy_datetimestruct dts;
NPY_DATETIMEUNIT bestunit = NPY_FR_ERROR;
/* Convert to an ASCII string for the date parser */
if (PyUnicode_Check(obj)) {
bytes = PyUnicode_AsASCIIString(obj);
if (bytes == NULL) {
return -1;
}
}
else {
bytes = obj;
Py_INCREF(bytes);
}
if (PyBytes_AsStringAndSize(bytes, &str, &len) < 0) {
Py_DECREF(bytes);
return -1;
}
/* Parse the ISO date */
if (parse_iso_8601_datetime(str, len, meta->base, casting,
&dts, &bestunit, NULL) < 0) {
Py_DECREF(bytes);
return -1;
}
/* Use the detected unit if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = bestunit;
meta->num = 1;
}
if (convert_datetimestruct_to_datetime(meta, &dts, out) < 0) {
Py_DECREF(bytes);
return -1;
}
Py_DECREF(bytes);
return 0;
}
/* Do no conversion on raw integers */
else if (PyInt_Check(obj) || PyLong_Check(obj)) {
/* Don't allow conversion from an integer without specifying a unit */
if (meta->base == NPY_FR_ERROR || meta->base == NPY_FR_GENERIC) {
PyErr_SetString(PyExc_ValueError, "Converting an integer to a "
"NumPy datetime requires a specified unit");
return -1;
}
*out = PyLong_AsLongLong(obj);
return 0;
}
/* Datetime scalar */
else if (PyArray_IsScalar(obj, Datetime)) {
PyDatetimeScalarObject *dts = (PyDatetimeScalarObject *)obj;
/* Copy the scalar directly if units weren't specified */
if (meta->base == NPY_FR_ERROR) {
*meta = dts->obmeta;
*out = dts->obval;
return 0;
}
/* Otherwise do a casting transformation */
else {
/* Allow NaT (not-a-time) values to slip through any rule */
if (dts->obval != NPY_DATETIME_NAT &&
raise_if_datetime64_metadata_cast_error(
"NumPy timedelta64 scalar",
&dts->obmeta, meta, casting) < 0) {
return -1;
}
else {
return cast_datetime_to_datetime(&dts->obmeta, meta,
dts->obval, out);
}
}
}
/* Datetime zero-dimensional array */
else if (PyArray_Check(obj) &&
PyArray_NDIM((PyArrayObject *)obj) == 0 &&
PyArray_DESCR((PyArrayObject *)obj)->type_num == NPY_DATETIME) {
PyArrayObject *arr = (PyArrayObject *)obj;
PyArray_DatetimeMetaData *arr_meta;
npy_datetime dt = 0;
arr_meta = get_datetime_metadata_from_dtype(PyArray_DESCR(arr));
if (arr_meta == NULL) {
return -1;
}
PyArray_DESCR(arr)->f->copyswap(&dt,
PyArray_DATA(arr),
PyArray_ISBYTESWAPPED(arr),
obj);
/* Copy the value directly if units weren't specified */
if (meta->base == NPY_FR_ERROR) {
*meta = *arr_meta;
*out = dt;
return 0;
}
/* Otherwise do a casting transformation */
else {
/* Allow NaT (not-a-time) values to slip through any rule */
if (dt != NPY_DATETIME_NAT &&
raise_if_datetime64_metadata_cast_error(
"NumPy timedelta64 scalar",
arr_meta, meta, casting) < 0) {
return -1;
}
else {
return cast_datetime_to_datetime(arr_meta, meta, dt, out);
}
}
}
/* Convert from a Python date or datetime object */
else {
int code;
npy_datetimestruct dts;
NPY_DATETIMEUNIT bestunit = NPY_FR_ERROR;
code = convert_pydatetime_to_datetimestruct(obj, &dts, &bestunit, 1);
if (code == -1) {
return -1;
}
else if (code == 0) {
/* Use the detected unit if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = bestunit;
meta->num = 1;
}
else {
PyArray_DatetimeMetaData obj_meta;
obj_meta.base = bestunit;
obj_meta.num = 1;
if (raise_if_datetime64_metadata_cast_error(
bestunit == NPY_FR_D ? "datetime.date object"
: "datetime.datetime object",
&obj_meta, meta, casting) < 0) {
return -1;
}
}
return convert_datetimestruct_to_datetime(meta, &dts, out);
}
}
/*
* With unsafe casting, convert unrecognized objects into NaT
* and with same_kind casting, convert None into NaT
*/
if (casting == NPY_UNSAFE_CASTING ||
(obj == Py_None && casting == NPY_SAME_KIND_CASTING)) {
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_FR_GENERIC;
meta->num = 1;
}
*out = NPY_DATETIME_NAT;
return 0;
}
else {
PyErr_SetString(PyExc_ValueError,
"Could not convert object to NumPy datetime");
return -1;
}
}
/*
* Converts a PyObject * into a timedelta, in any of the forms supported
*
* If the units metadata isn't known ahead of time, set meta->base
* to -1, and this function will populate meta with either default
* values or values from the input object.
*
* The 'casting' parameter is used to control what kinds of inputs
* are accepted, and what happens. For example, with 'unsafe' casting,
* unrecognized inputs are converted to 'NaT' instead of throwing an error,
* while with 'safe' casting an error will be thrown if any precision
* from the input will be thrown away.
*
* Returns -1 on error, 0 on success.
*/
NPY_NO_EXPORT int
convert_pyobject_to_timedelta(PyArray_DatetimeMetaData *meta, PyObject *obj,
NPY_CASTING casting, npy_timedelta *out)
{
if (PyBytes_Check(obj) || PyUnicode_Check(obj)) {
PyObject *bytes = NULL;
char *str = NULL;
Py_ssize_t len = 0;
int succeeded = 0;
/* Convert to an ASCII string for the date parser */
if (PyUnicode_Check(obj)) {
bytes = PyUnicode_AsASCIIString(obj);
if (bytes == NULL) {
return -1;
}
}
else {
bytes = obj;
Py_INCREF(bytes);
}
if (PyBytes_AsStringAndSize(bytes, &str, &len) < 0) {
Py_DECREF(bytes);
return -1;
}
/* Check for a NaT string */
if (len <= 0 || (len == 3 &&
tolower(str[0]) == 'n' &&
tolower(str[1]) == 'a' &&
tolower(str[2]) == 't')) {
*out = NPY_DATETIME_NAT;
succeeded = 1;
}
/* Parse as an integer */
else {
char *strend = NULL;
*out = strtol(str, &strend, 10);
if (strend - str == len) {
succeeded = 1;
}
}
Py_DECREF(bytes);
if (succeeded) {
/* Use generic units if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_FR_GENERIC;
meta->num = 1;
}
return 0;
}
}
/* Do no conversion on raw integers */
else if (PyInt_Check(obj) || PyLong_Check(obj)) {
/* Use the default unit if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_DATETIME_DEFAULTUNIT;
meta->num = 1;
}
*out = PyLong_AsLongLong(obj);
return 0;
}
/* Timedelta scalar */
else if (PyArray_IsScalar(obj, Timedelta)) {
PyTimedeltaScalarObject *dts = (PyTimedeltaScalarObject *)obj;
/* Copy the scalar directly if units weren't specified */
if (meta->base == NPY_FR_ERROR) {
*meta = dts->obmeta;
*out = dts->obval;
return 0;
}
/* Otherwise do a casting transformation */
else {
/* Allow NaT (not-a-time) values to slip through any rule */
if (dts->obval != NPY_DATETIME_NAT &&
raise_if_timedelta64_metadata_cast_error(
"NumPy timedelta64 scalar",
&dts->obmeta, meta, casting) < 0) {
return -1;
}
else {
return cast_timedelta_to_timedelta(&dts->obmeta, meta,
dts->obval, out);
}
}
}
/* Timedelta zero-dimensional array */
else if (PyArray_Check(obj) &&
PyArray_NDIM((PyArrayObject *)obj) == 0 &&
PyArray_DESCR((PyArrayObject *)obj)->type_num == NPY_TIMEDELTA) {
PyArrayObject *arr = (PyArrayObject *)obj;
PyArray_DatetimeMetaData *arr_meta;
npy_timedelta dt = 0;
arr_meta = get_datetime_metadata_from_dtype(PyArray_DESCR(arr));
if (arr_meta == NULL) {
return -1;
}
PyArray_DESCR(arr)->f->copyswap(&dt,
PyArray_DATA(arr),
PyArray_ISBYTESWAPPED(arr),
obj);
/* Copy the value directly if units weren't specified */
if (meta->base == NPY_FR_ERROR) {
*meta = *arr_meta;
*out = dt;
return 0;
}
/* Otherwise do a casting transformation */
else {
/* Allow NaT (not-a-time) values to slip through any rule */
if (dt != NPY_DATETIME_NAT &&
raise_if_timedelta64_metadata_cast_error(
"NumPy timedelta64 scalar",
arr_meta, meta, casting) < 0) {
return -1;
}
else {
return cast_timedelta_to_timedelta(arr_meta, meta, dt, out);
}
}
}
/* Convert from a Python timedelta object */
else if (PyObject_HasAttrString(obj, "days") &&
PyObject_HasAttrString(obj, "seconds") &&
PyObject_HasAttrString(obj, "microseconds")) {
PyObject *tmp;
PyArray_DatetimeMetaData us_meta;
npy_timedelta td;
npy_int64 days;
int seconds = 0, useconds = 0;
/* Get the days */
tmp = PyObject_GetAttrString(obj, "days");
if (tmp == NULL) {
return -1;
}
days = PyLong_AsLongLong(tmp);
if (error_converting(days)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the seconds */
tmp = PyObject_GetAttrString(obj, "seconds");
if (tmp == NULL) {
return -1;
}
seconds = PyInt_AsLong(tmp);
if (error_converting(seconds)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
/* Get the microseconds */
tmp = PyObject_GetAttrString(obj, "microseconds");
if (tmp == NULL) {
return -1;
}
useconds = PyInt_AsLong(tmp);
if (error_converting(useconds)) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
td = days*(24*60*60*1000000LL) + seconds*1000000LL + useconds;
/* Use microseconds if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_FR_us;
meta->num = 1;
*out = td;
return 0;
}
else {
/*
* Detect the largest unit where every value after is zero,
* to allow safe casting to seconds if microseconds is zero,
* for instance.
*/
if (td % 1000LL != 0) {
us_meta.base = NPY_FR_us;
}
else if (td % 1000000LL != 0) {
us_meta.base = NPY_FR_ms;
}
else if (td % (60*1000000LL) != 0) {
us_meta.base = NPY_FR_s;
}
else if (td % (60*60*1000000LL) != 0) {
us_meta.base = NPY_FR_m;
}
else if (td % (24*60*60*1000000LL) != 0) {
us_meta.base = NPY_FR_h;
}
else if (td % (7*24*60*60*1000000LL) != 0) {
us_meta.base = NPY_FR_D;
}
else {
us_meta.base = NPY_FR_W;
}
us_meta.num = 1;
if (raise_if_timedelta64_metadata_cast_error(
"datetime.timedelta object",
&us_meta, meta, casting) < 0) {
return -1;
}
else {
/* Switch back to microseconds for the casting operation */
us_meta.base = NPY_FR_us;
return cast_timedelta_to_timedelta(&us_meta, meta, td, out);
}
}
}
/*
* With unsafe casting, convert unrecognized objects into NaT
* and with same_kind casting, convert None into NaT
*/
if (casting == NPY_UNSAFE_CASTING ||
(obj == Py_None && casting == NPY_SAME_KIND_CASTING)) {
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_FR_GENERIC;
meta->num = 1;
}
*out = NPY_DATETIME_NAT;
return 0;
}
else if (PyArray_IsScalar(obj, Integer)) {
/* Use the default unit if none was specified */
if (meta->base == NPY_FR_ERROR) {
meta->base = NPY_DATETIME_DEFAULTUNIT;
meta->num = 1;
}
*out = PyLong_AsLongLong(obj);
return 0;
}
else {
PyErr_SetString(PyExc_ValueError,
"Could not convert object to NumPy timedelta");
return -1;
}
}
/*
* Converts a datetime into a PyObject *.
*
* Not-a-time is returned as the string "NaT".
* For days or coarser, returns a datetime.date.
* For microseconds or coarser, returns a datetime.datetime.
* For units finer than microseconds, returns an integer.
*/
NPY_NO_EXPORT PyObject *
convert_datetime_to_pyobject(npy_datetime dt, PyArray_DatetimeMetaData *meta)
{
PyObject *ret = NULL;
npy_datetimestruct dts;
/*
* Convert NaT (not-a-time) and any value with generic units
* into None.
*/
if (dt == NPY_DATETIME_NAT || meta->base == NPY_FR_GENERIC) {
Py_RETURN_NONE;
}
/* If the type's precision is greater than microseconds, return an int */
if (meta->base > NPY_FR_us) {
return PyLong_FromLongLong(dt);
}
/* Convert to a datetimestruct */
if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) {
return NULL;
}
/*
* If the year is outside the range of years supported by Python's
* datetime, or the datetime64 falls on a leap second,
* return a raw int.
*/
if (dts.year < 1 || dts.year > 9999 || dts.sec == 60) {
return PyLong_FromLongLong(dt);
}
/* If the type's precision is greater than days, return a datetime */
if (meta->base > NPY_FR_D) {
ret = PyDateTime_FromDateAndTime(dts.year, dts.month, dts.day,
dts.hour, dts.min, dts.sec, dts.us);
}
/* Otherwise return a date */
else {
ret = PyDate_FromDate(dts.year, dts.month, dts.day);
}
return ret;
}
/*
* Converts a timedelta into a PyObject *.
*
* Not-a-time is returned as the string "NaT".
* For microseconds or coarser, returns a datetime.timedelta.
* For units finer than microseconds, returns an integer.
*/
NPY_NO_EXPORT PyObject *
convert_timedelta_to_pyobject(npy_timedelta td, PyArray_DatetimeMetaData *meta)
{
PyObject *ret = NULL;
npy_timedelta value;
int days = 0, seconds = 0, useconds = 0;
/*
* Convert NaT (not-a-time) into None.
*/
if (td == NPY_DATETIME_NAT) {
Py_RETURN_NONE;
}
/*
* If the type's precision is greater than microseconds, is
* Y/M/B (nonlinear units), or is generic units, return an int
*/
if (meta->base > NPY_FR_us ||
meta->base == NPY_FR_Y ||
meta->base == NPY_FR_M ||
meta->base == NPY_FR_GENERIC) {
return PyLong_FromLongLong(td);
}
value = td;
/* Apply the unit multiplier (TODO: overflow treatment...) */
value *= meta->num;
/* Convert to days/seconds/useconds */
switch (meta->base) {
case NPY_FR_W:
value *= 7;
break;
case NPY_FR_D:
break;
case NPY_FR_h:
seconds = (int)((value % 24) * (60*60));
value = value / 24;
break;
case NPY_FR_m:
seconds = (int)(value % (24*60)) * 60;
value = value / (24*60);
break;
case NPY_FR_s:
seconds = (int)(value % (24*60*60));
value = value / (24*60*60);
break;
case NPY_FR_ms:
useconds = (int)(value % 1000) * 1000;
value = value / 1000;
seconds = (int)(value % (24*60*60));
value = value / (24*60*60);
break;
case NPY_FR_us:
useconds = (int)(value % (1000*1000));
value = value / (1000*1000);
seconds = (int)(value % (24*60*60));
value = value / (24*60*60);
break;
default:
break;
}
/*
* 'value' represents days, and seconds/useconds are filled.
*
* If it would overflow the datetime.timedelta days, return a raw int
*/
if (value < -999999999 || value > 999999999) {
return PyLong_FromLongLong(td);
}
else {
days = (int)value;
ret = PyDelta_FromDSU(days, seconds, useconds);
if (ret == NULL) {
return NULL;
}
}
return ret;
}
/*
* Returns true if the datetime metadata matches
*/
NPY_NO_EXPORT npy_bool
has_equivalent_datetime_metadata(PyArray_Descr *type1, PyArray_Descr *type2)
{
PyArray_DatetimeMetaData *meta1, *meta2;
if ((type1->type_num != NPY_DATETIME &&
type1->type_num != NPY_TIMEDELTA) ||
(type2->type_num != NPY_DATETIME &&
type2->type_num != NPY_TIMEDELTA)) {
return 0;
}
meta1 = get_datetime_metadata_from_dtype(type1);
if (meta1 == NULL) {
PyErr_Clear();
return 0;
}
meta2 = get_datetime_metadata_from_dtype(type2);
if (meta2 == NULL) {
PyErr_Clear();
return 0;
}
/* For generic units, the num is ignored */
if (meta1->base == NPY_FR_GENERIC && meta2->base == NPY_FR_GENERIC) {
return 1;
}
return meta1->base == meta2->base &&
meta1->num == meta2->num;
}
/*
* Casts a single datetime from having src_meta metadata into
* dst_meta metadata.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
cast_datetime_to_datetime(PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
npy_datetime src_dt,
npy_datetime *dst_dt)
{
npy_datetimestruct dts;
/* If the metadata is the same, short-circuit the conversion */
if (src_meta->base == dst_meta->base &&
src_meta->num == dst_meta->num) {
*dst_dt = src_dt;
return 0;
}
/* Otherwise convert through a datetimestruct */
if (convert_datetime_to_datetimestruct(src_meta, src_dt, &dts) < 0) {
*dst_dt = NPY_DATETIME_NAT;
return -1;
}
if (convert_datetimestruct_to_datetime(dst_meta, &dts, dst_dt) < 0) {
*dst_dt = NPY_DATETIME_NAT;
return -1;
}
return 0;
}
/*
* Casts a single timedelta from having src_meta metadata into
* dst_meta metadata.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
cast_timedelta_to_timedelta(PyArray_DatetimeMetaData *src_meta,
PyArray_DatetimeMetaData *dst_meta,
npy_timedelta src_dt,
npy_timedelta *dst_dt)
{
npy_int64 num = 0, denom = 0;
/* If the metadata is the same, short-circuit the conversion */
if (src_meta->base == dst_meta->base &&
src_meta->num == dst_meta->num) {
*dst_dt = src_dt;
return 0;
}
/* Get the conversion factor */
get_datetime_conversion_factor(src_meta, dst_meta, &num, &denom);
if (num == 0) {
return -1;
}
/* Apply the scaling */
if (src_dt < 0) {
*dst_dt = (src_dt * num - (denom - 1)) / denom;
}
else {
*dst_dt = src_dt * num / denom;
}
return 0;
}
/*
* Returns true if the object is something that is best considered
* a Datetime, false otherwise.
*/
static NPY_GCC_NONNULL(1) npy_bool
is_any_numpy_datetime(PyObject *obj)
{
return (PyArray_IsScalar(obj, Datetime) ||
(PyArray_Check(obj) && (
PyArray_DESCR((PyArrayObject *)obj)->type_num ==
NPY_DATETIME)) ||
PyDate_Check(obj) ||
PyDateTime_Check(obj));
}
/*
* Returns true if the object is something that is best considered
* a Timedelta, false otherwise.
*/
static npy_bool
is_any_numpy_timedelta(PyObject *obj)
{
return (PyArray_IsScalar(obj, Timedelta) ||
(PyArray_Check(obj) && (
PyArray_DESCR((PyArrayObject *)obj)->type_num == NPY_TIMEDELTA)) ||
PyDelta_Check(obj));
}
/*
* Returns true if the object is something that is best considered
* a Datetime or Timedelta, false otherwise.
*/
NPY_NO_EXPORT npy_bool
is_any_numpy_datetime_or_timedelta(PyObject *obj)
{
return obj != NULL &&
(is_any_numpy_datetime(obj) ||
is_any_numpy_timedelta(obj));
}
/*
* Converts an array of PyObject * into datetimes and/or timedeltas,
* based on the values in type_nums.
*
* If inout_meta->base is -1, uses GCDs to calculate the metadata, filling
* in 'inout_meta' with the resulting metadata. Otherwise uses the provided
* 'inout_meta' for all the conversions.
*
* When obj[i] is NULL, out_value[i] will be set to NPY_DATETIME_NAT.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
convert_pyobjects_to_datetimes(int count,
PyObject **objs, int *type_nums,
NPY_CASTING casting,
npy_int64 *out_values,
PyArray_DatetimeMetaData *inout_meta)
{
int i, is_out_strict;
PyArray_DatetimeMetaData *meta;
/* No values trivially succeeds */
if (count == 0) {
return 0;
}
/* Use the inputs to resolve the unit metadata if requested */
if (inout_meta->base == NPY_FR_ERROR) {
/* Allocate an array of metadata corresponding to the objects */
meta = PyArray_malloc(count * sizeof(PyArray_DatetimeMetaData));
if (meta == NULL) {
PyErr_NoMemory();
return -1;
}
/* Convert all the objects into timedeltas or datetimes */
for (i = 0; i < count; ++i) {
meta[i].base = NPY_FR_ERROR;
meta[i].num = 1;
/* NULL -> NaT */
if (objs[i] == NULL) {
out_values[i] = NPY_DATETIME_NAT;
meta[i].base = NPY_FR_GENERIC;
}
else if (type_nums[i] == NPY_DATETIME) {
if (convert_pyobject_to_datetime(&meta[i], objs[i],
casting, &out_values[i]) < 0) {
PyArray_free(meta);
return -1;
}
}
else if (type_nums[i] == NPY_TIMEDELTA) {
if (convert_pyobject_to_timedelta(&meta[i], objs[i],
casting, &out_values[i]) < 0) {
PyArray_free(meta);
return -1;
}
}
else {
PyErr_SetString(PyExc_ValueError,
"convert_pyobjects_to_datetimes requires that "
"all the type_nums provided be datetime or timedelta");
PyArray_free(meta);
return -1;
}
}
/* Merge all the metadatas, starting with the first one */
*inout_meta = meta[0];
is_out_strict = (type_nums[0] == NPY_TIMEDELTA);
for (i = 1; i < count; ++i) {
if (compute_datetime_metadata_greatest_common_divisor(
&meta[i], inout_meta, inout_meta,
type_nums[i] == NPY_TIMEDELTA,
is_out_strict) < 0) {
PyArray_free(meta);
return -1;
}
is_out_strict = is_out_strict || (type_nums[i] == NPY_TIMEDELTA);
}
/* Convert all the values into the resolved unit metadata */
for (i = 0; i < count; ++i) {
if (type_nums[i] == NPY_DATETIME) {
if (cast_datetime_to_datetime(&meta[i], inout_meta,
out_values[i], &out_values[i]) < 0) {
PyArray_free(meta);
return -1;
}
}
else if (type_nums[i] == NPY_TIMEDELTA) {
if (cast_timedelta_to_timedelta(&meta[i], inout_meta,
out_values[i], &out_values[i]) < 0) {
PyArray_free(meta);
return -1;
}
}
}
PyArray_free(meta);
}
/* Otherwise convert to the provided unit metadata */
else {
/* Convert all the objects into timedeltas or datetimes */
for (i = 0; i < count; ++i) {
/* NULL -> NaT */
if (objs[i] == NULL) {
out_values[i] = NPY_DATETIME_NAT;
}
else if (type_nums[i] == NPY_DATETIME) {
if (convert_pyobject_to_datetime(inout_meta, objs[i],
casting, &out_values[i]) < 0) {
return -1;
}
}
else if (type_nums[i] == NPY_TIMEDELTA) {
if (convert_pyobject_to_timedelta(inout_meta, objs[i],
casting, &out_values[i]) < 0) {
return -1;
}
}
else {
PyErr_SetString(PyExc_ValueError,
"convert_pyobjects_to_datetimes requires that "
"all the type_nums provided be datetime or timedelta");
return -1;
}
}
}
return 0;
}
NPY_NO_EXPORT PyArrayObject *
datetime_arange(PyObject *start, PyObject *stop, PyObject *step,
PyArray_Descr *dtype)
{
PyArray_DatetimeMetaData meta;
/*
* Both datetime and timedelta are stored as int64, so they can
* share value variables.
*/
npy_int64 values[3];
PyObject *objs[3];
int type_nums[3];
npy_intp i, length;
PyArrayObject *ret;
npy_int64 *ret_data;
/*
* First normalize the input parameters so there is no Py_None,
* and start is moved to stop if stop is unspecified.
*/
if (step == Py_None) {
step = NULL;
}
if (stop == NULL || stop == Py_None) {
stop = start;
start = NULL;
/* If start was NULL or None, raise an exception */
if (stop == NULL || stop == Py_None) {
PyErr_SetString(PyExc_ValueError,
"arange needs at least a stopping value");
return NULL;
}
}
if (start == Py_None) {
start = NULL;
}
/* Step must not be a Datetime */
if (step != NULL && is_any_numpy_datetime(step)) {
PyErr_SetString(PyExc_ValueError,
"cannot use a datetime as a step in arange");
return NULL;
}
/* Check if the units of the given dtype are generic, in which
* case we use the code path that detects the units
*/
if (dtype != NULL) {
PyArray_DatetimeMetaData *meta_tmp;
type_nums[0] = dtype->type_num;
if (type_nums[0] != NPY_DATETIME && type_nums[0] != NPY_TIMEDELTA) {
PyErr_SetString(PyExc_ValueError,
"datetime_arange was given a non-datetime dtype");
return NULL;
}
meta_tmp = get_datetime_metadata_from_dtype(dtype);
if (meta_tmp == NULL) {
return NULL;
}
/*
* If the dtype specified is in generic units, detect the
* units from the input parameters.
*/
if (meta_tmp->base == NPY_FR_GENERIC) {
dtype = NULL;
meta.base = NPY_FR_ERROR;
}
/* Otherwise use the provided metadata */
else {
meta = *meta_tmp;
}
}
else {
if ((start && is_any_numpy_datetime(start)) ||
is_any_numpy_datetime(stop)) {
type_nums[0] = NPY_DATETIME;
}
else {
type_nums[0] = NPY_TIMEDELTA;
}
meta.base = NPY_FR_ERROR;
}
if (type_nums[0] == NPY_DATETIME && start == NULL) {
PyErr_SetString(PyExc_ValueError,
"arange requires both a start and a stop for "
"NumPy datetime64 ranges");
return NULL;
}
/* Set up to convert the objects to a common datetime unit metadata */
objs[0] = start;
objs[1] = stop;
objs[2] = step;
if (type_nums[0] == NPY_TIMEDELTA) {
type_nums[1] = NPY_TIMEDELTA;
type_nums[2] = NPY_TIMEDELTA;
}
else {
if (PyInt_Check(objs[1]) ||
PyLong_Check(objs[1]) ||
PyArray_IsScalar(objs[1], Integer) ||
is_any_numpy_timedelta(objs[1])) {
type_nums[1] = NPY_TIMEDELTA;
}
else {
type_nums[1] = NPY_DATETIME;
}
type_nums[2] = NPY_TIMEDELTA;
}
/* Convert all the arguments */
if (convert_pyobjects_to_datetimes(3, objs, type_nums,
NPY_SAME_KIND_CASTING, values, &meta) < 0) {
return NULL;
}
/* If no step was provided, default to 1 */
if (step == NULL) {
values[2] = 1;
}
/*
* In the case of arange(datetime, timedelta), convert
* the timedelta into a datetime by adding the start datetime.
*/
if (type_nums[0] == NPY_DATETIME && type_nums[1] == NPY_TIMEDELTA) {
values[1] += values[0];
}
/* Now start, stop, and step have their values and matching metadata */
if (values[0] == NPY_DATETIME_NAT ||
values[1] == NPY_DATETIME_NAT ||
values[2] == NPY_DATETIME_NAT) {
PyErr_SetString(PyExc_ValueError,
"arange: cannot use NaT (not-a-time) datetime values");
return NULL;
}
/* Calculate the array length */
if (values[2] > 0 && values[1] > values[0]) {
length = (values[1] - values[0] + (values[2] - 1)) / values[2];
}
else if (values[2] < 0 && values[1] < values[0]) {
length = (values[1] - values[0] + (values[2] + 1)) / values[2];
}
else if (values[2] != 0) {
length = 0;
}
else {
PyErr_SetString(PyExc_ValueError,
"arange: step cannot be zero");
return NULL;
}
/* Create the dtype of the result */
if (dtype != NULL) {
Py_INCREF(dtype);
}
else {
dtype = create_datetime_dtype(type_nums[0], &meta);
if (dtype == NULL) {
return NULL;
}
}
/* Create the result array */
ret = (PyArrayObject *)PyArray_NewFromDescr(
&PyArray_Type, dtype, 1, &length, NULL,
NULL, 0, NULL);
if (ret == NULL) {
return NULL;
}
if (length > 0) {
/* Extract the data pointer */
ret_data = (npy_int64 *)PyArray_DATA(ret);
/* Create the timedeltas or datetimes */
for (i = 0; i < length; ++i) {
*ret_data = values[0];
values[0] += values[2];
ret_data++;
}
}
return ret;
}
/*
* Examines all the strings in the given string array, and parses them
* to find the right metadata.
*
* Returns 0 on success, -1 on failure.
*/
static int
find_string_array_datetime64_type(PyArrayObject *arr,
PyArray_DatetimeMetaData *meta)
{
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char **dataptr;
npy_intp *strideptr, *innersizeptr;
PyArray_Descr *string_dtype;
int maxlen;
char *tmp_buffer = NULL;
npy_datetimestruct dts;
PyArray_DatetimeMetaData tmp_meta;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(arr) == 0) {
return 0;
}
string_dtype = PyArray_DescrFromType(NPY_STRING);
if (string_dtype == NULL) {
return -1;
}
/* Use unsafe casting to allow unicode -> ascii string */
iter = NpyIter_New((PyArrayObject *)arr,
NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_BUFFERED,
NPY_KEEPORDER, NPY_UNSAFE_CASTING,
string_dtype);
Py_DECREF(string_dtype);
if (iter == NULL) {
return -1;
}
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
dataptr = NpyIter_GetDataPtrArray(iter);
strideptr = NpyIter_GetInnerStrideArray(iter);
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
/* Get the resulting string length */
maxlen = NpyIter_GetDescrArray(iter)[0]->elsize;
/* Allocate a buffer for strings which fill the buffer completely */
tmp_buffer = PyArray_malloc(maxlen+1);
if (tmp_buffer == NULL) {
PyErr_NoMemory();
NpyIter_Deallocate(iter);
return -1;
}
/* The iteration loop */
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
char *tmp;
/* The inner loop */
while (count--) {
/* Replicating strnlen with memchr, because Mac OS X lacks it */
tmp = memchr(data, '\0', maxlen);
/* If the string is all full, use the buffer */
if (tmp == NULL) {
memcpy(tmp_buffer, data, maxlen);
tmp_buffer[maxlen] = '\0';
tmp_meta.base = NPY_FR_ERROR;
if (parse_iso_8601_datetime(tmp_buffer, maxlen, -1,
NPY_UNSAFE_CASTING, &dts,
&tmp_meta.base, NULL) < 0) {
goto fail;
}
}
/* Otherwise parse the data in place */
else {
tmp_meta.base = NPY_FR_ERROR;
if (parse_iso_8601_datetime(data, tmp - data, -1,
NPY_UNSAFE_CASTING, &dts,
&tmp_meta.base, NULL) < 0) {
goto fail;
}
}
tmp_meta.num = 1;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&tmp_meta, meta, 0, 0) < 0) {
goto fail;
}
data += stride;
}
} while(iternext(iter));
PyArray_free(tmp_buffer);
NpyIter_Deallocate(iter);
return 0;
fail:
PyArray_free(tmp_buffer);
NpyIter_Deallocate(iter);
return -1;
}
/*
* Recursively determines the metadata for an NPY_DATETIME dtype.
*
* Returns 0 on success, -1 on failure.
*/
static int
recursive_find_object_datetime64_type(PyObject *obj,
PyArray_DatetimeMetaData *meta)
{
/* Array -> use its metadata */
if (PyArray_Check(obj)) {
PyArrayObject *arr = (PyArrayObject *)obj;
PyArray_Descr *arr_dtype = PyArray_DESCR(arr);
if (arr_dtype->type_num == NPY_STRING ||
arr_dtype->type_num == NPY_UNICODE) {
return find_string_array_datetime64_type(arr, meta);
}
/* If the array has metadata, use it */
else if (arr_dtype->type_num == NPY_DATETIME ||
arr_dtype->type_num == NPY_TIMEDELTA) {
PyArray_DatetimeMetaData *tmp_meta;
/* Get the metadata from the type */
tmp_meta = get_datetime_metadata_from_dtype(arr_dtype);
if (tmp_meta == NULL) {
return -1;
}
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* If it's not an object array, stop looking */
else if (arr_dtype->type_num != NPY_OBJECT) {
return 0;
}
}
/* Datetime scalar -> use its metadata */
else if (PyArray_IsScalar(obj, Datetime)) {
PyDatetimeScalarObject *dts = (PyDatetimeScalarObject *)obj;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&dts->obmeta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* String -> parse it to find out */
else if (PyBytes_Check(obj) || PyUnicode_Check(obj)) {
npy_datetime tmp = 0;
PyArray_DatetimeMetaData tmp_meta;
tmp_meta.base = NPY_FR_ERROR;
tmp_meta.num = 1;
if (convert_pyobject_to_datetime(&tmp_meta, obj,
NPY_UNSAFE_CASTING, &tmp) < 0) {
/* If it's a value error, clear the error */
if (PyErr_Occurred() &&
PyErr_GivenExceptionMatches(PyErr_Occurred(),
PyExc_ValueError)) {
PyErr_Clear();
return 0;
}
/* Otherwise propagate the error */
else {
return -1;
}
}
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* Python datetime object -> 'us' */
else if (PyDateTime_Check(obj)) {
PyArray_DatetimeMetaData tmp_meta;
tmp_meta.base = NPY_FR_us;
tmp_meta.num = 1;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* Python date object -> 'D' */
else if (PyDate_Check(obj)) {
PyArray_DatetimeMetaData tmp_meta;
tmp_meta.base = NPY_FR_D;
tmp_meta.num = 1;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* Now check if what we have left is a sequence for recursion */
if (PySequence_Check(obj)) {
Py_ssize_t i, len = PySequence_Size(obj);
if (len < 0 && PyErr_Occurred()) {
return -1;
}
for (i = 0; i < len; ++i) {
int ret;
PyObject *f = PySequence_GetItem(obj, i);
if (f == NULL) {
return -1;
}
if (Npy_EnterRecursiveCall(" in recursive_find_object_datetime64_type") != 0) {
Py_DECREF(f);
return -1;
}
ret = recursive_find_object_datetime64_type(f, meta);
Py_LeaveRecursiveCall();
Py_DECREF(f);
if (ret < 0) {
return ret;
}
}
return 0;
}
/* Otherwise ignore it */
else {
return 0;
}
}
/*
* handler function for PyDelta values
* which may also be in a 0 dimensional
* NumPy array
*/
static int
delta_checker(PyArray_DatetimeMetaData *meta)
{
PyArray_DatetimeMetaData tmp_meta;
tmp_meta.base = NPY_FR_us;
tmp_meta.num = 1;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(
meta, &tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/*
* Recursively determines the metadata for an NPY_TIMEDELTA dtype.
*
* Returns 0 on success, -1 on failure.
*/
static int
recursive_find_object_timedelta64_type(PyObject *obj,
PyArray_DatetimeMetaData *meta)
{
/* Array -> use its metadata */
if (PyArray_Check(obj)) {
PyArrayObject *arr = (PyArrayObject *)obj;
PyArray_Descr *arr_dtype = PyArray_DESCR(arr);
/* If the array has metadata, use it */
if (arr_dtype->type_num == NPY_DATETIME ||
arr_dtype->type_num == NPY_TIMEDELTA) {
PyArray_DatetimeMetaData *tmp_meta;
/* Get the metadata from the type */
tmp_meta = get_datetime_metadata_from_dtype(arr_dtype);
if (tmp_meta == NULL) {
return -1;
}
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
tmp_meta, meta, 0, 0) < 0) {
return -1;
}
return 0;
}
/* If it's not an object array, stop looking */
else if (arr_dtype->type_num != NPY_OBJECT) {
return 0;
}
else {
if (PyArray_NDIM(arr) == 0) {
/*
* special handling of 0 dimensional NumPy object
* arrays, which may be indexed to retrieve their
* single object using [()], but not by using
* __getitem__(integer) approaches
*/
PyObject *item, *meth, *args;
meth = PyObject_GetAttrString(obj, "__getitem__");
args = Py_BuildValue("(())");
item = PyObject_CallObject(meth, args);
/*
* NOTE: may need other type checks here in the future
* for expanded 0 D datetime array conversions?
*/
if (PyDelta_Check(item)) {
return delta_checker(meta);
}
}
}
}
/* Datetime scalar -> use its metadata */
else if (PyArray_IsScalar(obj, Timedelta)) {
PyTimedeltaScalarObject *dts = (PyTimedeltaScalarObject *)obj;
/* Combine it with 'meta' */
if (compute_datetime_metadata_greatest_common_divisor(meta,
&dts->obmeta, meta, 1, 1) < 0) {
return -1;
}
return 0;
}
/* String -> parse it to find out */
else if (PyBytes_Check(obj) || PyUnicode_Check(obj)) {
/* No timedelta parser yet */
return 0;
}
/* Python timedelta object -> 'us' */
else if (PyDelta_Check(obj)) {
return delta_checker(meta);
}
/* Now check if what we have left is a sequence for recursion */
if (PySequence_Check(obj)) {
Py_ssize_t i, len = PySequence_Size(obj);
if (len < 0 && PyErr_Occurred()) {
return -1;
}
for (i = 0; i < len; ++i) {
int ret;
PyObject *f = PySequence_GetItem(obj, i);
if (f == NULL) {
return -1;
}
if (Npy_EnterRecursiveCall(" in recursive_find_object_timedelta64_type") != 0) {
Py_DECREF(f);
return -1;
}
ret = recursive_find_object_timedelta64_type(f, meta);
Py_LeaveRecursiveCall();
Py_DECREF(f);
if (ret < 0) {
return ret;
}
}
return 0;
}
/* Otherwise ignore it */
else {
return 0;
}
}
/*
* Examines all the objects in the given Python object by
* recursively descending the sequence structure. Returns a
* datetime or timedelta type with metadata based on the data.
*/
NPY_NO_EXPORT PyArray_Descr *
find_object_datetime_type(PyObject *obj, int type_num)
{
PyArray_DatetimeMetaData meta;
meta.base = NPY_FR_GENERIC;
meta.num = 1;
if (type_num == NPY_DATETIME) {
if (recursive_find_object_datetime64_type(obj, &meta) < 0) {
return NULL;
}
else {
return create_datetime_dtype(type_num, &meta);
}
}
else if (type_num == NPY_TIMEDELTA) {
if (recursive_find_object_timedelta64_type(obj, &meta) < 0) {
return NULL;
}
else {
return create_datetime_dtype(type_num, &meta);
}
}
else {
PyErr_SetString(PyExc_ValueError,
"find_object_datetime_type needs a datetime or "
"timedelta type number");
return NULL;
}
}