diff options
author | Mark Wiebe <mwiebe@enthought.com> | 2011-06-03 10:52:13 -0500 |
---|---|---|
committer | Mark Wiebe <mwiebe@enthought.com> | 2011-06-03 10:52:13 -0500 |
commit | 681adf030eda8e9097cf53856dda6426b2351485 (patch) | |
tree | 451e28ff6c2ca6de869cd9c6bdd3800bfc8a00f3 | |
parent | 8f35cd72500420512915f75cbefe7dd9fc12a009 (diff) | |
download | numpy-681adf030eda8e9097cf53856dda6426b2351485.tar.gz |
ENH: datetime: Got repr and str for datetime scalars working
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 15 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_datetime.h | 24 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arrayobject.c | 5 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 512 | ||||
-rw-r--r-- | numpy/core/src/multiarray/descriptor.c | 20 | ||||
-rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 168 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 19 |
7 files changed, 714 insertions, 49 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 13a4fb7a5..e69ff38b9 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -215,6 +215,19 @@ typedef enum { /* The special not-a-time (NaT) value */ #define NPY_DATETIME_NAT NPY_MIN_INT64 +/* + * Theoretical maximum length of a DATETIME ISO 8601 string + * YEAR: 21 (64-bit year) + * MONTH: 3 + * DAY: 3 + * HOURS: 3 + * MINUTES: 3 + * SECONDS: 3 + * ATTOSECONDS: 1 + 3*6 + * TIMEZONE: 6 + * NULL TERMINATOR: 1 + */ +#define NPY_DATETIME_MAX_ISO8601_STRLEN (21+3*5+1+3*6+6+1) typedef enum { NPY_FR_Y, /* Years */ @@ -689,7 +702,7 @@ typedef struct { /* * This structure contains an exploded view of a date-time value. - * NaT is represented by year == NPY_MIN_INT64. + * NaT is represented by year == NPY_DATETIME_NAT. */ typedef struct { npy_int64 year; diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h index 820b4f7e9..41c6e4ca1 100644 --- a/numpy/core/src/multiarray/_datetime.h +++ b/numpy/core/src/multiarray/_datetime.h @@ -166,10 +166,15 @@ convert_pyobject_to_datetime_metadata(PyObject *obj, * 'ret' is a PyUString containing the datetime string, and this * function appends the metadata string to it. * + * If 'skip_brackets' is true, skips the '[]' when events == 1. + * * This function steals the reference 'ret' */ NPY_NO_EXPORT PyObject * -append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret); +append_metastr_to_string(PyArray_DatetimeMetaData *meta, + int skip_brackets, + PyObject *ret); + /* * Parses (almost) standard ISO 8601 date strings. The differences are: @@ -189,6 +194,23 @@ NPY_NO_EXPORT int parse_iso_8601_date(char *str, int len, npy_datetimestruct *out); /* + * Converts an npy_datetimestruct to an (almost) ISO 8601 + * NULL-terminated string. + * + * If 'local' is non-zero, it produces a string in local time with + * a +-#### timezone offset, otherwise it uses timezone Z (UTC). + * + * 'base' restricts the output to that unit. Set 'base' to + * -1 to auto-detect a base after which all the values are zero. + * + * Returns 0 on success, -1 on failure (for example if the output + * string was too short). + */ +NPY_NO_EXPORT int +make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen, + int local, NPY_DATETIMEUNIT base); + +/* * Tests for and converts a Python datetime.datetime or datetime.date * object into a NumPy npy_datetimestruct. * diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c index f75759593..ba4b2fc59 100644 --- a/numpy/core/src/multiarray/arrayobject.c +++ b/numpy/core/src/multiarray/arrayobject.c @@ -340,8 +340,7 @@ array_repr_builtin(PyArrayObject *self, int repr) max_n = PyArray_NBYTES(self)*4*sizeof(char) + 7; if ((string = (char *)_pya_malloc(max_n)) == NULL) { - PyErr_SetString(PyExc_MemoryError, "out of memory"); - return NULL; + return PyErr_NoMemory(); } if (repr) { @@ -407,6 +406,8 @@ PyArray_SetStringFunction(PyObject *op, int repr) /*NUMPY_API * This function is scheduled to be removed + * + * TO BE REMOVED - NOT USED INTERNALLY. */ NPY_NO_EXPORT void PyArray_SetDatetimeParseFunction(PyObject *op) diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 7923bac66..d20dac925 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -2226,40 +2226,54 @@ convert_pyobject_to_datetime_metadata(PyObject *obj, * 'ret' is a PyUString containing the datetime string, and this * function appends the metadata string to it. * + * If 'skip_brackets' is true, skips the '[]' when events == 1. + * * This function steals the reference 'ret' */ NPY_NO_EXPORT PyObject * -append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret) +append_metastr_to_string(PyArray_DatetimeMetaData *meta, + int skip_brackets, + PyObject *ret) { - PyObject *tmp; PyObject *res; int num, events; char *basestr; - PyArray_DatetimeMetaData *dt_data; - dt_data = get_datetime_metadata_from_dtype(self); - if (dt_data == NULL) { - Py_DECREF(ret); + if (ret == NULL) { return NULL; } - num = dt_data->num; - events = dt_data->events; - basestr = _datetime_strings[dt_data->base]; + num = meta->num; + events = meta->events; + 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) { - tmp = PyUString_FromString(basestr); + if (skip_brackets && events == 1) { + res = PyUString_FromFormat("%s", basestr); + } + else { + res = PyUString_FromFormat("[%s]", basestr); + } } else { - tmp = PyUString_FromFormat("%d%s", num, basestr); + if (skip_brackets && events == 1) { + res = PyUString_FromFormat("%d%s", num, basestr); + } + else { + res = PyUString_FromFormat("[%d%s]", num, basestr); + } } - 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(&res, + PyUString_FromFormat("//%d", events)); } PyUString_ConcatAndDel(&ret, res); return ret; @@ -2275,7 +2289,7 @@ datetimestruct_timezone_offset(npy_datetimestruct *dts, int minutes) int isleap; /* MINUTES */ - dts->min += minutes; + dts->min -= minutes; while (dts->min < 0) { dts->min += 60; dts->hour--; @@ -2812,6 +2826,470 @@ error: } /* + * Converts an npy_datetimestruct to an (almost) ISO 8601 + * NULL-terminated string. + * + * If 'local' is non-zero, it produces a string in local time with + * a +-#### timezone offset, otherwise it uses timezone Z (UTC). + * + * 'base' restricts the output to that unit. Set 'base' to + * -1 to auto-detect a base after which all the values are zero. + * + * Returns 0 on success, -1 on failure (for example if the output + * string was too short). + */ +NPY_NO_EXPORT int +make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen, + int local, NPY_DATETIMEUNIT base) +{ + npy_datetimestruct dts_local; + int timezone_offset = 0; + + char *substr = outstr, sublen = outlen; + int tmplen; + + /* Handle NaT */ + if (dts->year == NPY_DATETIME_NAT) { + if (outlen < 4) { + goto string_too_short; + } + outstr[0] = 'N'; + outstr[0] = 'a'; + outstr[0] = 'T'; + outstr[0] = '\0'; + + return 0; + } + + /* Only do local time within a reasonable year range */ + if (dts->year <= 1900 || dts->year >= 10000) { + local = 0; + } + + /* Automatically detect a good unit */ + if (base == -1) { + if (dts->as % 1000 != 0) { + base = NPY_FR_as; + } + else if (dts->as != 0) { + base = NPY_FR_fs; + } + else if (dts->ps % 1000 != 0) { + base = NPY_FR_ps; + } + else if (dts->ps != 0) { + base = NPY_FR_ns; + } + else if (dts->us % 1000 != 0) { + base = NPY_FR_us; + } + else if (dts->us != 0) { + base = NPY_FR_ms; + } + else if (dts->sec != 0) { + base = NPY_FR_s; + } + /* If 'local' is enabled, always include minutes with hours */ + else if (dts->min != 0 || (local && dts->hour != 0)) { + base = NPY_FR_m; + } + else if (dts->hour != 0) { + base = NPY_FR_h; + } + /* 'local' has no effect on date-only printing */ + else if (dts->day != 1) { + base = NPY_FR_D; + } + else if (dts->month != 1) { + base = NPY_FR_M; + } + else { + base = NPY_FR_Y; + } + } + /* + * Print business days and weeks with the same precision as days. + * + * TODO: Could print weeks with YYYY-Www format if the week + * epoch is a Monday. + */ + else if (base == NPY_FR_B || base == NPY_FR_W) { + base = NPY_FR_D; + } + + /* Printed dates have no time zone */ + if (base < NPY_FR_h) { + local = 0; + } + + /* Use the C API to convert from UTC to local time */ + if (local) { + time_t rawtime = 0, localrawtime; + struct tm tm_; + + /* + * Convert everything in 'dts' to a time_t, to minutes precision. + * This is POSIX time, which skips leap-seconds, but because + * we drop the seconds value from the npy_datetimestruct, everything + * is ok for this operation. + */ + rawtime = (time_t)get_datetimestruct_days(dts) * 24 * 60 * 60; + rawtime += dts->hour * 60 * 60; + rawtime += dts->min * 60; + + /* localtime converts a 'time_t' into a local 'struct tm' */ +#if defined(_WIN32) + if (localtime_s(&tm_, &rawtime) != 0) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_s to " + "get a local time"); + return -1; + } +#else + /* Other platforms may require something else */ + if (localtime_r(&rawtime, &tm_) == NULL) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_r to " + "get a local time"); + return -1; + } +#endif + /* Make a copy of the npy_datetimestruct we can modify */ + dts_local = *dts; + + /* Copy back all the values except seconds */ + dts_local.min = tm_.tm_min; + dts_local.hour = tm_.tm_hour; + dts_local.day = tm_.tm_mday; + dts_local.month = tm_.tm_mon + 1; + dts_local.year = tm_.tm_year + 1900; + + /* Extract the timezone offset that was applied */ + rawtime /= 60; + localrawtime = (time_t)get_datetimestruct_days(&dts_local) * 24 * 60; + localrawtime += dts_local.hour * 60; + localrawtime += dts_local.min; + + timezone_offset = localrawtime - rawtime; + + /* Set dts to point to our local time instead of the UTC time */ + dts = &dts_local; + } + + /* YEAR */ +#ifdef _WIN32 + tmplen = _snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year); +#else + tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year); +#endif + /* If it ran out of space or there isn't space for the NULL terminator */ + if (tmplen < 0 || tmplen >= sublen) { + goto string_too_short; + } + substr += tmplen; + sublen -= tmplen; + + /* Stop if the unit is years */ + if (base == NPY_FR_Y) { + *substr = '\0'; + return 0; + } + + /* MONTH */ + substr[0] = '-'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->month / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->month % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is months */ + if (base == NPY_FR_M) { + *substr = '\0'; + return 0; + } + + /* DAY */ + substr[0] = '-'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->day / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->day % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is days */ + if (base == NPY_FR_D) { + *substr = '\0'; + return 0; + } + + /* HOUR */ + substr[0] = 'T'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->hour / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->hour % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is hours */ + if (base == NPY_FR_h) { + goto add_time_zone; + } + + /* MINUTE */ + substr[0] = ':'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->min / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->min % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is minutes */ + if (base == NPY_FR_m) { + goto add_time_zone; + } + + /* SECOND */ + substr[0] = ':'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->sec / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->sec % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is seconds */ + if (base == NPY_FR_s) { + goto add_time_zone; + } + + /* MILLISECOND */ + substr[0] = '.'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->us / 100000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->us / 10000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr[3] = (char)((dts->us / 1000) % 10 + '0'); + if (sublen <= 4 ) { + goto string_too_short; + } + substr += 4; + sublen -= 4; + + /* Stop if the unit is milliseconds */ + if (base == NPY_FR_ms) { + goto add_time_zone; + } + + /* MICROSECOND */ + substr[0] = (char)((dts->us / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->us / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->us % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is microseconds */ + if (base == NPY_FR_us) { + goto add_time_zone; + } + + /* NANOSECOND */ + substr[0] = (char)((dts->ps / 100000) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->ps / 10000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->ps / 1000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is nanoseconds */ + if (base == NPY_FR_ns) { + goto add_time_zone; + } + + /* PICOSECOND */ + substr[0] = (char)((dts->ps / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->ps / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->ps % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is picoseconds */ + if (base == NPY_FR_ps) { + goto add_time_zone; + } + + /* FEMTOSECOND */ + substr[0] = (char)((dts->as / 100000) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->as / 10000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->as / 1000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is femtoseconds */ + if (base == NPY_FR_fs) { + goto add_time_zone; + } + + /* ATTOSECOND */ + substr[0] = (char)((dts->as / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->as / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->as % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + +add_time_zone: + if (local) { + /* Add the +/- sign */ + if (timezone_offset < 0) { + substr[0] = '-'; + timezone_offset = -timezone_offset; + } + else { + substr[0] = '+'; + } + if (sublen <= 1) { + goto string_too_short; + } + substr += 1; + sublen -= 1; + + /* Add the timezone offset */ + substr[0] = (char)((timezone_offset / (10*60)) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((timezone_offset / 60) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(((timezone_offset % 60) / 10) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr[3] = (char)((timezone_offset % 60) % 10 + '0'); + if (sublen <= 4 ) { + goto string_too_short; + } + substr += 4; + sublen -= 4; + } + /* UTC "Zulu" time */ + else { + substr[0] = 'Z'; + if (sublen <= 1) { + goto string_too_short; + } + substr += 1; + sublen -= 1; + } + + /* Add a NULL terminator, and return */ + substr[0] = '\0'; + + return 0; + +string_too_short: + /* Put a NULL terminator on anyway */ + if (outlen > 0) { + outstr[outlen-1] = '\0'; + } + + PyErr_Format(PyExc_RuntimeError, + "The string provided for NumPy ISO datetime formatting " + "was too short, with length %d", + outlen); + return -1; +} + +/* * Tests for and converts a Python datetime.datetime or datetime.date * object into a NumPy npy_datetimestruct. * diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index d42624f68..1fe8f748d 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -1319,7 +1319,15 @@ arraydescr_protocol_typestr_get(PyArray_Descr *self) ret = PyUString_FromFormat("%c%c%d", endian, basic_, size); if (PyDataType_ISDATETIME(self)) { - ret = append_metastr_to_datetime_typestr(self, ret); + PyArray_DatetimeMetaData *meta; + + meta = get_datetime_metadata_from_dtype(self); + if (meta == NULL) { + Py_DECREF(ret); + return NULL; + } + + ret = append_metastr_to_string(meta, 0, ret); } return ret; @@ -1362,7 +1370,15 @@ arraydescr_typename_get(PyArray_Descr *self) PyUString_ConcatAndDel(&res, p); } if (PyDataType_ISDATETIME(self)) { - res = append_metastr_to_datetime_typestr(self, res); + PyArray_DatetimeMetaData *meta; + + meta = get_datetime_metadata_from_dtype(self); + if (meta == NULL) { + Py_DECREF(res); + return NULL; + } + + res = append_metastr_to_string(meta, 0, res); } return res; diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index 66969fbb2..4203ca37b 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -334,15 +334,13 @@ gentype_nonzero_number(PyObject *m1) static PyObject * gentype_str(PyObject *self) { - PyArrayObject *arr; - PyObject *ret; + PyObject *arr, *ret = NULL; - arr = (PyArrayObject *)PyArray_FromScalar(self, NULL); - if (arr == NULL) { - return NULL; + arr = PyArray_FromScalar(self, NULL); + if (arr != NULL) { + ret = PyObject_Str((PyObject *)arr); + Py_DECREF(arr); } - ret = PyObject_Str((PyObject *)arr); - Py_DECREF(arr); return ret; } @@ -350,15 +348,14 @@ gentype_str(PyObject *self) static PyObject * gentype_repr(PyObject *self) { - PyArrayObject *arr; - PyObject *ret; + PyObject *arr, *ret = NULL; - arr = (PyArrayObject *)PyArray_FromScalar(self, NULL); - if (arr == NULL) { - return NULL; + arr = PyArray_FromScalar(self, NULL); + if (arr != NULL) { + /* XXX: Why are we using str here? */ + ret = PyObject_Str((PyObject *)arr); + Py_DECREF(arr); } - ret = PyObject_Str((PyObject *)arr); - Py_DECREF(arr); return ret; } @@ -597,6 +594,145 @@ static PyObject * } /**end repeat**/ +static PyObject * +datetimetype_repr(PyObject *self) +{ + PyDatetimeScalarObject *scal; + npy_datetimestruct dts; + PyObject *ret; + char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; + + if (!PyArray_IsScalar(self, Datetime)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy datetime repr on a non-datetime type"); + return NULL; + } + + scal = (PyDatetimeScalarObject *)self; + + if (convert_datetime_to_datetimestruct(&scal->obmeta, scal->obval, + &dts) < 0) { + return NULL; + } + + if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, scal->obmeta.base) < 0) { + return NULL; + } + + ret = PyUString_FromString("numpy.datetime64('"); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(iso)); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("','")); + ret = append_metastr_to_string(&scal->obmeta, 1, ret); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("')")); + + return ret; +} + +static PyObject * +timedeltatype_repr(PyObject *self) +{ + PyTimedeltaScalarObject *scal; + PyObject *ret; + + if (!PyArray_IsScalar(self, Timedelta)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy timedelta repr on a non-datetime type"); + return NULL; + } + + scal = (PyTimedeltaScalarObject *)self; + + ret = PyUString_FromFormat("numpy.timedelta64(%lld", scal->obval); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(",'")); + ret = append_metastr_to_string(&scal->obmeta, 1, ret); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("')")); + + return ret; +} + +static PyObject * +datetimetype_str(PyObject *self) +{ + PyDatetimeScalarObject *scal; + npy_datetimestruct dts; + char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; + + if (!PyArray_IsScalar(self, Datetime)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy datetime str on a non-datetime type"); + return NULL; + } + + scal = (PyDatetimeScalarObject *)self; + + if (convert_datetime_to_datetimestruct(&scal->obmeta, scal->obval, + &dts) < 0) { + return NULL; + } + + if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, scal->obmeta.base) < 0) { + return NULL; + } + + return PyUString_FromString(iso); +} + +static char *_datetime_strings[] = { + "years", + "months", + "weeks", + "business days", + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", + "picoseconds", + "femtoseconds", + "attoseconds" +}; + +static PyObject * +timedeltatype_str(PyObject *self) +{ + PyTimedeltaScalarObject *scal; + PyObject *ret; + char *basestr = "invalid"; + + if (!PyArray_IsScalar(self, Timedelta)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy timedelta str on a non-datetime type"); + return NULL; + } + + scal = (PyTimedeltaScalarObject *)self; + + /* TODO: Account for events, etc */ + + if (scal->obmeta.base >= 0 && scal->obmeta.base < NPY_DATETIME_NUMUNITS) { + basestr = _datetime_strings[scal->obmeta.base]; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "NumPy datetime metadata is corrupted"); + return NULL; + } + + ret = PyUString_FromFormat("%lld ", + (long long)(scal->obval * scal->obmeta.num)); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(basestr)); + + return ret; +} + /* The REPR values are finfo.precision + 2 */ #define HALFPREC_REPR 5 #define HALFPREC_STR 5 @@ -3872,6 +4008,9 @@ initialize_numeric_types(void) PyDoubleArrType_Type.tp_@name@ = doubletype_@name@; PyCDoubleArrType_Type.tp_@name@ = cdoubletype_@name@; + + PyDatetimeArrType_Type.tp_@name@ = datetimetype_@name@; + PyTimedeltaArrType_Type.tp_@name@ = timedeltatype_@name@; /**end repeat**/ PyHalfArrType_Type.tp_print = halftype_print; @@ -3898,6 +4037,7 @@ initialize_numeric_types(void) PyCLongDoubleArrType_Type.@kind@_@name@ = clongdoubletype_@name@; /**end repeat**/ + #if !defined(NPY_PY3K) /**begin repeat * #name = long, hex, oct# diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 1059aeaa1..d2a25c1ff 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -1,4 +1,5 @@ import os, pickle +import numpy import numpy as np from numpy.testing import * from numpy.compat import asbytes @@ -767,15 +768,6 @@ class TestDateTime(TestCase): self.assertRaises(ValueError, lambda : np.dtype('M8[as/10]')) def test_string_parser_variants(self): - """ - # Different month formats - assert_equal(np.array(['1980-02-29'], np.dtype('M8')), - np.array(['1980-Feb-29'], np.dtype('M8'))) - assert_equal(np.array(['1980-02-29'], np.dtype('M8')), - np.array(['1980-feb-29'], np.dtype('M8'))) - assert_equal(np.array(['1980-02-29'], np.dtype('M8')), - np.array(['1980-FEB-29'], np.dtype('M8'))) - """ # Allow space instead of 'T' between date and time assert_equal(np.array(['1980-02-29T01:02:03'], np.dtype('M8')), np.array(['1980-02-29 01:02:03'], np.dtype('M8'))) @@ -787,11 +779,14 @@ class TestDateTime(TestCase): np.array(['-1980-02-29 01:02:03Z'], np.dtype('M8'))) # Time zone offset assert_equal(np.array(['1980-02-29T02:02:03Z'], np.dtype('M8')), - np.array(['1980-02-29 00:32:03+0130'], np.dtype('M8'))) + np.array(['1980-02-29 00:32:03-0130'], np.dtype('M8'))) assert_equal(np.array(['1980-02-28T22:32:03Z'], np.dtype('M8')), - np.array(['1980-02-29 00:02:03-01:30'], np.dtype('M8'))) + np.array(['1980-02-29 00:02:03+01:30'], np.dtype('M8'))) assert_equal(np.array(['1980-02-29T02:32:03.506Z'], np.dtype('M8')), - np.array(['1980-02-29 00:32:03.506+02'], np.dtype('M8'))) + np.array(['1980-02-29 00:32:03.506-02'], np.dtype('M8'))) + assert_equal(np.datetime64('1977-03-02T12:30-0230'), + np.datetime64('1977-03-02T15:00Z')) + def test_string_parser_error_check(self): # Arbitrary bad string |