diff options
author | Mark Wiebe <mwiebe@enthought.com> | 2011-06-03 18:15:04 -0500 |
---|---|---|
committer | Mark Wiebe <mwiebe@enthought.com> | 2011-06-03 18:15:04 -0500 |
commit | 283b2e712bd52e6661f2dc338eb14caae2f5a8da (patch) | |
tree | 0d8d4b4fd5e6c10c524e9f6f2bf4c4ccee0bd170 | |
parent | de719936cf64df1a8f1096fb7f3a2be720906da4 (diff) | |
download | numpy-283b2e712bd52e6661f2dc338eb14caae2f5a8da.tar.gz |
ENH: datetime: Finish fixing up datetime printing, add datetime to string function with several options
-rw-r--r-- | numpy/core/arrayprint.py | 12 | ||||
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 2 | ||||
-rw-r--r-- | numpy/core/numerictypes.py | 4 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_datetime.h | 28 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 606 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 167 | ||||
-rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 6 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 119 |
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): |