diff options
author | Mark Wiebe <mwiebe@enthought.com> | 2011-06-02 16:49:25 -0500 |
---|---|---|
committer | Mark Wiebe <mwiebe@enthought.com> | 2011-06-02 16:52:13 -0500 |
commit | 6c2c90c4a3bda6acb89e27d299d312f795e452bf (patch) | |
tree | 0a73c9cb2df7a69e04c3f0b82af54f261c7ce7c2 | |
parent | e08fbd04ff4f6aa71d67d722eef4fa290894fd94 (diff) | |
download | numpy-6c2c90c4a3bda6acb89e27d299d312f795e452bf.tar.gz |
ENH: datetime: Eliminate use of npy_timedeltastruct, an unnecessary abstraction
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_datetime.h | 24 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 200 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 197 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 31 |
5 files changed, 266 insertions, 188 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 3e6af3c08..13a4fb7a5 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -697,12 +697,14 @@ typedef struct { npy_int32 event; } npy_datetimestruct; +/* TO BE REMOVED - NOT USED INTERNALLY. */ typedef struct { npy_int64 day; npy_int32 sec, us, ps, as; npy_int32 event; } npy_timedeltastruct; +/* TO BE REMOVED - NOT USED INTERNALLY. */ #if PY_VERSION_HEX >= 0x03000000 #define PyDataType_GetDatetimeMetaData(descr) \ ((descr->metadata == NULL) ? NULL : \ diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h index 4c7e4457b..820b4f7e9 100644 --- a/numpy/core/src/multiarray/_datetime.h +++ b/numpy/core/src/multiarray/_datetime.h @@ -4,20 +4,6 @@ NPY_NO_EXPORT void numpy_pydatetime_import(); -NPY_NO_EXPORT void -PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, - npy_datetimestruct *result); - -NPY_NO_EXPORT void -PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, - npy_timedeltastruct *result); - -NPY_NO_EXPORT npy_datetime -PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d); - -NPY_NO_EXPORT npy_datetime -PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d); - /* * This function returns the a new reference to the * capsule with the datetime metadata. @@ -242,6 +228,16 @@ NPY_NO_EXPORT PyObject * convert_datetime_to_pyobject(npy_datetime dt, PyArray_DatetimeMetaData *meta); /* + * 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); + +/* * Converts a datetime based on the given metadata into a datetimestruct */ NPY_NO_EXPORT int diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index e4fe2c000..3059c4109 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -106,8 +106,6 @@ MyPyLong_AsUnsigned@Type@ (PyObject *obj) */ -static char * _SEQUENCE_MESSAGE = "error setting an array element with a sequence"; - /**begin repeat * * #TYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, LONG, UINT, ULONG, @@ -764,141 +762,6 @@ fail: return -1; } -/* - * Acknowledgement: Example code contributed by Marty Fuhr sponsored by - * Google Summer of Code 2009 was used to integrate and adapt the mxDateTime - * parser - */ - -/* #include "datetime.c" --- now included in multiarray_onefile */ - - -/* DateTime Objects in Python only keep microsecond resolution. - * - * When converting from datetime objects with an event component return a - * tuple: * (baseunit, number of event) where baseunit follows is a datetime - * type and number of events is a Python integer - */ - - -/* - * We also can lose precision and range here. Ignored. - * Don't use this function if you care. - */ - -NPY_NO_EXPORT PyObject * -PyTimeDelta_FromNormalized(npy_timedelta val, NPY_DATETIMEUNIT base) -{ - npy_timedeltastruct td; - - PyDateTime_IMPORT; - PyArray_TimedeltaToTimedeltaStruct(val, base, &td); - - /* We discard td.ps and td.as */ - return PyDelta_FromDSU(td.day, td.sec, td.us); -} - -NPY_NO_EXPORT PyObject * -PyTimeDelta_FromInt64(timedelta val, PyArray_Descr *descr) -{ - PyArray_DatetimeMetaData *meta; - meta = PyDataType_GetDatetimeMetaData(descr); - if (meta == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "metadata not set for descriptor"); - return NULL; - } - - if (meta->events > 1) { - int events, rem, div; - PyObject *obj; - - obj = PyTuple_New(2); - events = meta->events; - div = val/events; - rem = val % events; - PyTuple_SET_ITEM(obj, 1, PyInt_FromLong(rem)); - /* This resets meta->events for recursive call */ - meta->events = 1; - PyTuple_SET_ITEM(obj, 0, PyTimeDelta_FromInt64(div, descr)); - meta->events = events; - if (PyErr_Occurred()) { - Py_DECREF(obj); - return NULL; - } - return obj; - } - - /* FIXME? : Check for Overflow */ - return PyTimeDelta_FromNormalized(val*meta->num, meta->base); -} - -NPY_NO_EXPORT npy_timedelta -PyTimeDelta_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) -{ - npy_timedeltastruct td; - - PyDateTime_IMPORT; - - if (!PyDelta_Check(obj)) { - PyErr_SetString(PyExc_ValueError, - "Must be a datetime.timedelta object"); - return -1; - } - - td.day = ((PyDateTime_Delta *)obj)->days; - td.sec = ((PyDateTime_Delta *)obj)->seconds; - td.us = ((PyDateTime_Delta *)obj)->microseconds; - td.ps = 0; - td.as = 0; - - return PyArray_TimedeltaStructToTimedelta(base, &td); -} - -NPY_NO_EXPORT timedelta -PyTimeDelta_AsInt64(PyObject *obj, PyArray_Descr *descr) -{ - PyArray_DatetimeMetaData *meta; - npy_timedelta res; - - meta = PyDataType_GetDatetimeMetaData(descr); - if (meta == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "metadata not set for descriptor"); - return -1; - } - - if (meta->events > 1) { - timedelta tmp; - int events; - - if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { - PyErr_SetString(PyExc_ValueError, - "need a 2-tuple on setting if events > 1"); - return -1; - } - /* Alter the dictionary and call again (not thread safe) */ - events = meta->events; - meta->events = 1; - tmp = PyTimeDelta_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); - meta->events = events; - if (PyErr_Occurred()) { - return -1; - } - /* FIXME: Check for overflow */ - tmp *= events; - tmp += MyPyLong_AsLongLong(PyTuple_GET_ITEM(obj, 1)); - if (PyErr_Occurred()) { - return -1; - } - return tmp; - } - - res = PyTimeDelta_AsNormalized(obj, meta->base); - return res / meta->num; -} - - static PyObject * DATETIME_getitem(char *ip, PyArrayObject *ap) { npy_datetime dt; @@ -911,7 +774,7 @@ DATETIME_getitem(char *ip, PyArrayObject *ap) { } if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) { - dt = *((datetime *)ip); + dt = *((npy_datetime *)ip); } else { ap->descr->f->copyswap(&dt, ip, !PyArray_ISNOTSWAPPED(ap), ap); @@ -923,16 +786,23 @@ DATETIME_getitem(char *ip, PyArrayObject *ap) { static PyObject * TIMEDELTA_getitem(char *ip, PyArrayObject *ap) { - timedelta t1; + npy_timedelta td; + PyArray_DatetimeMetaData *meta = NULL; + + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); + if (meta == NULL) { + return NULL; + } if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) { - t1 = *((timedelta *)ip); - return PyTimeDelta_FromInt64((timedelta)t1, ap->descr); + td = *((npy_timedelta *)ip); } else { - ap->descr->f->copyswap(&t1, ip, !PyArray_ISNOTSWAPPED(ap), ap); - return PyTimeDelta_FromInt64((timedelta)t1, ap->descr); + ap->descr->f->copyswap(&td, ip, !PyArray_ISNOTSWAPPED(ap), ap); } + + return convert_timedelta_to_pyobject(td, meta); } static int @@ -954,7 +824,7 @@ DATETIME_setitem(PyObject *op, char *ov, PyArrayObject *ap) { /* Copy the value into the output */ if (ap == NULL || PyArray_ISBEHAVED(ap)) { - *((datetime *)ov)=temp; + *((npy_datetime *)ov)=temp; } else { ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), ap); @@ -963,43 +833,31 @@ DATETIME_setitem(PyObject *op, char *ov, PyArrayObject *ap) { return 0; } -/* FIXME: This needs to take - * 1) Integers and Longs (anything that can be converted to an Int) - * 2) Timedelta scalar objects (with resolution conversion) - * 3) Python Timedelta objects - * - * Plus a tuple for meta->events > 1 - */ - static int TIMEDELTA_setitem(PyObject *op, char *ov, PyArrayObject *ap) { /* ensure alignment */ - timedelta temp; + npy_timedelta temp = 0; + PyArray_DatetimeMetaData *meta = NULL; - if (PyArray_IsScalar(op, Timedelta)) { - temp = ((PyTimedeltaScalarObject *)op)->obval; - } - else if (PyInt_Check(op)) { - temp = PyInt_AS_LONG(op); - } - else if (PyLong_Check(op)) { - temp = PyLong_AsLongLong(op); - } - else { - temp = PyTimeDelta_AsInt64(op, ap->descr); + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); + if (meta == NULL) { + return -1; } - if (PyErr_Occurred()) { - if (PySequence_Check(op)) { - PyErr_Clear(); - PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); - } + + /* Convert the object into a NumPy datetime */ + if (convert_pyobject_to_timedelta(meta, op, &temp) < 0) { return -1; } - if (ap == NULL || PyArray_ISBEHAVED(ap)) - *((timedelta *)ov)=temp; + + /* Copy the value into the output */ + if (ap == NULL || PyArray_ISBEHAVED(ap)) { + *((npy_timedelta *)ov)=temp; + } else { ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), ap); } + return 0; } diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 84297198a..7923bac66 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -440,6 +440,8 @@ convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta, /*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) @@ -467,6 +469,8 @@ PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d) /*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) @@ -847,6 +851,8 @@ convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta, /*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, @@ -875,6 +881,8 @@ PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, /*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, @@ -2392,9 +2400,15 @@ parse_iso_8601_date(char *str, int len, npy_datetimestruct *out) tolower(str[1]) == 'o' && tolower(str[2]) == 'w') { time_t rawtime = 0; + PyArray_DatetimeMetaData meta; time(&rawtime); - PyArray_DatetimeToDatetimeStruct(rawtime, NPY_FR_s, out); - return 0; + + /* Set up a dummy metadata for the conversion */ + meta.base = NPY_FR_s; + meta.num = 1; + meta.events = 1; + + return convert_datetime_to_datetimestruct(&meta, rawtime, out); } substr = str; @@ -3161,7 +3175,63 @@ convert_pyobject_to_timedelta(PyArray_DatetimeMetaData *meta, PyObject *obj, return cast_timedelta_to_timedelta(obj_meta, meta, dt, out); } - /* TODO: Finish this function */ + /* 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 (days == -1 && PyErr_Occurred()) { + 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 (seconds == -1 && PyErr_Occurred()) { + 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 (useconds == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* + * Convert to a microseconds timedelta, then cast to the + * desired units. + */ + td = days*(24*60*60*1000000LL) + seconds*1000000LL + useconds; + us_meta.base = NPY_FR_us; + us_meta.num = 1; + us_meta.events = 1; + + return cast_timedelta_to_timedelta(&us_meta, meta, td, out); + } PyErr_SetString(PyExc_ValueError, "Could not convert object to NumPy timedelta"); @@ -3243,6 +3313,127 @@ convert_datetime_to_pyobject(npy_datetime dt, PyArray_DatetimeMetaData *meta) } /* + * 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, *tup = NULL; + npy_timedelta value; + int event = 0; + int days = 0, seconds = 0, useconds = 0; + + /* Handle not-a-time */ + if (td == NPY_DATETIME_NAT) { + return PyUString_FromString("NaT"); + } + + /* + * If the type's precision is greater than microseconds or is + * Y/M/B (nonlinear units), return an int + */ + if (meta->base > NPY_FR_us || + meta->base == NPY_FR_Y || + meta->base == NPY_FR_M || + meta->base == NPY_FR_B) { + /* Skip use of a tuple for the events, just return the raw int */ + return PyLong_FromLongLong(td); + } + + value = td; + + /* If there are events, extract the event */ + if (meta->events > 1) { + event = (int)(value % meta->events); + value = value / meta->events; + if (event < 0) { + --value; + event += meta->events; + } + } + + /* 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; + } + } + + /* If there is one event, just return the datetime */ + if (meta->events == 1) { + return ret; + } + /* Otherwise return a tuple with the event in the second position */ + else { + tup = PyTuple_New(2); + if (tup == NULL) { + Py_DECREF(ret); + return NULL; + } + PyTuple_SET_ITEM(tup, 0, ret); + + ret = PyInt_FromLong(event); + if (ret == NULL) { + Py_DECREF(tup); + return NULL; + } + PyTuple_SET_ITEM(tup, 1, ret); + + return tup; + } +} + +/* * Returns true if the datetime metadata matches */ NPY_NO_EXPORT npy_bool diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index fe130fb05..1059aeaa1 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -99,6 +99,17 @@ class TestDateTime(TestCase): assert_equal(np.datetime64(b, 's'), a) assert_equal(np.datetime64(b, 's').dtype, np.dtype('M8[s]')) + # Construction from datetime.date + assert_equal(np.datetime64('1945-03-25'), + np.datetime64(datetime.date(1945,3,25))) + assert_equal(np.datetime64('2045-03-25', 'D'), + np.datetime64(datetime.date(2045,3,25), 'D')) + # Construction from datetime.datetime + assert_equal(np.datetime64('1980-01-25T14:36:22.5Z'), + np.datetime64(datetime.datetime(1980,1,25, + 14,36,22,500000))) + + def test_timedelta_scalar_construction(self): # Construct with different units assert_equal(np.timedelta64(7, 'D'), @@ -126,6 +137,26 @@ class TestDateTime(TestCase): assert_equal(np.timedelta64(b, 's'), a) assert_equal(np.timedelta64(b, 's').dtype, np.dtype('m8[s]')) + # Construction from datetime.timedelta + assert_equal(np.timedelta64(5, 'D'), + np.timedelta64(datetime.timedelta(days=5))) + assert_equal(np.timedelta64(102347621, 's'), + np.timedelta64(datetime.timedelta(seconds=102347621))) + assert_equal(np.timedelta64(-10234760000, 'us'), + np.timedelta64(datetime.timedelta( + microseconds=-10234760000))) + assert_equal(np.timedelta64(10234760000, 'us'), + np.timedelta64(datetime.timedelta( + microseconds=10234760000))) + assert_equal(np.timedelta64(1023476, 'ms'), + np.timedelta64(datetime.timedelta(milliseconds=1023476))) + assert_equal(np.timedelta64(10, 'm'), + np.timedelta64(datetime.timedelta(minutes=10))) + assert_equal(np.timedelta64(281, 'h'), + np.timedelta64(datetime.timedelta(hours=281))) + assert_equal(np.timedelta64(28, 'W'), + np.timedelta64(datetime.timedelta(weeks=28))) + def test_datetime_nat_casting(self): a = np.array('NaT', dtype='M8[D]') b = np.datetime64('NaT', '[D]') |