summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/arrayprint.py12
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h2
-rw-r--r--numpy/core/numerictypes.py4
-rw-r--r--numpy/core/src/multiarray/_datetime.h28
-rw-r--r--numpy/core/src/multiarray/ctors.c10
-rw-r--r--numpy/core/src/multiarray/datetime.c606
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c167
-rw-r--r--numpy/core/src/multiarray/scalartypes.c.src6
-rw-r--r--numpy/core/tests/test_datetime.py119
9 files changed, 652 insertions, 302 deletions
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
index c02d3e0a1..8595d6f02 100644
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -15,7 +15,7 @@ __docformat__ = 'restructuredtext'
import sys
import numerictypes as _nt
from umath import maximum, minimum, absolute, not_equal, isnan, isinf
-from multiarray import format_longfloat
+from multiarray import format_longfloat, datetime_as_string
from fromnumeric import ravel
@@ -245,7 +245,7 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ',
'complexfloat' : ComplexFormat(data, precision,
suppress_small),
'longcomplexfloat' : LongComplexFormat(precision),
- 'datetime' : DatetimeFormat(True, None),
+ 'datetime' : DatetimeFormat(True, None, -1),
'timedelta' : TimedeltaFormat(data),
'numpystr' : repr,
'str' : str}
@@ -698,12 +698,16 @@ class ComplexFormat(object):
return r + i
class DatetimeFormat(object):
- def __init__(self, uselocaltime=True, overrideunit=None):
+ def __init__(self, uselocaltime=True, overrideunit=None, tzoffset=-1):
self.local = uselocaltime
self.unit = overrideunit
+ self.tzoffset = -1
def __call__(self, x):
- return "'%s'" % str(x)
+ return "'%s'" % datetime_as_string(x,
+ local=self.local,
+ unit=self.unit,
+ tzoffset=self.tzoffset)
class TimedeltaFormat(object):
def __init__(self, data):
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index e69ff38b9..5bed8d70c 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -224,7 +224,7 @@ typedef enum {
* MINUTES: 3
* SECONDS: 3
* ATTOSECONDS: 1 + 3*6
- * TIMEZONE: 6
+ * TIMEZONE: 5
* NULL TERMINATOR: 1
*/
#define NPY_DATETIME_MAX_ISO8601_STRLEN (21+3*5+1+3*6+6+1)
diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py
index 7afdf2263..6d655cb17 100644
--- a/numpy/core/numerictypes.py
+++ b/numpy/core/numerictypes.py
@@ -92,10 +92,10 @@ Exported symbols include:
__all__ = ['sctypeDict', 'sctypeNA', 'typeDict', 'typeNA', 'sctypes',
'ScalarType', 'obj2sctype', 'cast', 'nbytes', 'sctype2char',
'maximum_sctype', 'issctype', 'typecodes', 'find_common_type',
- 'issubdtype','datetime_data']
+ 'issubdtype','datetime_data','datetime_as_string']
from numpy.core.multiarray import typeinfo, ndarray, array, \
- empty, dtype, datetime_data
+ empty, dtype, datetime_data, datetime_as_string
import types as _types
import sys
diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h
index 41c6e4ca1..a91953787 100644
--- a/numpy/core/src/multiarray/_datetime.h
+++ b/numpy/core/src/multiarray/_datetime.h
@@ -175,6 +175,13 @@ append_metastr_to_string(PyArray_DatetimeMetaData *meta,
int skip_brackets,
PyObject *ret);
+/*
+ * Provides a string length to use for converting datetime
+ * objects with the given local and unit settings.
+ */
+NPY_NO_EXPORT int
+get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base);
+
/*
* Parses (almost) standard ISO 8601 date strings. The differences are:
@@ -203,12 +210,17 @@ parse_iso_8601_date(char *str, int len, npy_datetimestruct *out);
* 'base' restricts the output to that unit. Set 'base' to
* -1 to auto-detect a base after which all the values are zero.
*
+ * 'tzoffset' is used if 'local' is enabled, and 'tzoffset' is
+ * set to a value other than -1. This is a manual override for
+ * the local time zone to use, as an offset in minutes.
+ *
* 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);
+ int local, NPY_DATETIMEUNIT base, int tzoffset);
+
/*
* Tests for and converts a Python datetime.datetime or datetime.date
@@ -281,6 +293,20 @@ convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta,
npy_datetime *out);
/*
+ * 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);
+
+/*
+ * 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);
+
+/*
* Returns true if the datetime metadata matches
*/
NPY_NO_EXPORT npy_bool
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index e3891af9f..c4980d6c0 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -2790,11 +2790,11 @@ PyArray_CopyInto(PyArrayObject *dst, PyArrayObject *src)
/*NUMPY_API
- PyArray_CheckAxis
-
- check that axis is valid
- convert 0-d arrays to 1-d arrays
-*/
+ * PyArray_CheckAxis
+ *
+ * check that axis is valid
+ * convert 0-d arrays to 1-d arrays
+ */
NPY_NO_EXPORT PyObject *
PyArray_CheckAxis(PyArrayObject *arr, int *axis, int flags)
{
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index d20dac925..b84a14fde 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -31,15 +31,6 @@ is_leapyear(npy_int64 year);
/* For defaults and errors */
#define NPY_FR_ERR -1
-/* Offset for number of days between Dec 31, 1969 and Jan 1, 0001
-* Assuming Gregorian calendar was always in effect (proleptic Gregorian calendar)
-*/
-
-/* Calendar Structure for Parsing Long -> Date */
-typedef struct {
- int hour, min, sec;
-} hmsstruct;
-
/* Exported as DATETIMEUNITS in multiarraymodule.c */
NPY_NO_EXPORT char *_datetime_strings[] = {
NPY_STR_Y,
@@ -60,6 +51,7 @@ NPY_NO_EXPORT char *_datetime_strings[] = {
/*
====================================================
+ }
== Beginning of section borrowed from mx.DateTime ==
====================================================
*/
@@ -94,34 +86,6 @@ day_of_week(npy_longlong absdate)
}
}
-/* Returns absolute seconds from an hour, minute, and second
- */
-#define secs_from_hms(hour, min, sec, multiplier) (\
- ((hour)*3600 + (min)*60 + (sec)) * (npy_int64)(multiplier)\
-)
-
-/*
- * Converts an integer number of seconds in a day to hours minutes seconds.
- * It assumes seconds is between 0 and 86399.
- */
-
-static hmsstruct
-seconds_to_hmsstruct(npy_longlong dlong)
-{
- int hour, minute, second;
- hmsstruct hms;
-
- hour = dlong / 3600;
- minute = (dlong % 3600) / 60;
- second = dlong - (hour*3600 + minute*60);
-
- hms.hour = hour;
- hms.min = minute;
- hms.sec = second;
-
- return hms;
-}
-
/*
====================================================
== End of section adapted from mx.DateTime ==
@@ -305,115 +269,111 @@ convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta,
else {
/* Otherwise calculate the number of days to start */
npy_int64 days = get_datetimestruct_days(dts);
+ int dotw;
- if (base == NPY_FR_W) {
- /* Truncate to weeks */
- if (days >= 0) {
- ret = days / 7;
- }
- else {
- ret = (days - 6) / 7;
- }
- }
- else if (base == NPY_FR_B) {
- /* TODO: this needs work... */
- npy_longlong x;
- int dotw = day_of_week(days);
-
- if (dotw > 4) {
- /* Invalid business day */
- ret = 0;
- }
- else {
+ switch (base) {
+ case NPY_FR_W:
+ /* Truncate to weeks */
if (days >= 0) {
- /* offset to adjust first week */
- x = days - 4;
+ ret = days / 7;
}
else {
- x = days - 2;
+ ret = (days - 6) / 7;
}
- ret = 2 + (x / 7) * 5 + x % 7;
- }
- }
- else if (base == NPY_FR_D) {
- ret = days;
- }
- else if (base == NPY_FR_h) {
- ret = days * 24 + dts->hour;
- }
- else if (base == NPY_FR_m) {
- ret = days * 1440 + dts->hour * 60 + dts->min;
- }
- else if (base == NPY_FR_s) {
- ret = days * (npy_int64)(86400) +
- secs_from_hms(dts->hour, dts->min, dts->sec, 1);
- }
- else if (base == NPY_FR_ms) {
- ret = days * (npy_int64)(86400000)
- + secs_from_hms(dts->hour, dts->min, dts->sec, 1000)
- + (dts->us / 1000);
- }
- else if (base == NPY_FR_us) {
- npy_int64 num = 86400 * 1000;
- num *= (npy_int64)(1000);
- ret = days * num + secs_from_hms(dts->hour, dts->min, dts->sec,
- 1000000)
- + dts->us;
- }
- else if (base == NPY_FR_ns) {
- npy_int64 num = 86400 * 1000;
- num *= (npy_int64)(1000 * 1000);
- ret = days * num + secs_from_hms(dts->hour, dts->min, dts->sec,
- 1000000000)
- + dts->us * (npy_int64)(1000) + (dts->ps / 1000);
- }
- else if (base == NPY_FR_ps) {
- npy_int64 num2 = 1000 * 1000;
- npy_int64 num1;
-
- num2 *= (npy_int64)(1000 * 1000);
- num1 = (npy_int64)(86400) * num2;
- ret = days * num1 + secs_from_hms(dts->hour, dts->min, dts->sec, num2)
- + dts->us * (npy_int64)(1000000) + dts->ps;
- }
- else if (base == NPY_FR_fs) {
- /* only 2.6 hours */
- npy_int64 num2 = 1000000;
- num2 *= (npy_int64)(1000000);
- num2 *= (npy_int64)(1000);
-
- /* get number of seconds as a postive or negative number */
- if (days >= 0) {
- ret = secs_from_hms(dts->hour, dts->min, dts->sec, 1);
- }
- else {
- ret = ((dts->hour - 24)*3600 + dts->min*60 + dts->sec);
- }
- ret = ret * num2 + dts->us * (npy_int64)(1000000000)
- + dts->ps * (npy_int64)(1000) + (dts->as / 1000);
- }
- else if (base == NPY_FR_as) {
- /* only 9.2 secs */
- npy_int64 num1, num2;
-
- num1 = 1000000;
- num1 *= (npy_int64)(1000000);
- num2 = num1 * (npy_int64)(1000000);
-
- if (days >= 0) {
- ret = dts->sec;
- }
- else {
- ret = dts->sec - 60;
- }
- ret = ret * num2 + dts->us * num1 + dts->ps * (npy_int64)(1000000)
- + dts->as;
- }
- else {
- /* Something got corrupted */
- PyErr_SetString(PyExc_ValueError,
+ break;
+ case NPY_FR_B:
+ /* TODO: this needs work... */
+ dotw = day_of_week(days);
+
+ if (dotw > 4) {
+ /* Invalid business day */
+ ret = 0;
+ }
+ else {
+ npy_int64 x;
+ if (days >= 0) {
+ /* offset to adjust first week */
+ x = days - 4;
+ }
+ else {
+ x = days - 2;
+ }
+ ret = 2 + (x / 7) * 5 + x % 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;
+ return -1;
}
}
@@ -571,9 +531,8 @@ convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta,
npy_datetime dt,
npy_datetimestruct *out)
{
- hmsstruct hms;
npy_int64 absdays;
- npy_int64 tmp, num1, num2, num3;
+ npy_int64 perday;
/* Initialize the output to all zeros */
memset(out, 0, sizeof(npy_datetimestruct));
@@ -652,190 +611,171 @@ convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta,
break;
case NPY_FR_h:
+ perday = 24LL;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / 24, out);
- out->hour = dt % 24;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - 23) / 24, out);
- out->hour = 23 + (dt + 1) % 24;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
}
+ out->hour = dt;
break;
case NPY_FR_m:
+ perday = 24LL * 60;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / 1440, out);
- out->min = dt % 1440;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - 1439) / 1440, out);
- out->min = 1439 + (dt + 1) % 1440;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
}
- hms = seconds_to_hmsstruct(out->min * 60);
- out->hour = hms.hour;
- out->min = hms.min;
+ out->hour = dt / 60;
+ out->min = dt % 60;
break;
case NPY_FR_s:
+ perday = 24LL * 60 * 60;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / 86400, out);
- out->sec = dt % 86400;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - 86399) / 86400, out);
- out->sec = 86399 + (dt + 1) % 86400;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
}
- hms = seconds_to_hmsstruct(out->sec);
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ out->hour = dt / (60*60);
+ out->min = (dt / 60) % 60;
+ out->sec = dt % 60;
break;
case NPY_FR_ms:
+ perday = 24LL * 60 * 60 * 1000;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / 86400000, out);
- tmp = dt % 86400000;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - 86399999) / 86400000, out);
- tmp = 86399999 + (dt + 1) % 86399999;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
}
- hms = seconds_to_hmsstruct(tmp / 1000);
- out->us = (tmp % 1000)*1000;
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ out->hour = dt / (60*60*1000LL);
+ out->min = (dt / (60*1000LL)) % 60;
+ out->sec = (dt / 1000LL) % 60;
+ out->us = (dt % 1000LL) * 1000;
break;
case NPY_FR_us:
- num1 = 86400000;
- num1 *= 1000;
- num2 = num1 - 1;
+ perday = 24LL * 60LL * 60LL * 1000LL * 1000LL;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / num1, out);
- tmp = dt % num1;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - num2)/ num1, out);
- tmp = num2 + (dt + 1) % num1;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
}
- hms = seconds_to_hmsstruct(tmp / 1000000);
- out->us = tmp % 1000000;
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ out->hour = dt / (60*60*1000000LL);
+ out->min = (dt / (60*1000000LL)) % 60;
+ out->sec = (dt / 1000000LL) % 60;
+ out->us = dt % 1000000LL;
break;
case NPY_FR_ns:
- num1 = 86400000;
- num1 *= 1000000000;
- num2 = num1 - 1;
- num3 = 1000000;
- num3 *= 1000000;
+ perday = 24LL * 60LL * 60LL * 1000LL * 1000LL * 1000LL;
+
if (dt >= 0) {
- set_datetimestruct_days(dt / num1, out);
- tmp = dt % num1;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - num2)/ num1, out);
- tmp = num2 + (dt + 1) % num1;
- }
- hms = seconds_to_hmsstruct(tmp / 1000000000);
- tmp = tmp % 1000000000;
- out->us = tmp / 1000;
- out->ps = (tmp % 1000) * (npy_int64)(1000);
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
+ }
+ out->hour = dt / (60*60*1000000000LL);
+ out->min = (dt / (60*1000000000LL)) % 60;
+ out->sec = (dt / 1000000000LL) % 60;
+ out->us = (dt / 1000LL) % 1000000LL;
+ out->ps = (dt % 1000LL) * 1000;
break;
case NPY_FR_ps:
- num3 = 1000000000;
- num3 *= (npy_int64)(1000);
- num1 = (npy_int64)(86400) * num3;
- num2 = num1 - 1;
+ perday = 24LL * 60 * 60 * 1000 * 1000 * 1000 * 1000;
if (dt >= 0) {
- set_datetimestruct_days(dt / num1, out);
- tmp = dt % num1;
+ set_datetimestruct_days(dt / perday, out);
+ dt = dt % perday;
}
else {
- set_datetimestruct_days((dt - num2) / num1, out);
- tmp = num2 + (dt + 1) % num1;
- }
- hms = seconds_to_hmsstruct(tmp / num3);
- tmp = tmp % num3;
- out->us = tmp / 1000000;
- out->ps = tmp % 1000000;
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ set_datetimestruct_days((dt - (perday-1)) / perday, out);
+ dt = (perday-1) + (dt + 1) % perday;
+ }
+ out->hour = dt / (60*60*1000000000000LL);
+ out->min = (dt / (60*1000000000000LL)) % 60;
+ out->sec = (dt / 1000000000000LL) % 60;
+ out->us = (dt / 1000000LL) % 1000000LL;
+ out->ps = dt % 1000000LL;
break;
case NPY_FR_fs:
- /* entire range is only += 2.6 hours */
- num1 = 1000000000;
- num1 *= (npy_int64)(1000);
- num2 = num1 * (npy_int64)(1000);
-
+ /* entire range is only +- 2.6 hours */
if (dt >= 0) {
- out->sec = dt / num2;
- tmp = dt % num2;
- hms = seconds_to_hmsstruct(out->sec);
- out->hour = hms.hour;
- out->min = hms.min;
- out->sec = hms.sec;
+ out->hour = dt / (60*60*1000000000000000LL);
+ out->min = (dt / (60*1000000000000000LL)) % 60;
+ out->sec = (dt / 1000000000000000LL) % 60;
+ out->us = (dt / 1000000000LL) % 1000000LL;
+ out->ps = (dt / 1000LL) % 1000000LL;
+ out->as = (dt % 1000LL) * 1000;
}
else {
- /* tmp (number of fs) will be positive after this segment */
- out->year = 1969;
- out->day = 31;
- out->month = 12;
- out->sec = (dt - (num2-1))/num2;
- tmp = (num2-1) + (dt + 1) % num2;
- if (out->sec == 0) {
- /* we are at the last second */
- out->hour = 23;
- out->min = 59;
- out->sec = 59;
- }
- else {
- out->hour = 24 + (out->sec - 3599)/3600;
- out->sec = 3599 + (out->sec+1)%3600;
- out->min = out->sec / 60;
- out->sec = out->sec % 60;
+ 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 = (dt / 1000000000000000LL) % 60;
+ out->us = (dt / 1000000000LL) % 1000000LL;
+ out->ps = (dt / 1000LL) % 1000000LL;
+ out->as = (dt % 1000LL) * 1000;
}
- out->us = tmp / 1000000000;
- tmp = tmp % 1000000000;
- out->ps = tmp / 1000;
- out->as = (tmp % 1000) * (npy_int64)(1000);
break;
case NPY_FR_as:
- /* entire range is only += 9.2 seconds */
- num1 = 1000000;
- num2 = num1 * (npy_int64)(1000000);
- num3 = num2 * (npy_int64)(1000000);
+ /* entire range is only +- 9.2 seconds */
if (dt >= 0) {
- out->hour = 0;
- out->min = 0;
- out->sec = dt / num3;
- tmp = dt % num3;
+ out->sec = (dt / 1000000000000000000LL) % 60;
+ out->us = (dt / 1000000000000LL) % 1000000LL;
+ out->ps = (dt / 1000000LL) % 1000000LL;
+ out->as = dt % 1000000LL;
}
else {
- out->year = 1969;
- out->day = 31;
- out->month = 12;
- out->hour = 23;
- out->min = 59;
- out->sec = 60 + (dt - (num3-1)) / num3;
- tmp = (num3-1) + (dt+1) % num3;
+ 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 = (dt / 1000000000000LL) % 1000000LL;
+ out->ps = (dt / 1000000LL) % 1000000LL;
+ out->as = dt % 1000000LL;
}
- out->us = tmp / num2;
- tmp = tmp % num2;
- out->ps = tmp / num1;
- out->as = tmp % num1;
break;
default:
@@ -2280,16 +2220,42 @@ append_metastr_to_string(PyArray_DatetimeMetaData *meta,
}
/*
- * Adjusts a datetimestruct based on a time zone offset. Assumes
+ * 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
-datetimestruct_timezone_offset(npy_datetimestruct *dts, int minutes)
+add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes)
{
int isleap;
/* MINUTES */
- dts->min -= minutes;
+ dts->min += minutes;
while (dts->min < 0) {
dts->min += 60;
dts->hour--;
@@ -2781,7 +2747,7 @@ parse_timezone:
offset_hour = -offset_hour;
offset_minute = -offset_minute;
}
- datetimestruct_timezone_offset(out, 60 * offset_hour + offset_minute);
+ add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute);
}
/* May have a ' ' followed by an event number */
@@ -2826,6 +2792,64 @@ error:
}
/*
+ * Provides a string length to use for converting datetime
+ * objects with the given local and unit settings.
+ */
+NPY_NO_EXPORT int
+get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base)
+{
+ int len = 0;
+
+ /* If no unit is provided, return the maximum length */
+ if (base == -1) {
+ return NPY_DATETIME_MAX_ISO8601_STRLEN;
+ }
+
+ switch (base) {
+ case NPY_FR_as:
+ len += 3; /* "###" */
+ case NPY_FR_fs:
+ len += 3; /* "###" */
+ case NPY_FR_ps:
+ len += 3; /* "###" */
+ case NPY_FR_ns:
+ len += 3; /* "###" */
+ case NPY_FR_us:
+ len += 3; /* "###" */
+ case NPY_FR_ms:
+ len += 4; /* ".###" */
+ case NPY_FR_s:
+ len += 3; /* ":##" */
+ case NPY_FR_m:
+ len += 3; /* ":##" */
+ case NPY_FR_h:
+ len += 3; /* "T##" */
+ case NPY_FR_D:
+ case NPY_FR_B:
+ case NPY_FR_W:
+ len += 3; /* "-##" */
+ case NPY_FR_M:
+ len += 3; /* "-##" */
+ case NPY_FR_Y:
+ len += 21; /* 64-bit year */
+ break;
+ }
+
+ if (base >= NPY_FR_h) {
+ if (local) {
+ len += 5; /* "+####" or "-####" */
+ }
+ else {
+ len += 1; /* "Z" */
+ }
+ }
+
+ len += 1; /* NULL terminator */
+
+ return len;
+}
+
+/*
* Converts an npy_datetimestruct to an (almost) ISO 8601
* NULL-terminated string.
*
@@ -2835,12 +2859,16 @@ error:
* 'base' restricts the output to that unit. Set 'base' to
* -1 to auto-detect a base after which all the values are zero.
*
+ * 'tzoffset' is used if 'local' is enabled, and 'tzoffset' is
+ * set to a value other than -1. This is a manual override for
+ * the local time zone to use, as an offset in minutes.
+ *
* 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)
+ int local, NPY_DATETIMEUNIT base, int tzoffset)
{
npy_datetimestruct dts_local;
int timezone_offset = 0;
@@ -2862,7 +2890,7 @@ make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen,
}
/* Only do local time within a reasonable year range */
- if (dts->year <= 1900 || dts->year >= 10000) {
+ if ((dts->year <= 1900 || dts->year >= 10000) && tzoffset == -1) {
local = 0;
}
@@ -2889,22 +2917,16 @@ make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen,
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)) {
+ /*
+ * hours and minutes don't get split up by default, and printing
+ * in local time forces minutes
+ */
+ else if (local || dts->min != 0 || 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;
- }
+ /* dates don't get split up by default */
else {
- base = NPY_FR_Y;
+ base = NPY_FR_D;
}
}
/*
@@ -2923,7 +2945,7 @@ make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen,
}
/* Use the C API to convert from UTC to local time */
- if (local) {
+ if (local && tzoffset == -1) {
time_t rawtime = 0, localrawtime;
struct tm tm_;
@@ -2973,6 +2995,16 @@ make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen,
/* Set dts to point to our local time instead of the UTC time */
dts = &dts_local;
}
+ /* Use the manually provided tzoffset */
+ else if (local) {
+ /* Make a copy of the npy_datetimestruct we can modify */
+ dts_local = *dts;
+ dts = &dts_local;
+
+ /* Set and apply the required timezone offset */
+ timezone_offset = tzoffset;
+ add_minutes_to_datetimestruct(dts, timezone_offset);
+ }
/* YEAR */
#ifdef _WIN32
@@ -3466,7 +3498,7 @@ convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out)
/* Convert to a minutes offset and apply it */
minutes_offset = seconds_offset / 60;
- datetimestruct_timezone_offset(out, minutes_offset);
+ add_minutes_to_datetimestruct(out, -minutes_offset);
}
}
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index 054bdb6c1..f1103ef7f 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -2806,6 +2806,170 @@ array_datetime_data(PyObject *NPY_UNUSED(dummy), PyObject *args)
return convert_datetime_metadata_to_tuple(meta);
}
+static PyObject *
+array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args,
+ PyObject *kwds)
+{
+ PyObject *arr_in = NULL, *unit_in = NULL;
+ int local = 0, tzoffset = -1;
+ NPY_DATETIMEUNIT unit;
+ PyArray_DatetimeMetaData *meta;
+ int strsize;
+
+ PyArrayObject *ret = NULL;
+
+ NpyIter *iter = NULL;
+ PyArrayObject *op[2] = {NULL, NULL};
+ PyArray_Descr *op_dtypes[2] = {NULL, NULL};
+ npy_uint32 flags, op_flags[2];
+
+ static char *kwlist[] = {"arr", "local", "unit", "tzoffset", NULL};
+
+ if(!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|iOi:datetime_as_string", kwlist,
+ &arr_in,
+ &local,
+ &unit_in,
+ &tzoffset)) {
+ goto fail;
+ }
+
+ op[0] = (PyArrayObject *)PyArray_FromAny(arr_in,
+ NULL, 0, 0, 0, NULL);
+ if (PyArray_DESCR(op[0])->type_num != NPY_DATETIME) {
+ PyErr_SetString(PyExc_TypeError,
+ "input must have type NumPy datetime");
+ goto fail;
+ }
+
+ /* Get the datetime metadata */
+ meta = get_datetime_metadata_from_dtype(PyArray_DESCR(op[0]));
+ if (meta == NULL) {
+ goto fail;
+ }
+
+ /* Use the metadata's unit for printing by default */
+ unit = meta->base;
+
+ /* Parse the input unit if provided */
+ if (unit_in != NULL && unit_in != Py_None) {
+ PyObject *strobj;
+ char *str = NULL;
+ Py_ssize_t len = 0;
+
+ if (PyUnicode_Check(unit_in)) {
+ strobj = PyUnicode_AsASCIIString(unit_in);
+ if (strobj == NULL) {
+ goto fail;
+ }
+ }
+ else {
+ strobj = unit_in;
+ Py_INCREF(strobj);
+ }
+
+ if (PyBytes_AsStringAndSize(strobj, &str, &len) < 0) {
+ Py_DECREF(strobj);
+ goto fail;
+ }
+
+ /* unit == -1 means to autodetect the unit from the datetime data */
+ if (strcmp(str, "auto") == 0) {
+ unit = -1;
+ }
+ else {
+ unit = parse_datetime_unit_from_string(str, len, NULL);
+ if (unit == -1) {
+ Py_DECREF(strobj);
+ goto fail;
+ }
+ }
+ Py_DECREF(strobj);
+ }
+
+ if (!local && tzoffset != -1) {
+ PyErr_SetString(PyExc_ValueError,
+ "Can only use 'tzoffset' parameter when 'local' is "
+ "set to True");
+ goto fail;
+ }
+
+ /* Create the output string data type with a big enough length */
+ op_dtypes[1] = PyArray_DescrNewFromType(NPY_STRING);
+ if (op_dtypes[1] == NULL) {
+ goto fail;
+ }
+ strsize = get_datetime_iso_8601_strlen(local, unit);
+ op_dtypes[1]->elsize = strsize;
+
+ flags = NPY_ITER_ZEROSIZE_OK|
+ NPY_ITER_BUFFERED;
+ op_flags[0] = NPY_ITER_READONLY|
+ NPY_ITER_ALIGNED;
+ op_flags[1] = NPY_ITER_WRITEONLY|
+ NPY_ITER_ALLOCATE;
+
+ iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_NO_CASTING,
+ op_flags, op_dtypes);
+ if (iter == NULL) {
+ goto fail;
+ }
+
+ if (NpyIter_GetIterSize(iter) != 0) {
+ NpyIter_IterNextFunc *iternext;
+ char **dataptr;
+ npy_datetime dt;
+ npy_datetimestruct dts;
+
+ iternext = NpyIter_GetIterNext(iter, NULL);
+ if (iternext == NULL) {
+ goto fail;
+ }
+ dataptr = NpyIter_GetDataPtrArray(iter);
+
+ do {
+ /* Get the datetime */
+ dt = *(datetime *)dataptr[0];
+ /* Convert it to a struct */
+ if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) {
+ goto fail;
+ }
+ /* Zero the destination string completely */
+ memset(dataptr[1], 0, strsize);
+ /* Convert that into a string */
+ if (make_iso_8601_date(&dts, (char *)dataptr[1], strsize,
+ local, unit, tzoffset) < 0) {
+ goto fail;
+ }
+ } while(iternext(iter));
+ }
+
+ ret = NpyIter_GetOperandArray(iter)[1];
+ Py_INCREF(ret);
+
+ Py_XDECREF(op[0]);
+ Py_XDECREF(op[1]);
+ Py_XDECREF(op_dtypes[0]);
+ Py_XDECREF(op_dtypes[1]);
+ if (iter != NULL) {
+ NpyIter_Deallocate(iter);
+ }
+
+ return PyArray_Return(ret);
+
+fail:
+ Py_XDECREF(op[0]);
+ Py_XDECREF(op[1]);
+ Py_XDECREF(op_dtypes[0]);
+ Py_XDECREF(op_dtypes[1]);
+ if (iter != NULL) {
+ NpyIter_Deallocate(iter);
+ }
+
+ return NULL;
+}
+
+
#if !defined(NPY_PY3K)
static PyObject *
new_buffer(PyObject *NPY_UNUSED(dummy), PyObject *args)
@@ -3429,6 +3593,9 @@ static struct PyMethodDef array_module_methods[] = {
{"datetime_data",
(PyCFunction)array_datetime_data,
METH_VARARGS, NULL},
+ {"datetime_as_string",
+ (PyCFunction)array_datetime_as_string,
+ METH_VARARGS | METH_KEYWORDS, NULL},
#if !defined(NPY_PY3K)
{"newbuffer",
(PyCFunction)new_buffer,
diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src
index 03b3c8c86..2fea9d28d 100644
--- a/numpy/core/src/multiarray/scalartypes.c.src
+++ b/numpy/core/src/multiarray/scalartypes.c.src
@@ -618,7 +618,8 @@ datetimetype_repr(PyObject *self)
return NULL;
}
- if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, scal->obmeta.base) < 0) {
+ if (make_iso_8601_date(&dts, iso, sizeof(iso), 1,
+ scal->obmeta.base, -1) < 0) {
return NULL;
}
@@ -678,7 +679,8 @@ datetimetype_str(PyObject *self)
return NULL;
}
- if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, scal->obmeta.base) < 0) {
+ if (make_iso_8601_date(&dts, iso, sizeof(iso), 1,
+ scal->obmeta.base, -1) < 0) {
return NULL;
}
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index d2a25c1ff..bb6594a9a 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -873,6 +873,125 @@ class TestDateTime(TestCase):
assert_equal(x[0].astype(np.int64), 322689600000000000)
+ def test_datetime_as_string(self):
+ # Check all the units with default string conversion
+ date = '1959-10-13T12:34:56.789012345678901234Z'
+
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'Y')),
+ '1959')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'M')),
+ '1959-10')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'D')),
+ '1959-10-13')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'h')),
+ '1959-10-13T12Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'm')),
+ '1959-10-13T12:34Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 's')),
+ '1959-10-13T12:34:56Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'ms')),
+ '1959-10-13T12:34:56.789Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'us')),
+ '1959-10-13T12:34:56.789012Z')
+
+ date = '1969-12-31T23:34:56.789012345678901234Z'
+
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'ns')),
+ '1969-12-31T23:34:56.789012345Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'ps')),
+ '1969-12-31T23:34:56.789012345678Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'fs')),
+ '1969-12-31T23:34:56.789012345678901Z')
+
+ date = '1969-12-31T23:59:57.789012345678901234Z'
+
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'as')),
+ date);
+ date = '1970-01-01T00:34:56.789012345678901234Z'
+
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'ns')),
+ '1970-01-01T00:34:56.789012345Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'ps')),
+ '1970-01-01T00:34:56.789012345678Z')
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'fs')),
+ '1970-01-01T00:34:56.789012345678901Z')
+
+ date = '1970-01-01T00:00:05.789012345678901234Z'
+
+ assert_equal(np.datetime_as_string(np.datetime64(date, 'as')),
+ date);
+
+ # String conversion with the unit= parameter
+ a = np.datetime64('2032-07-18T12:23:34.123456Z', 'us')
+ assert_equal(np.datetime_as_string(a, unit='Y'), '2032')
+ assert_equal(np.datetime_as_string(a, unit='M'), '2032-07')
+ assert_equal(np.datetime_as_string(a, unit='W'), '2032-07-18')
+ assert_equal(np.datetime_as_string(a, unit='D'), '2032-07-18')
+ assert_equal(np.datetime_as_string(a, unit='h'), '2032-07-18T12Z')
+ assert_equal(np.datetime_as_string(a, unit='m'),
+ '2032-07-18T12:23Z')
+ assert_equal(np.datetime_as_string(a, unit='s'),
+ '2032-07-18T12:23:34Z')
+ assert_equal(np.datetime_as_string(a, unit='ms'),
+ '2032-07-18T12:23:34.123Z')
+ assert_equal(np.datetime_as_string(a, unit='us'),
+ '2032-07-18T12:23:34.123456Z')
+ assert_equal(np.datetime_as_string(a, unit='ns'),
+ '2032-07-18T12:23:34.123456000Z')
+ assert_equal(np.datetime_as_string(a, unit='ps'),
+ '2032-07-18T12:23:34.123456000000Z')
+ assert_equal(np.datetime_as_string(a, unit='fs'),
+ '2032-07-18T12:23:34.123456000000000Z')
+ assert_equal(np.datetime_as_string(a, unit='as'),
+ '2032-07-18T12:23:34.123456000000000000Z')
+
+ # unit='auto' parameter
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T12:23:34.123456Z', 'us'),
+ unit='auto'),
+ '2032-07-18T12:23:34.123456Z')
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T12:23:34.12Z', 'us'),
+ unit='auto'),
+ '2032-07-18T12:23:34.120Z')
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T12:23:34Z', 'us'),
+ unit='auto'),
+ '2032-07-18T12:23:34Z')
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T12:23:00Z', 'us'),
+ unit='auto'),
+ '2032-07-18T12:23Z')
+ # 'auto' doesn't split up hour and minute
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T12:00:00Z', 'us'),
+ unit='auto'),
+ '2032-07-18T12:00Z')
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-18T00:00:00Z', 'us'),
+ unit='auto'),
+ '2032-07-18')
+ # 'auto' doesn't split up the date
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-07-01T00:00:00Z', 'us'),
+ unit='auto'),
+ '2032-07-01')
+ assert_equal(np.datetime_as_string(
+ np.datetime64('2032-01-01T00:00:00Z', 'us'),
+ unit='auto'),
+ '2032-01-01')
+
+ # local=True
+ a = np.datetime64('2010-03-15T06:30Z', 'm')
+ assert_(np.datetime_as_string(a, local=True) != '2010-03-15T6:30Z')
+ # local=True with tzoffset
+ assert_equal(np.datetime_as_string(a, local=True, tzoffset=-60),
+ '2010-03-15T05:30-0100')
+ assert_equal(np.datetime_as_string(a, local=True, tzoffset=+30),
+ '2010-03-15T07:00+0030')
+ assert_equal(np.datetime_as_string(a, local=True, tzoffset=-5*60),
+ '2010-03-15T01:30-0500')
+
class TestDateTimeData(TestCase):
def test_basic(self):