summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/_datetime.h68
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c47
-rw-r--r--numpy/core/src/multiarray/datetime.c530
-rw-r--r--numpy/core/src/multiarray/descriptor.c534
-rw-r--r--numpy/core/tests/test_datetime.py5
5 files changed, 672 insertions, 512 deletions
diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h
index 9be7e5673..331b02824 100644
--- a/numpy/core/src/multiarray/_datetime.h
+++ b/numpy/core/src/multiarray/_datetime.h
@@ -15,4 +15,72 @@ PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d);
NPY_NO_EXPORT npy_datetime
PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d);
+/*
+ * 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);
+
+/*
+ * This function returns a reference to a PyCObject/Capsule
+ * which contains the datetime metadata parsed from a metadata
+ * string. 'metastr' should be NULL-terminated, and len should
+ * contain its string length.
+ */
+NPY_NO_EXPORT PyObject *
+parse_datetime_metacobj_from_metastr(char *metastr, Py_ssize_t len);
+
+/*
+ * Converts a datetype dtype string into a dtype descr object.
+ * The "type" string should be NULL-terminated, and len should
+ * contain its string length.
+ */
+NPY_NO_EXPORT PyArray_Descr *
+parse_dtype_from_datetime_typestr(char *typestr, Py_ssize_t len);
+
+/*
+ * 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 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT NPY_DATETIMEUNIT
+parse_datetime_unit_from_string(char *str, Py_ssize_t len, char *metastr);
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT int
+convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta,
+ char *metastr);
+
+/*
+ * Given an the CObject/Capsule datetime metadata object,
+ * returns a tuple for pickling and other purposes.
+ */
+NPY_NO_EXPORT PyObject *
+convert_datetime_metadata_to_tuple(PyArray_DatetimeMetaData *meta);
+
+/*
+ * Given a tuple representing datetime metadata tuple,
+ * returns a CObject/Capsule datetime metadata object.
+ */
+NPY_NO_EXPORT PyObject *
+convert_datetime_metadata_tuple_to_metacobj(PyObject *tuple);
+
+/*
+ * 'ret' is a PyUString containing the datetime string, and this
+ * function appends the metadata string to it.
+ *
+ * This function steals the reference 'ret'
+ */
+NPY_NO_EXPORT PyObject *
+append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret);
+
#endif
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 0439c42fc..e20e8142d 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -16,6 +16,7 @@
#include "mapping.h"
#include "convert_datatype.h"
+#include "_datetime.h"
/*NUMPY_API
* For backward compatibility
@@ -723,10 +724,54 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2)
}
break;
case NPY_DATETIME:
+ /* 'M[A],'M[B]' -> M[A] when A==B, error otherwise */
if (type_num2 == NPY_DATETIME) {
+ PyArray_DatetimeMetaData *meta1, *meta2;
+ meta1 = get_datetime_metadata_from_dtype(type1);
+ if (meta1 == NULL) {
+ return NULL;
+ }
+ meta2 = get_datetime_metadata_from_dtype(type2);
+ if (meta2 == NULL) {
+ return NULL;
+ }
+
+ if (meta1->base == meta2->base &&
+ meta1->num == meta2->num &&
+ meta1->den == meta2->den &&
+ meta1->events == meta2->events) {
+ Py_INCREF(type1);
+ return type1;
+ }
+ else {
+ PyObject *errmsg;
+ errmsg = PyUString_FromString("Cannot promote "
+ "datetime types ");
+ PyUString_ConcatAndDel(&errmsg,
+ PyObject_Repr((PyObject *)type1));
+ PyUString_ConcatAndDel(&errmsg,
+ PyUString_FromString(" and "));
+ PyUString_ConcatAndDel(&errmsg,
+ PyObject_Repr((PyObject *)type2));
+ PyUString_ConcatAndDel(&errmsg,
+ PyUString_FromString(" because they have "
+ "different units metadata"));
+ PyErr_SetObject(PyExc_TypeError, errmsg);
+ return NULL;
+ }
}
- /* 'M[A]','m[B]' -> 'M[A]' */
+ /* 'M[A]','m[B]' -> 'M[A]', but only when A divides into B */
else if (type_num2 == NPY_TIMEDELTA) {
+ PyArray_DatetimeMetaData *meta1, *meta2;
+ meta1 = get_datetime_metadata_from_dtype(type1);
+ if (meta1 == NULL) {
+ return NULL;
+ }
+ meta2 = get_datetime_metadata_from_dtype(type2);
+ if (meta2 == NULL) {
+ return NULL;
+ }
+
Py_INCREF(type1);
return type1;
}
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index ad1d98270..8c35235c3 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -5,8 +5,7 @@
#include <time.h>
#define _MULTIARRAYMODULE
-#define NPY_NO_PREFIX
-#include <numpy/ndarrayobject.h>
+#include <numpy/arrayobject.h>
#include "npy_config.h"
@@ -30,6 +29,44 @@ typedef struct {
int hour, min, sec;
} hmsstruct;
+/* Exported as DATETIMEUNITS in multiarraymodule.c */
+NPY_NO_EXPORT char *_datetime_strings[] = {
+ NPY_STR_Y,
+ NPY_STR_M,
+ NPY_STR_W,
+ NPY_STR_B,
+ NPY_STR_D,
+ NPY_STR_h,
+ NPY_STR_m,
+ NPY_STR_s,
+ NPY_STR_ms,
+ NPY_STR_us,
+ NPY_STR_ns,
+ NPY_STR_ps,
+ NPY_STR_fs,
+ NPY_STR_as
+};
+
+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},
+ {5, 7, 168, 10080}, /* NPY_FR_W */
+ {NPY_FR_B, NPY_FR_D, NPY_FR_h, NPY_FR_m},
+ {24, 1440, 86400}, /* NPY_FR_B */
+ {NPY_FR_h, NPY_FR_m, NPY_FR_s},
+ {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}
+};
+
+
/*
====================================================
@@ -43,7 +80,13 @@ typedef struct {
* license version 1.0.0
*/
-#define Py_AssertWithArg(x,errortype,errorstr,a1) {if (!(x)) {PyErr_Format(errortype,errorstr,a1);goto onError;}}
+#define Py_AssertWithArg(x,errortype,errorstr,a1) \
+ { \
+ if (!(x)) { \
+ PyErr_Format(errortype,errorstr,a1); \
+ goto onError; \
+ } \
+ }
/* Table with day offsets for each month (0-based, without and with leap) */
static int month_offset[2][13] = {
@@ -781,7 +824,8 @@ PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr,
/*
* FIXME: Overflow is not handled at all
- * To convert from Years, Months, and Business Days, multiplication by the average is done
+ * To convert from Years, Months, and Business Days,
+ * multiplication by the average is done
*/
/*NUMPY_API
@@ -796,9 +840,10 @@ PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr,
npy_bool negative=0;
/*
- * Note that what looks like val / N and val % N for positive numbers maps to
- * [val - (N-1)] / N and [N-1 + (val+1) % N] for negative numbers (with the 2nd
- * value, the remainder, being positive in both cases).
+ * Note that what looks like val / N and val % N for positive
+ * numbers maps to [val - (N-1)] / N and [N-1 + (val+1) % N]
+ * for negative numbers (with the 2nd value, the remainder,
+ * being positive in both cases).
*/
if (val < 0) {
@@ -920,3 +965,474 @@ PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr,
}
return;
}
+
+/*
+ * 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)
+{
+ PyObject *tmp;
+ PyArray_DatetimeMetaData *meta = NULL;
+
+ /* Check that the dtype has metadata */
+ if (dtype->metadata == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Datetime type object is invalid, lacks metadata");
+ return NULL;
+ }
+
+ /* Check that the dtype has unit metadata */
+ tmp = PyDict_GetItemString(dtype->metadata, NPY_METADATA_DTSTR);
+ if (tmp == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Datetime type object is invalid, lacks unit metadata");
+ return NULL;
+ }
+ /* Check that the dtype has an NpyCapsule for the metadata */
+ meta = (PyArray_DatetimeMetaData *)NpyCapsule_AsVoidPtr(tmp);
+ if (meta == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Datetime type object is invalid, unit metadata is corrupt");
+ return NULL;
+ }
+
+ return meta;
+}
+
+NPY_NO_EXPORT PyObject *
+parse_datetime_metacobj_from_metastr(char *metastr, Py_ssize_t len)
+{
+ PyArray_DatetimeMetaData *dt_data;
+ char *substr = metastr, *substrend = NULL;
+
+ dt_data = PyArray_malloc(sizeof(PyArray_DatetimeMetaData));
+ if (dt_data == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ /* If there's no metastr, use the default */
+ if (len == 0) {
+ dt_data->num = 1;
+ dt_data->base = NPY_DATETIME_DEFAULTUNIT;
+ dt_data->den = 1;
+ dt_data->events = 1;
+ }
+ else {
+
+ /* The metadata string must start with a '[' */
+ if (len < 3 || *substr++ != '[') {
+ goto bad_input;
+ }
+
+ /* First comes an optional integer multiplier */
+ dt_data->num = (int)strtol(substr, &substrend, 10);
+ if (substr == substrend) {
+ dt_data->num = 1;
+ }
+ substr = substrend;
+
+ /* Next comes the unit itself, followed by either '/' or ']' */
+ substrend = substr;
+ while (*substrend != '\0' && *substrend != '/' && *substrend != ']') {
+ ++substrend;
+ }
+ if (*substrend == '\0') {
+ goto bad_input;
+ }
+ dt_data->base = parse_datetime_unit_from_string(substr,
+ substrend-substr, metastr);
+ if (dt_data->base == -1) {
+ goto error;
+ }
+ substr = substrend;
+
+ /* Next comes an optional integer denominator */
+ if (*substr == '/') {
+ substr++;
+ dt_data->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 == ']') {
+ dt_data->den = 1;
+ substr++;
+ }
+ else {
+ goto bad_input;
+ }
+
+ /* Finally comes an optional number of events */
+ if (substr[0] == '/' && substr[1] == '/') {
+ substr += 2;
+
+ dt_data->events = (int)strtol(substr, &substrend, 10);
+ if (substr == substrend || *substrend != '\0') {
+ goto bad_input;
+ }
+ }
+ else if (*substr != '\0') {
+ goto bad_input;
+ }
+ else {
+ dt_data->events = 1;
+ }
+
+ if (dt_data->den > 1) {
+ if (convert_datetime_divisor_to_multiple(dt_data, metastr) < 0) {
+ goto error;
+ }
+ }
+ }
+
+ return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor);
+
+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);
+ }
+error:
+ PyArray_free(dt_data);
+ return NULL;
+}
+
+/*
+ * 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_Descr *dtype = NULL;
+ char *metastr = NULL;
+ int is_timedelta = 0;
+ Py_ssize_t metalen = 0;
+ PyObject *metacobj = NULL;
+
+ 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;
+ }
+
+ /* Create a default datetime or timedelta */
+ if (is_timedelta) {
+ dtype = PyArray_DescrNewFromType(PyArray_TIMEDELTA);
+ }
+ else {
+ dtype = PyArray_DescrNewFromType(PyArray_DATETIME);
+ }
+ if (dtype == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Remove any reference to old metadata dictionary
+ * And create a new one for this new dtype
+ */
+ Py_XDECREF(dtype->metadata);
+ dtype->metadata = PyDict_New();
+ if (dtype->metadata == NULL) {
+ Py_DECREF(dtype);
+ return NULL;
+ }
+
+ /* Parse the metadata string into a metadata CObject */
+ metacobj = parse_datetime_metacobj_from_metastr(metastr, metalen);
+ if (metacobj == NULL) {
+ Py_DECREF(dtype);
+ return NULL;
+ }
+
+ /* Set the metadata object in the dictionary. */
+ if (PyDict_SetItemString(dtype->metadata, NPY_METADATA_DTSTR,
+ metacobj) < 0) {
+ Py_DECREF(dtype);
+ Py_DECREF(metacobj);
+ return NULL;
+ }
+ Py_DECREF(metacobj);
+
+ return dtype;
+}
+
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT int
+convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta,
+ char *metastr)
+{
+ int i, num, ind;
+ NPY_DATETIMEUNIT *totry;
+ NPY_DATETIMEUNIT *baseunit;
+ int q, r;
+
+ 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_DATETIME_NUMUNITS - 2) {
+ num = 1;
+ }
+ if (meta->base == NPY_DATETIME_NUMUNITS - 1) {
+ num = 0;
+ }
+ }
+
+ for (i = 0; i < num; i++) {
+ q = totry[i] / meta->den;
+ r = totry[i] % meta->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", meta->den);
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "divisor (%d) is not a multiple of a lower-unit "
+ "in datetime metadata \"%s\"", meta->den, metastr);
+ }
+ return -1;
+ }
+ meta->base = baseunit[i];
+ meta->den = 1;
+ meta->num *= q;
+
+ return 0;
+}
+
+/*
+ * 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 0 on success, -1 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 'B':
+ return NPY_FR_B;
+ 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;
+ }
+ }
+
+ /* If nothing matched, it's an error */
+ if (metastr == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid datetime unit in metadata");
+ }
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "Invalid datetime unit in metadata string \"%s\"",
+ metastr);
+ }
+ return -1;
+}
+
+
+NPY_NO_EXPORT PyObject *
+convert_datetime_metadata_to_tuple(PyArray_DatetimeMetaData *meta)
+{
+ PyObject *dt_tuple;
+
+ dt_tuple = PyTuple_New(4);
+ if (dt_tuple == NULL) {
+ return NULL;
+ }
+
+ PyTuple_SET_ITEM(dt_tuple, 0,
+ PyBytes_FromString(_datetime_strings[meta->base]));
+ PyTuple_SET_ITEM(dt_tuple, 1,
+ PyInt_FromLong(meta->num));
+ PyTuple_SET_ITEM(dt_tuple, 2,
+ PyInt_FromLong(meta->den));
+ PyTuple_SET_ITEM(dt_tuple, 3,
+ PyInt_FromLong(meta->events));
+
+ return dt_tuple;
+}
+
+NPY_NO_EXPORT PyObject *
+convert_datetime_metadata_tuple_to_metacobj(PyObject *tuple)
+{
+ PyArray_DatetimeMetaData *dt_data;
+ char *basestr = NULL;
+ Py_ssize_t len = 0;
+
+ if (PyBytes_AsStringAndSize(PyTuple_GET_ITEM(tuple, 0),
+ &basestr, &len) < 0) {
+ return NULL;
+ }
+
+ dt_data = PyArray_malloc(sizeof(PyArray_DatetimeMetaData));
+ dt_data->base = parse_datetime_unit_from_string(basestr, len, NULL);
+ if (dt_data->base == -1) {
+ PyArray_free(dt_data);
+ return NULL;
+ }
+
+ /* Assumes other objects are Python integers */
+ dt_data->num = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 1));
+ dt_data->den = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 2));
+ dt_data->events = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 3));
+
+ if (dt_data->den > 1) {
+ if (convert_datetime_divisor_to_multiple(dt_data, NULL) < 0) {
+ PyArray_free(dt_data);
+ return NULL;
+ }
+ }
+
+ return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor);
+}
+
+/*
+ * 'ret' is a PyUString containing the datetime string, and this
+ * function appends the metadata string to it.
+ *
+ * This function steals the reference 'ret'
+ */
+NPY_NO_EXPORT PyObject *
+append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret)
+{
+ PyObject *tmp;
+ PyObject *res;
+ int num, den, events;
+ char *basestr;
+ PyArray_DatetimeMetaData *dt_data;
+
+ dt_data = get_datetime_metadata_from_dtype(self);
+ if (dt_data == NULL) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ num = dt_data->num;
+ den = dt_data->den;
+ events = dt_data->events;
+ basestr = _datetime_strings[dt_data->base];
+
+ if (num == 1) {
+ tmp = PyUString_FromString(basestr);
+ }
+ else {
+ tmp = PyUString_FromFormat("%d%s", num, basestr);
+ }
+ if (den != 1) {
+ res = PyUString_FromFormat("/%d", den);
+ PyUString_ConcatAndDel(&tmp, res);
+ }
+
+ res = PyUString_FromString("[");
+ PyUString_ConcatAndDel(&res, tmp);
+ PyUString_ConcatAndDel(&res, PyUString_FromString("]"));
+ if (events != 1) {
+ tmp = PyUString_FromFormat("//%d", events);
+ PyUString_ConcatAndDel(&res, tmp);
+ }
+ PyUString_ConcatAndDel(&ret, res);
+ return ret;
+}
+
+
diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c
index 40572e908..58f573fef 100644
--- a/numpy/core/src/multiarray/descriptor.c
+++ b/numpy/core/src/multiarray/descriptor.c
@@ -13,6 +13,7 @@
#include "numpy/npy_3kcompat.h"
+#include "_datetime.h"
#include "common.h"
#define _chk_byteorder(arg) (arg == '>' || arg == '<' || \
@@ -251,7 +252,7 @@ _convert_from_tuple(PyObject *obj)
newdescr->elsize = type->elsize;
newdescr->elsize *= PyArray_MultiplyList(shape.ptr, shape.len);
PyDimMem_FREE(shape.ptr);
- newdescr->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr));
+ newdescr->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr));
newdescr->flags = type->flags;
newdescr->subarray->base = type;
type = NULL;
@@ -535,426 +536,6 @@ _convert_from_list(PyObject *obj, int align)
return NULL;
}
-/* Exported as DATETIMEUNITS in multiarraymodule.c */
-NPY_NO_EXPORT char *_datetime_strings[] = {
- NPY_STR_Y,
- NPY_STR_M,
- NPY_STR_W,
- NPY_STR_B,
- NPY_STR_D,
- NPY_STR_h,
- NPY_STR_m,
- NPY_STR_s,
- NPY_STR_ms,
- NPY_STR_us,
- NPY_STR_ns,
- NPY_STR_ps,
- NPY_STR_fs,
- NPY_STR_as
-};
-
-/*
- * 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 -1 if there is an error.
- */
-static NPY_DATETIMEUNIT
-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 'B':
- return NPY_FR_B;
- 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;
- }
- }
-
- /* If nothing matched, it's an error */
- if (metastr == NULL) {
- PyErr_SetString(PyExc_TypeError,
- "Invalid datetime unit in metadata");
- }
- else {
- PyErr_Format(PyExc_TypeError,
- "Invalid datetime unit in metadata string \"%s\"",
- metastr);
- }
- return -1;
-}
-
-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},
- {5, 7, 168, 10080}, /* NPY_FR_W */
- {NPY_FR_B, NPY_FR_D, NPY_FR_h, NPY_FR_m},
- {24, 1440, 86400}, /* NPY_FR_B */
- {NPY_FR_h, NPY_FR_m, NPY_FR_s},
- {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.
- */
-static int
-convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta,
- char *metastr)
-{
- int i, num, ind;
- NPY_DATETIMEUNIT *totry;
- NPY_DATETIMEUNIT *baseunit;
- int q, r;
-
- 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_DATETIME_NUMUNITS - 2) {
- num = 1;
- }
- if (meta->base == NPY_DATETIME_NUMUNITS - 1) {
- num = 0;
- }
- }
-
- for (i = 0; i < num; i++) {
- q = totry[i] / meta->den;
- r = totry[i] % meta->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", meta->den);
- }
- else {
- PyErr_Format(PyExc_ValueError,
- "divisor (%d) is not a multiple of a lower-unit "
- "in datetime metadata \"%s\"", meta->den, metastr);
- }
- return -1;
- }
- meta->base = baseunit[i];
- meta->den = 1;
- meta->num *= q;
-
- return 0;
-}
-
-
-static PyObject *
-_get_datetime_tuple_from_cobj(PyObject *cobj)
-{
- PyArray_DatetimeMetaData *dt_data;
- PyObject *dt_tuple;
-
- dt_data = NpyCapsule_AsVoidPtr(cobj);
- dt_tuple = PyTuple_New(4);
-
- PyTuple_SET_ITEM(dt_tuple, 0,
- PyBytes_FromString(_datetime_strings[dt_data->base]));
- PyTuple_SET_ITEM(dt_tuple, 1,
- PyInt_FromLong(dt_data->num));
- PyTuple_SET_ITEM(dt_tuple, 2,
- PyInt_FromLong(dt_data->den));
- PyTuple_SET_ITEM(dt_tuple, 3,
- PyInt_FromLong(dt_data->events));
-
- return dt_tuple;
-}
-
-static PyObject *
-_convert_datetime_tuple_to_cobj(PyObject *tuple)
-{
- PyArray_DatetimeMetaData *dt_data;
- char *basestr = NULL;
- Py_ssize_t len = 0;
-
- if (PyBytes_AsStringAndSize(PyTuple_GET_ITEM(tuple, 0),
- &basestr, &len) < 0) {
- return NULL;
- }
-
- dt_data = _pya_malloc(sizeof(PyArray_DatetimeMetaData));
- dt_data->base = datetime_unit_from_string(basestr, len, NULL);
- if (dt_data->base == -1) {
- _pya_free(dt_data);
- return NULL;
- }
-
- /* Assumes other objects are Python integers */
- dt_data->num = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 1));
- dt_data->den = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 2));
- dt_data->events = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 3));
-
- if (dt_data->den > 1) {
- if (convert_datetime_divisor_to_multiple(dt_data, NULL) < 0) {
- _pya_free(dt_data);
- return NULL;
- }
- }
-
- return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor);
-}
-
-static PyObject *
-datetime_metacobj_from_metastr(char *metastr, Py_ssize_t len)
-{
- PyArray_DatetimeMetaData *dt_data;
- char *substr = metastr, *substrend = NULL;
-
- dt_data = _pya_malloc(sizeof(PyArray_DatetimeMetaData));
- if (dt_data == NULL) {
- return PyErr_NoMemory();
- }
-
- /* If there's no metastr, use the default */
- if (len == 0) {
- dt_data->num = 1;
- dt_data->base = NPY_DATETIME_DEFAULTUNIT;
- dt_data->den = 1;
- dt_data->events = 1;
- }
- else {
-
- /* The metadata string must start with a '[' */
- if (len < 3 || *substr++ != '[') {
- goto bad_input;
- }
-
- /* First comes an optional integer multiplier */
- dt_data->num = (int)strtol(substr, &substrend, 10);
- if (substr == substrend) {
- dt_data->num = 1;
- }
- substr = substrend;
-
- /* Next comes the unit itself, followed by either '/' or ']' */
- substrend = substr;
- while (*substrend != '\0' && *substrend != '/' && *substrend != ']') {
- ++substrend;
- }
- if (*substrend == '\0') {
- goto bad_input;
- }
- dt_data->base = datetime_unit_from_string(substr,
- substrend-substr, metastr);
- if (dt_data->base == -1) {
- goto error;
- }
- substr = substrend;
-
- /* Next comes an optional integer denominator */
- if (*substr == '/') {
- substr++;
- dt_data->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 == ']') {
- dt_data->den = 1;
- substr++;
- }
- else {
- goto bad_input;
- }
-
- /* Finally comes an optional number of events */
- if (substr[0] == '/' && substr[1] == '/') {
- substr += 2;
-
- dt_data->events = (int)strtol(substr, &substrend, 10);
- if (substr == substrend || *substrend != '\0') {
- goto bad_input;
- }
- }
- else if (*substr != '\0') {
- goto bad_input;
- }
- else {
- dt_data->events = 1;
- }
-
- if (dt_data->den > 1) {
- if (convert_datetime_divisor_to_multiple(dt_data, metastr) < 0) {
- goto error;
- }
- }
- }
-
- return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor);
-
-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);
- }
-error:
- _pya_free(dt_data);
- return NULL;
-}
-
-/*
- * Converts a datetype dtype string into a dtype descr object.
- * The "type" string should be NULL-terminated.
- */
-static PyArray_Descr *
-dtype_from_datetime_typestr(char *typestr, Py_ssize_t len)
-{
- PyArray_Descr *dtype = NULL;
- char *metastr = NULL;
- int is_timedelta = 0;
- Py_ssize_t metalen = 0;
- PyObject *metacobj = NULL;
-
- 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;
- }
-
- /* Create a default datetime or timedelta */
- if (is_timedelta) {
- dtype = PyArray_DescrNewFromType(PyArray_TIMEDELTA);
- }
- else {
- dtype = PyArray_DescrNewFromType(PyArray_DATETIME);
- }
- if (dtype == NULL) {
- return NULL;
- }
-
- /*
- * Remove any reference to old metadata dictionary
- * And create a new one for this new dtype
- */
- Py_XDECREF(dtype->metadata);
- dtype->metadata = PyDict_New();
- if (dtype->metadata == NULL) {
- Py_DECREF(dtype);
- return NULL;
- }
-
- /* Parse the metadata string into a metadata CObject */
- metacobj = datetime_metacobj_from_metastr(metastr, metalen);
- if (metacobj == NULL) {
- Py_DECREF(dtype);
- return NULL;
- }
-
- /* Set the metadata object in the dictionary. */
- if (PyDict_SetItemString(dtype->metadata, NPY_METADATA_DTSTR,
- metacobj) < 0) {
- Py_DECREF(dtype);
- Py_DECREF(metacobj);
- return NULL;
- }
- Py_DECREF(metacobj);
-
- return dtype;
-}
-
/*
* comma-separated string
@@ -1453,7 +1034,7 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at)
}
/* check for datetime format */
if (is_datetime_typestr(type, len)) {
- *at = dtype_from_datetime_typestr(type, len);
+ *at = parse_dtype_from_datetime_typestr(type, len);
return (*at) ? PY_SUCCEED : PY_FAIL;
}
/* check for commas present or first (or second) element a digit */
@@ -1643,7 +1224,7 @@ PyArray_DescrNew(PyArray_Descr *base)
Py_XINCREF(new->fields);
Py_XINCREF(new->names);
if (new->subarray) {
- new->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr));
+ new->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr));
memcpy(new->subarray, base->subarray, sizeof(PyArray_ArrayDescr));
Py_INCREF(new->subarray->shape);
Py_INCREF(new->subarray->base);
@@ -1675,7 +1256,7 @@ arraydescr_dealloc(PyArray_Descr *self)
if (self->subarray) {
Py_XDECREF(self->subarray->shape);
Py_DECREF(self->subarray->base);
- _pya_free(self->subarray);
+ PyArray_free(self->subarray);
}
Py_XDECREF(self->metadata);
Py_TYPE(self)->tp_free((PyObject *)self);
@@ -1718,48 +1299,6 @@ arraydescr_subdescr_get(PyArray_Descr *self)
(PyObject *)self->subarray->base, self->subarray->shape);
}
-static PyObject *
-_append_to_datetime_typestr(PyArray_Descr *self, PyObject *ret)
-{
- PyObject *tmp;
- PyObject *res;
- int num, den, events;
- char *basestr;
- PyArray_DatetimeMetaData *dt_data;
-
- /* This shouldn't happen */
- if (self->metadata == NULL) {
- return ret;
- }
- tmp = PyDict_GetItemString(self->metadata, NPY_METADATA_DTSTR);
- dt_data = NpyCapsule_AsVoidPtr(tmp);
- num = dt_data->num;
- den = dt_data->den;
- events = dt_data->events;
- basestr = _datetime_strings[dt_data->base];
-
- if (num == 1) {
- tmp = PyUString_FromString(basestr);
- }
- else {
- tmp = PyUString_FromFormat("%d%s", num, basestr);
- }
- if (den != 1) {
- res = PyUString_FromFormat("/%d", den);
- PyUString_ConcatAndDel(&tmp, res);
- }
-
- res = PyUString_FromString("[");
- PyUString_ConcatAndDel(&res, tmp);
- PyUString_ConcatAndDel(&res, PyUString_FromString("]"));
- if (events != 1) {
- tmp = PyUString_FromFormat("//%d", events);
- PyUString_ConcatAndDel(&res, tmp);
- }
- PyUString_ConcatAndDel(&ret, res);
- return ret;
-}
-
NPY_NO_EXPORT PyObject *
arraydescr_protocol_typestr_get(PyArray_Descr *self)
{
@@ -1780,7 +1319,7 @@ arraydescr_protocol_typestr_get(PyArray_Descr *self)
ret = PyUString_FromFormat("%c%c%d", endian, basic_, size);
if (PyDataType_ISDATETIME(self)) {
- ret = _append_to_datetime_typestr(self, ret);
+ ret = append_metastr_to_datetime_typestr(self, ret);
}
return ret;
@@ -1823,7 +1362,7 @@ arraydescr_typename_get(PyArray_Descr *self)
PyUString_ConcatAndDel(&res, p);
}
if (PyDataType_ISDATETIME(self)) {
- res = _append_to_datetime_typestr(self, res);
+ res = append_metastr_to_datetime_typestr(self, res);
}
return res;
@@ -2201,31 +1740,6 @@ arraydescr_new(PyTypeObject *NPY_UNUSED(subtype), PyObject *args, PyObject *kwds
return (PyObject *)conv;
}
-/*
- * Return a tuple of
- * (cleaned metadata dictionary, tuple with (str, num, events))
- */
-static PyObject *
-_get_pickleabletype_from_metadata(PyObject *metadata)
-{
- PyObject *newdict;
- PyObject *newtup, *dt_tuple;
- PyObject *cobj;
-
- newdict = PyDict_Copy(metadata);
- PyDict_DelItemString(newdict, NPY_METADATA_DTSTR);
- newtup = PyTuple_New(2);
- PyTuple_SET_ITEM(newtup, 0, newdict);
-
- cobj = PyDict_GetItemString(metadata, NPY_METADATA_DTSTR);
- dt_tuple = _get_datetime_tuple_from_cobj(cobj);
-
- PyTuple_SET_ITEM(newtup, 1, dt_tuple);
-
- return newtup;
-}
-
-
/* return a tuple of (callable object, args, state). */
static PyObject *
arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args))
@@ -2287,14 +1801,19 @@ arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args))
state = PyTuple_New(9);
PyTuple_SET_ITEM(state, 0, PyInt_FromLong(version));
if (PyDataType_ISDATETIME(self)) {
- PyObject *newobj;
- /* Handle CObject in NPY_METADATA_DTSTR key separately */
- /*
- * newobj is a tuple of cleaned metadata dictionary
- * and tuple of date_time info (str, num, den, events)
- */
- newobj = _get_pickleabletype_from_metadata(self->metadata);
- PyTuple_SET_ITEM(state, 8, newobj);
+ PyArray_DatetimeMetaData *meta;
+ PyObject *dt_tuple;
+ meta = get_datetime_metadata_from_dtype(self);
+ if (meta == NULL) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ dt_tuple = convert_datetime_metadata_to_tuple(meta);
+ if (dt_tuple == NULL) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ PyTuple_SET_ITEM(state, 8, dt_tuple);
}
else {
Py_INCREF(self->metadata);
@@ -2521,7 +2040,7 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args)
if (self->subarray) {
Py_XDECREF(self->subarray->base);
Py_XDECREF(self->subarray->shape);
- _pya_free(self->subarray);
+ PyArray_free(self->subarray);
}
self->subarray = NULL;
@@ -2565,7 +2084,10 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args)
return NULL;
}
- self->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr));
+ self->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr));
+ if (self->subarray == NULL) {
+ return PyErr_NoMemory();
+ }
self->subarray->base = (PyArray_Descr *)PyTuple_GET_ITEM(subarray, 0);
Py_INCREF(self->subarray->base);
self->subarray->shape = subarray_shape;
@@ -2599,7 +2121,11 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args)
PyObject *cobj;
self->metadata = PyTuple_GET_ITEM(metadata, 0);
Py_INCREF(self->metadata);
- cobj = _convert_datetime_tuple_to_cobj(PyTuple_GET_ITEM(metadata, 1));
+ cobj = convert_datetime_metadata_tuple_to_metacobj(
+ PyTuple_GET_ITEM(metadata, 1));
+ if (cobj == NULL) {
+ return NULL;
+ }
PyDict_SetItemString(self->metadata, NPY_METADATA_DTSTR, cobj);
Py_DECREF(cobj);
}
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index 33006b737..e8c1890e4 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -24,6 +24,11 @@ class TestDateTime(TestCase):
assert_raises(TypeError, np.dtype, 'M16')
assert_raises(TypeError, np.dtype, 'm16')
+ def test_dtype_promotion(self):
+ assert_equal(np.promote_types(np.dtype('M8[Y]'), np.dtype('M8[Y]')),
+ np.dtype('M8[Y]'))
+ assert_raises(TypeError, np.promote_types,
+ np.dtype('M8[Y]'), np.dtype('M8[M]'))
def test_hours(self):
t = np.ones(3, dtype='M8[s]')