diff options
-rw-r--r-- | doc/release/1.11.0-notes.rst | 37 | ||||
-rw-r--r-- | doc/source/reference/arrays.datetime.rst | 62 | ||||
-rw-r--r-- | numpy/core/arrayprint.py | 14 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 32 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime_strings.c | 228 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime_strings.h | 7 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtype_transfer.c | 6 | ||||
-rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 22 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 369 | ||||
-rw-r--r-- | numpy/core/tests/test_deprecations.py | 29 |
10 files changed, 349 insertions, 457 deletions
diff --git a/doc/release/1.11.0-notes.rst b/doc/release/1.11.0-notes.rst index b5d22d770..ac3c1578c 100644 --- a/doc/release/1.11.0-notes.rst +++ b/doc/release/1.11.0-notes.rst @@ -7,6 +7,8 @@ This release supports Python 2.6 - 2.7 and 3.2 - 3.5. Highlights ========== +* The datetime64 type is now timezone naive. See "datetime64 changes" below + for more details. Dropped Support =============== @@ -25,6 +27,41 @@ Future Changes Compatibility notes =================== +datetime64 changes +~~~~~~~~~~~~~~~~~~ + +In prior versions of NumPy the experimental datetime64 type always stored +times in UTC. By default, creating a datetime64 object from a string or +printing it would convert from or to local time:: + + # old behavior + >>>> np.datetime64('2000-01-01T00:00:00') + numpy.datetime64('2000-01-01T00:00:00-0800') # note the timezone offset -08:00 + +A concensus of datetime64 users agreed that this behavior is undesirable +and at odds with how datetime64 is usually used (e.g., by pandas_). For +most use cases, a timezone naive datetime type is preferred, similar to the +``datetime.datetime`` type in the Python standard library. Accordingly, +datetime64 no longer assumes that input is in local time, nor does it print +local times:: + + >>>> np.datetime64('2000-01-01T00:00:00') + numpy.datetime64('2000-01-01T00:00:00') + +For backwards compatibility, datetime64 still parses timezone offsets, which +it handles by converting to UTC. However, the resulting datetime is timezone +naive:: + + >>> np.datetime64('2000-01-01T00:00:00-08') + DeprecationWarning: parsing timezone aware datetimes is deprecated; this will raise an error in the future + numpy.datetime64('2000-01-01T08:00:00') + +As a corollary to this change, we no longer prohibit casting between datetimes +with date units and datetimes with timeunits. With timezone naive datetimes, +the rule for casting from dates to times is no longer ambiguous. + +pandas_: http://pandas.pydata.org + DeprecationWarning to error ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/reference/arrays.datetime.rst b/doc/source/reference/arrays.datetime.rst index 0e8050b01..f5b454875 100644 --- a/doc/source/reference/arrays.datetime.rst +++ b/doc/source/reference/arrays.datetime.rst @@ -45,16 +45,10 @@ some additional SI-prefix seconds-based units. >>> np.datetime64('2005-02', 'D') numpy.datetime64('2005-02-01') - Using UTC "Zulu" time: - - >>> np.datetime64('2005-02-25T03:30Z') - numpy.datetime64('2005-02-24T21:30-0600') - - ISO 8601 specifies to use the local time zone - if none is explicitly given: + From a date and time: >>> np.datetime64('2005-02-25T03:30') - numpy.datetime64('2005-02-25T03:30-0600') + numpy.datetime64('2005-02-25T03:30') When creating an array of datetimes from a string, it is still possible to automatically select the unit from the inputs, by using the @@ -100,23 +94,6 @@ because the moment of time is still being represented exactly. >>> np.datetime64('2010-03-14T15Z') == np.datetime64('2010-03-14T15:00:00.00Z') True -An important exception to this rule is between datetimes with -:ref:`date units <arrays.dtypes.dateunits>` and datetimes with -:ref:`time units <arrays.dtypes.timeunits>`. This is because this kind -of conversion generally requires a choice of timezone and -particular time of day on the given date. - -.. admonition:: Example - - >>> np.datetime64('2003-12-25', 's') - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - TypeError: Cannot parse "2003-12-25" as unit 's' using casting rule 'same_kind' - - >>> np.datetime64('2003-12-25') == np.datetime64('2003-12-25T00Z') - False - - Datetime and Timedelta Arithmetic ================================= @@ -353,6 +330,41 @@ Some examples:: # any amount of whitespace is allowed; abbreviations are case-sensitive. weekmask = "MonTue Wed Thu\tFri" +Changes with NumPy 1.11 +======================= + +In prior versions of NumPy, the datetime64 type always stored +times in UTC. By default, creating a datetime64 object from a string or +printing it would convert from or to local time:: + + # old behavior + >>>> np.datetime64('2000-01-01T00:00:00') + numpy.datetime64('2000-01-01T00:00:00-0800') # note the timezone offset -08:00 + +A concensus of datetime64 users agreed that this behavior is undesirable +and at odds with how datetime64 is usually used (e.g., by pandas_). For +most use cases, a timezone naive datetime type is preferred, similar to the +``datetime.datetime`` type in the Python standard library. Accordingly, +datetime64 no longer assumes that input is in local time, nor does it print +local times:: + + >>>> np.datetime64('2000-01-01T00:00:00') + numpy.datetime64('2000-01-01T00:00:00') + +For backwards compatibility, datetime64 still parses timezone offsets, which +it handles by converting to UTC. However, the resulting datetime is timezone +naive:: + + >>> np.datetime64('2000-01-01T00:00:00-08') + DeprecationWarning: parsing timezone aware datetimes is deprecated; this will raise an error in the future + numpy.datetime64('2000-01-01T08:00:00') + +As a corollary to this change, we no longer prohibit casting between datetimes +with date units and datetimes with timeunits. With timezone naive datetimes, +the rule for casting from dates to times is no longer ambiguous. + +pandas_: http://pandas.pydata.org + Differences Between 1.6 and 1.7 Datetimes ========================================= diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index c5b5b5a8f..74a9d3da3 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -708,9 +708,9 @@ class ComplexFormat(object): i = i + 'j' return r + i + class DatetimeFormat(object): - def __init__(self, x, unit=None, - timezone=None, casting='same_kind'): + def __init__(self, x, unit=None, timezone=None, casting='same_kind'): # Get the unit from the dtype if unit is None: if x.dtype.kind == 'M': @@ -718,15 +718,9 @@ class DatetimeFormat(object): else: unit = 's' - # If timezone is default, make it 'local' or 'UTC' based on the unit if timezone is None: - # Date units -> UTC, time units -> local - if unit in ('Y', 'M', 'W', 'D'): - self.timezone = 'UTC' - else: - self.timezone = 'local' - else: - self.timezone = timezone + timezone = 'naive' + self.timezone = timezone self.unit = unit self.casting = casting diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 264178d30..3cf9a2bd5 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -1316,9 +1316,6 @@ datetime_metadata_divides( /* * This provides the casting rules for the DATETIME data type units. - * - * Notably, there is a barrier between 'date units' and 'time units' - * for all but 'unsafe' casting. */ NPY_NO_EXPORT npy_bool can_cast_datetime64_units(NPY_DATETIMEUNIT src_unit, @@ -1331,31 +1328,26 @@ can_cast_datetime64_units(NPY_DATETIMEUNIT src_unit, return 1; /* - * Only enforce the 'date units' vs 'time units' barrier with - * 'same_kind' casting. + * Can cast between all units with 'same_kind' casting. */ case NPY_SAME_KIND_CASTING: if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) { return src_unit == NPY_FR_GENERIC; } else { - return (src_unit <= NPY_FR_D && dst_unit <= NPY_FR_D) || - (src_unit > NPY_FR_D && dst_unit > NPY_FR_D); + return 1; } /* - * Enforce the 'date units' vs 'time units' barrier and that - * casting is only allowed towards more precise units with - * 'safe' casting. + * Casting is only allowed towards more precise units with 'safe' + * casting. */ case NPY_SAFE_CASTING: if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) { return src_unit == NPY_FR_GENERIC; } else { - return (src_unit <= dst_unit) && - ((src_unit <= NPY_FR_D && dst_unit <= NPY_FR_D) || - (src_unit > NPY_FR_D && dst_unit > NPY_FR_D)); + return (src_unit <= dst_unit); } /* Enforce equality with 'no' or 'equiv' casting */ @@ -2254,6 +2246,14 @@ convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out, PyObject *offset; int seconds_offset, minutes_offset; + /* 2016-01-14, 1.11 */ + PyErr_Clear(); + if (DEPRECATE( + "parsing timezone aware datetimes is deprecated; " + "this will raise an error in the future") < 0) { + return -1; + } + /* The utcoffset function should return a timedelta */ offset = PyObject_CallMethod(tmp, "utcoffset", "O", obj); if (offset == NULL) { @@ -2386,7 +2386,7 @@ convert_pyobject_to_datetime(PyArray_DatetimeMetaData *meta, PyObject *obj, /* Parse the ISO date */ if (parse_iso_8601_datetime(str, len, meta->base, casting, - &dts, NULL, &bestunit, NULL) < 0) { + &dts, &bestunit, NULL) < 0) { Py_DECREF(bytes); return -1; } @@ -3500,7 +3500,7 @@ find_string_array_datetime64_type(PyArrayObject *arr, tmp_meta.base = -1; if (parse_iso_8601_datetime(tmp_buffer, maxlen, -1, - NPY_UNSAFE_CASTING, &dts, NULL, + NPY_UNSAFE_CASTING, &dts, &tmp_meta.base, NULL) < 0) { goto fail; } @@ -3509,7 +3509,7 @@ find_string_array_datetime64_type(PyArrayObject *arr, else { tmp_meta.base = -1; if (parse_iso_8601_datetime(data, tmp - data, -1, - NPY_UNSAFE_CASTING, &dts, NULL, + NPY_UNSAFE_CASTING, &dts, &tmp_meta.base, NULL) < 0) { goto fail; } diff --git a/numpy/core/src/multiarray/datetime_strings.c b/numpy/core/src/multiarray/datetime_strings.c index 54587cb5c..09ddc46d4 100644 --- a/numpy/core/src/multiarray/datetime_strings.c +++ b/numpy/core/src/multiarray/datetime_strings.c @@ -111,82 +111,6 @@ fail: } /* - * Wraps `gmtime` functionality for multiple platforms. This - * converts a time value to a time structure in UTC. - * - * Returns 0 on success, -1 on failure. - */ -static int -get_gmtime(NPY_TIME_T *ts, struct tm *tms) -{ - char *func_name = "<unknown>"; -#if defined(_WIN32) - #if defined(_MSC_VER) && (_MSC_VER >= 1400) - if (gmtime_s(tms, ts) != 0) { - func_name = "gmtime_s"; - goto fail; - } - #elif defined(NPY_MINGW_USE_CUSTOM_MSVCR) - if (_gmtime64_s(tms, ts) != 0) { - func_name = "_gmtime64_s"; - goto fail; - } - #else - struct tm *tms_tmp; - tms_tmp = gmtime(ts); - if (tms_tmp == NULL) { - func_name = "gmtime"; - goto fail; - } - memcpy(tms, tms_tmp, sizeof(struct tm)); - #endif -#else - if (gmtime_r(ts, tms) == NULL) { - func_name = "gmtime_r"; - goto fail; - } -#endif - - return 0; - -fail: - PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert " - "to a UTC time", func_name); - return -1; -} - -/* - * Wraps `mktime` functionality for multiple platforms. This - * converts a local time struct to an UTC value. - * - * Returns timestamp on success, -1 on failure. - */ -static NPY_TIME_T -get_mktime(struct tm *tms) -{ - char *func_name = "<unknown>"; - NPY_TIME_T ts; -#if defined(NPY_MINGW_USE_CUSTOM_MSVCR) - ts = _mktime64(tms); - if (ts == -1) { - func_name = "_mktime64"; - goto fail; - } -#else - ts = mktime(tms); - if (ts == -1) { - func_name = "mktime"; - goto fail; - } -#endif - return ts; -fail: - PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert " - "local time to UTC", func_name); - return -1; -} - -/* * Converts a datetimestruct in UTC to a datetimestruct in local time, * also returning the timezone offset applied. This function works for any year * > 1970 on all platforms and both 32 and 64 bits. If the year < 1970, then it @@ -263,85 +187,6 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local, } /* - * Converts a datetimestruct in local time to a datetimestruct in UTC. - * - * Returns 0 on success, -1 on failure. - */ -static int -convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc, - const npy_datetimestruct *dts_local) -{ - npy_int64 year_correction = 0; - - /* Make a copy of the input 'dts' to modify */ - *out_dts_utc = *dts_local; - - /* - * For 32 bit NPY_TIME_T, the get_mktime()/get_gmtime() functions do not - * work for years later than 2038. So if the year >= 2038, we instead call - * get_mktime()/get_gmtime() for the year 2036 or 2037 (depending on the - * leap year) which must work and at the end we add the 'year_correction' - * back. - */ - if (sizeof(NPY_TIME_T) == 4 && out_dts_utc->year >= 2038) { - if (is_leapyear(out_dts_utc->year)) { - /* 2036 is a leap year */ - year_correction = out_dts_utc->year - 2036; - out_dts_utc->year -= year_correction; /* = 2036 */ - } - else { - /* 2037 is not a leap year */ - year_correction = out_dts_utc->year - 2037; - out_dts_utc->year -= year_correction; /* = 2037 */ - } - } - - /* - * ISO 8601 states to treat date-times without a timezone offset - * or 'Z' for UTC as local time. The C standard libary functions - * mktime and gmtime allow us to do this conversion. - * - * Only do this timezone adjustment for recent and future years. - * In this case, "recent" is defined to be 1970 and later, because - * on MS Windows, mktime raises an error when given an earlier date. - */ - if (out_dts_utc->year >= 1970) { - NPY_TIME_T rawtime = 0; - struct tm tm_; - - tm_.tm_sec = out_dts_utc->sec; - tm_.tm_min = out_dts_utc->min; - tm_.tm_hour = out_dts_utc->hour; - tm_.tm_mday = out_dts_utc->day; - tm_.tm_mon = out_dts_utc->month - 1; - tm_.tm_year = out_dts_utc->year - 1900; - tm_.tm_isdst = -1; - - /* mktime converts a local 'struct tm' into a time_t */ - rawtime = get_mktime(&tm_); - if (rawtime == -1) { - return -1; - } - - /* gmtime converts a 'time_t' into a UTC 'struct tm' */ - if (get_gmtime(&rawtime, &tm_) < 0) { - return -1; - } - out_dts_utc->sec = tm_.tm_sec; - out_dts_utc->min = tm_.tm_min; - out_dts_utc->hour = tm_.tm_hour; - out_dts_utc->day = tm_.tm_mday; - out_dts_utc->month = tm_.tm_mon + 1; - out_dts_utc->year = tm_.tm_year + 1900; - } - - /* Reapply the year 2038 year correction */ - out_dts_utc->year += year_correction; - - return 0; -} - -/* * Parses (almost) standard ISO 8601 date strings. The differences are: * * + The date "20100312" is parsed as the year 20100312, not as @@ -363,10 +208,6 @@ convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc, * to be cast to the 'unit' parameter. * * 'out' gets filled with the parsed date-time. - * 'out_local' gets set to 1 if the parsed time was in local time, - * to 0 otherwise. The values 'now' and 'today' don't get counted - * as local, and neither do UTC +/-#### timezone offsets, because - * they aren't using the computer's local timezone offset. * 'out_bestunit' gives a suggested unit based on the amount of * resolution provided in the string, or -1 for NaT. * 'out_special' gets set to 1 if the parsed time was 'today', @@ -381,7 +222,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, NPY_DATETIMEUNIT unit, NPY_CASTING casting, npy_datetimestruct *out, - npy_bool *out_local, NPY_DATETIMEUNIT *out_bestunit, npy_bool *out_special) { @@ -411,9 +251,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, * Indicate that this was a special value, and * recommend generic units. */ - if (out_local != NULL) { - *out_local = 0; - } if (out_bestunit != NULL) { *out_bestunit = NPY_FR_GENERIC; } @@ -462,9 +299,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, * Indicate that this was a special value, and * is a date (unit 'D'). */ - if (out_local != NULL) { - *out_local = 0; - } if (out_bestunit != NULL) { *out_bestunit = bestunit; } @@ -505,9 +339,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, * use 's' because the time() function has resolution * seconds. */ - if (out_local != NULL) { - *out_local = 0; - } if (out_bestunit != NULL) { *out_bestunit = bestunit; } @@ -569,9 +400,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, /* Next character must be a '-' or the end of the string */ if (sublen == 0) { - if (out_local != NULL) { - *out_local = 0; - } bestunit = NPY_FR_Y; goto finish; } @@ -606,9 +434,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, /* Next character must be a '-' or the end of the string */ if (sublen == 0) { - if (out_local != NULL) { - *out_local = 0; - } bestunit = NPY_FR_M; goto finish; } @@ -644,9 +469,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, /* Next character must be a 'T', ' ', or end of string */ if (sublen == 0) { - if (out_local != NULL) { - *out_local = 0; - } bestunit = NPY_FR_D; goto finish; } @@ -811,25 +633,20 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, parse_timezone: if (sublen == 0) { - if (convert_datetimestruct_local_to_utc(out, out) < 0) { - goto error; - } - - /* Since neither "Z" nor a time-zone was specified, it's local */ - if (out_local != NULL) { - *out_local = 1; - } - goto finish; } + else { + /* 2016-01-14, 1.11 */ + PyErr_Clear(); + if (DEPRECATE( + "parsing timezone aware datetimes is deprecated; " + "this will raise an error in the future") < 0) { + return -1; + } + } /* UTC specifier */ if (*substr == 'Z') { - /* "Z" means not local */ - if (out_local != NULL) { - *out_local = 0; - } - if (sublen == 1) { goto finish; } @@ -842,14 +659,6 @@ parse_timezone: else if (*substr == '-' || *substr == '+') { int offset_neg = 0, offset_hour = 0, offset_minute = 0; - /* - * Since "local" means local with respect to the current - * machine, we say this is non-local. - */ - if (out_local != NULL) { - *out_local = 0; - } - if (*substr == '-') { offset_neg = 1; } @@ -1056,7 +865,9 @@ lossless_unit_from_datetimestruct(npy_datetimestruct *dts) * the number of year digits is >= 4 instead of strictly 4. * * If 'local' is non-zero, it produces a string in local time with - * a +-#### timezone offset, otherwise it uses timezone Z (UTC). + * a +-#### timezone offset. If 'local' is zero and 'utc' is non-zero, + * produce a string ending with 'Z' to denote UTC. By default, no time + * zone information is attached. * * 'base' restricts the output to that unit. Set 'base' to * -1 to auto-detect a base after which all the values are zero. @@ -1075,7 +886,7 @@ lossless_unit_from_datetimestruct(npy_datetimestruct *dts) */ NPY_NO_EXPORT int make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen, - int local, NPY_DATETIMEUNIT base, int tzoffset, + int local, int utc, NPY_DATETIMEUNIT base, int tzoffset, NPY_CASTING casting) { npy_datetimestruct dts_local; @@ -1491,7 +1302,7 @@ add_time_zone: sublen -= 4; } /* UTC "Zulu" time */ - else { + else if (utc) { if (sublen < 1) { goto string_too_short; } @@ -1528,6 +1339,7 @@ array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args, NPY_CASTING casting = NPY_SAME_KIND_CASTING; int local = 0; + int utc = 0; PyArray_DatetimeMetaData *meta; int strsize; @@ -1643,11 +1455,19 @@ array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args, if (strcmp(str, "local") == 0) { local = 1; + utc = 0; Py_DECREF(timezone_obj); timezone_obj = NULL; } else if (strcmp(str, "UTC") == 0) { local = 0; + utc = 1; + Py_DECREF(timezone_obj); + timezone_obj = NULL; + } + else if (strcmp(str, "naive") == 0) { + local = 0; + utc = 0; Py_DECREF(timezone_obj); timezone_obj = NULL; } @@ -1738,7 +1558,7 @@ array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args, memset(dataptr[1], 0, strsize); /* Convert that into a string */ if (make_iso_8601_datetime(&dts, (char *)dataptr[1], strsize, - local, unit, tzoffset, casting) < 0) { + local, utc, unit, tzoffset, casting) < 0) { goto fail; } } while(iternext(iter)); diff --git a/numpy/core/src/multiarray/datetime_strings.h b/numpy/core/src/multiarray/datetime_strings.h index 4280f6de4..d7608565c 100644 --- a/numpy/core/src/multiarray/datetime_strings.h +++ b/numpy/core/src/multiarray/datetime_strings.h @@ -23,10 +23,6 @@ * to be cast to the 'unit' parameter. * * 'out' gets filled with the parsed date-time. - * 'out_local' gets set to 1 if the parsed time was in local time, - * to 0 otherwise. The values 'now' and 'today' don't get counted - * as local, and neither do UTC +/-#### timezone offsets, because - * they aren't using the computer's local timezone offset. * 'out_bestunit' gives a suggested unit based on the amount of * resolution provided in the string, or -1 for NaT. * 'out_special' gets set to 1 if the parsed time was 'today', @@ -41,7 +37,6 @@ parse_iso_8601_datetime(char *str, Py_ssize_t len, NPY_DATETIMEUNIT unit, NPY_CASTING casting, npy_datetimestruct *out, - npy_bool *out_local, NPY_DATETIMEUNIT *out_bestunit, npy_bool *out_special); @@ -76,7 +71,7 @@ get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base); */ NPY_NO_EXPORT int make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen, - int local, NPY_DATETIMEUNIT base, int tzoffset, + int local, int utc, NPY_DATETIMEUNIT base, int tzoffset, NPY_CASTING casting); /* diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index f11ea395f..bfb22ac30 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -868,7 +868,7 @@ _strided_to_strided_datetime_to_string(char *dst, npy_intp dst_stride, * to use PyErr_Occurred(). */ make_iso_8601_datetime(&dts, dst, dst_itemsize, - 0, d->src_meta.base, -1, + 0, 0, d->src_meta.base, -1, NPY_UNSAFE_CASTING); dst += dst_stride; @@ -901,7 +901,7 @@ _strided_to_strided_string_to_datetime(char *dst, npy_intp dst_stride, if (parse_iso_8601_datetime(tmp_buffer, src_itemsize, d->dst_meta.base, NPY_SAME_KIND_CASTING, - &dts, NULL, NULL, NULL) < 0) { + &dts, NULL, NULL) < 0) { dt = NPY_DATETIME_NAT; } } @@ -909,7 +909,7 @@ _strided_to_strided_string_to_datetime(char *dst, npy_intp dst_stride, else { if (parse_iso_8601_datetime(src, tmp - src, d->dst_meta.base, NPY_SAME_KIND_CASTING, - &dts, NULL, NULL, NULL) < 0) { + &dts, NULL, NULL) < 0) { dt = NPY_DATETIME_NAT; } } diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index 7c73822dd..2b3dc1817 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -647,7 +647,6 @@ datetimetype_repr(PyObject *self) npy_datetimestruct dts; PyObject *ret; char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; - int local; NPY_DATETIMEUNIT unit; if (!PyArray_IsScalar(self, Datetime)) { @@ -663,16 +662,8 @@ datetimetype_repr(PyObject *self) return NULL; } - local = (scal->obmeta.base > NPY_FR_D); - /* - * Because we're defaulting to local time, display hours with - * minutes precision, so that 30-minute timezone offsets can work. - */ unit = scal->obmeta.base; - if (unit == NPY_FR_h) { - unit = NPY_FR_m; - } - if (make_iso_8601_datetime(&dts, iso, sizeof(iso), local, + if (make_iso_8601_datetime(&dts, iso, sizeof(iso), 0, 0, unit, -1, NPY_SAFE_CASTING) < 0) { return NULL; } @@ -758,7 +749,6 @@ datetimetype_str(PyObject *self) PyDatetimeScalarObject *scal; npy_datetimestruct dts; char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; - int local; NPY_DATETIMEUNIT unit; if (!PyArray_IsScalar(self, Datetime)) { @@ -774,16 +764,8 @@ datetimetype_str(PyObject *self) return NULL; } - local = (scal->obmeta.base > NPY_FR_D); - /* - * Because we're defaulting to local time, display hours with - * minutes precision, so that 30-minute timezone offsets can work. - */ unit = scal->obmeta.base; - if (unit == NPY_FR_h) { - unit = NPY_FR_m; - } - if (make_iso_8601_datetime(&dts, iso, sizeof(iso), local, + if (make_iso_8601_datetime(&dts, iso, sizeof(iso), 0, 0, unit, -1, NPY_SAFE_CASTING) < 0) { return NULL; } diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 65b1d460a..3a4dcc8d3 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -9,7 +9,7 @@ import datetime from numpy.compat import asbytes from numpy.testing import ( TestCase, run_module_suite, assert_, assert_equal, assert_raises, - dec + assert_warns, dec ) # Use pytz to test out various time zones if available @@ -99,9 +99,8 @@ class TestDateTime(TestCase): # Can't cast timedelta same_kind from months/years to days assert_(not np.can_cast('m8[M]', 'm8[D]', casting='same_kind')) assert_(not np.can_cast('m8[Y]', 'm8[D]', casting='same_kind')) - # Can't cast datetime same_kind across the date/time boundary - assert_(not np.can_cast('M8[D]', 'M8[h]', casting='same_kind')) - assert_(not np.can_cast('M8[h]', 'M8[D]', casting='same_kind')) + # Can cast datetime same_kind across the date/time boundary + assert_(np.can_cast('M8[D]', 'M8[h]', casting='same_kind')) # Can cast timedelta same_kind across the date/time boundary assert_(np.can_cast('m8[D]', 'm8[h]', casting='same_kind')) assert_(np.can_cast('m8[h]', 'm8[D]', casting='same_kind')) @@ -140,8 +139,8 @@ class TestDateTime(TestCase): # Construct with different units assert_equal(np.datetime64('1950-03-12', 'D'), np.datetime64('1950-03-12')) - assert_equal(np.datetime64('1950-03-12T13Z', 's'), - np.datetime64('1950-03-12T13Z', 'm')) + assert_equal(np.datetime64('1950-03-12T13', 's'), + np.datetime64('1950-03-12T13', 'm')) # Default construction means NaT assert_equal(np.datetime64(), np.datetime64('NaT')) @@ -166,8 +165,8 @@ class TestDateTime(TestCase): # When constructing from a scalar or zero-dimensional array, # it either keeps the units or you can override them. - a = np.datetime64('2000-03-18T16Z', 'h') - b = np.array('2000-03-18T16Z', dtype='M8[h]') + a = np.datetime64('2000-03-18T16', 'h') + b = np.array('2000-03-18T16', dtype='M8[h]') assert_equal(a.dtype, np.dtype('M8[h]')) assert_equal(b.dtype, np.dtype('M8[h]')) @@ -190,22 +189,36 @@ class TestDateTime(TestCase): assert_equal(np.datetime64('2045-03-25', 'D'), np.datetime64(datetime.date(2045, 3, 25), 'D')) # Construction from datetime.datetime - assert_equal(np.datetime64('1980-01-25T14:36:22.5Z'), + assert_equal(np.datetime64('1980-01-25T14:36:22.5'), np.datetime64(datetime.datetime(1980, 1, 25, 14, 36, 22, 500000))) - # Construction with time units from a date raises - assert_raises(TypeError, np.datetime64, '1920-03-13', 'h') - assert_raises(TypeError, np.datetime64, '1920-03', 'm') - assert_raises(TypeError, np.datetime64, '1920', 's') - assert_raises(TypeError, np.datetime64, datetime.date(2045, 3, 25), 'ms') - # Construction with date units from a datetime raises - assert_raises(TypeError, np.datetime64, '1920-03-13T18Z', 'D') - assert_raises(TypeError, np.datetime64, '1920-03-13T18:33Z', 'W') - assert_raises(TypeError, np.datetime64, '1920-03-13T18:33:12Z', 'M') - assert_raises(TypeError, np.datetime64, '1920-03-13T18:33:12.5Z', 'Y') - assert_raises(TypeError, np.datetime64, - datetime.datetime(1920, 4, 14, 13, 20), 'D') + # Construction with time units from a date is okay + assert_equal(np.datetime64('1920-03-13', 'h'), + np.datetime64('1920-03-13T00')) + assert_equal(np.datetime64('1920-03', 'm'), + np.datetime64('1920-03-01T00:00')) + assert_equal(np.datetime64('1920', 's'), + np.datetime64('1920-01-01T00:00:00')) + assert_equal(np.datetime64(datetime.date(2045, 3, 25), 'ms'), + np.datetime64('2045-03-25T00:00:00.000')) + + # Construction with date units from a datetime is also okay + assert_equal(np.datetime64('1920-03-13T18', 'D'), + np.datetime64('1920-03-13')) + assert_equal(np.datetime64('1920-03-13T18:33:12', 'M'), + np.datetime64('1920-03')) + assert_equal(np.datetime64('1920-03-13T18:33:12.5', 'Y'), + np.datetime64('1920')) + + def test_datetime_scalar_construction_timezone(self): + # verify that supplying an explicit timezone works, but is deprecated + with assert_warns(DeprecationWarning): + assert_equal(np.datetime64('2000-01-01T00Z'), + np.datetime64('2000-01-01T00')) + with assert_warns(DeprecationWarning): + assert_equal(np.datetime64('2000-01-01T00-08'), + np.datetime64('2000-01-01T08')) def test_datetime_array_find_type(self): dt = np.datetime64('1970-01-01', 'M') @@ -324,57 +337,57 @@ class TestDateTime(TestCase): np.dtype('M8[D]')) assert_equal(np.datetime64('2010-03-12T17').dtype, np.dtype('M8[h]')) - assert_equal(np.datetime64('2010-03-12T17:15Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15').dtype, np.dtype('M8[m]')) - assert_equal(np.datetime64('2010-03-12T17:15:08Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08').dtype, np.dtype('M8[s]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.1Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.1').dtype, np.dtype('M8[ms]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.12Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.12').dtype, np.dtype('M8[ms]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.123Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.123').dtype, np.dtype('M8[ms]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.1234Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.1234').dtype, np.dtype('M8[us]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.12345Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.12345').dtype, np.dtype('M8[us]')) - assert_equal(np.datetime64('2010-03-12T17:15:08.123456Z').dtype, + assert_equal(np.datetime64('2010-03-12T17:15:08.123456').dtype, np.dtype('M8[us]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.1234567Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.1234567').dtype, np.dtype('M8[ns]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.12345678Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.12345678').dtype, np.dtype('M8[ns]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.123456789Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.123456789').dtype, np.dtype('M8[ns]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.1234567890Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.1234567890').dtype, np.dtype('M8[ps]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.12345678901Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.12345678901').dtype, np.dtype('M8[ps]')) - assert_equal(np.datetime64('1970-01-01T00:00:02.123456789012Z').dtype, + assert_equal(np.datetime64('1970-01-01T00:00:02.123456789012').dtype, np.dtype('M8[ps]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.1234567890123Z').dtype, + '1970-01-01T00:00:02.1234567890123').dtype, np.dtype('M8[fs]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.12345678901234Z').dtype, + '1970-01-01T00:00:02.12345678901234').dtype, np.dtype('M8[fs]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.123456789012345Z').dtype, + '1970-01-01T00:00:02.123456789012345').dtype, np.dtype('M8[fs]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.1234567890123456Z').dtype, + '1970-01-01T00:00:02.1234567890123456').dtype, np.dtype('M8[as]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.12345678901234567Z').dtype, + '1970-01-01T00:00:02.12345678901234567').dtype, np.dtype('M8[as]')) assert_equal(np.datetime64( - '1970-01-01T00:00:02.123456789012345678Z').dtype, + '1970-01-01T00:00:02.123456789012345678').dtype, np.dtype('M8[as]')) # Python date object @@ -390,18 +403,10 @@ class TestDateTime(TestCase): assert_equal(np.datetime64('today').dtype, np.dtype('M8[D]')) - assert_raises(TypeError, np.datetime64, 'today', 'h') - assert_raises(TypeError, np.datetime64, 'today', 's') - assert_raises(TypeError, np.datetime64, 'today', 'as') - # 'now' special value assert_equal(np.datetime64('now').dtype, np.dtype('M8[s]')) - assert_raises(TypeError, np.datetime64, 'now', 'Y') - assert_raises(TypeError, np.datetime64, 'now', 'M') - assert_raises(TypeError, np.datetime64, 'now', 'D') - def test_datetime_nat_casting(self): a = np.array('NaT', dtype='M8[D]') b = np.datetime64('NaT', '[D]') @@ -508,9 +513,9 @@ class TestDateTime(TestCase): #a = np.array(['now', datetime.datetime.now()], dtype='M8[s]') #assert_equal(a[0], a[1]) - # A datetime.date will raise if you try to give it time units - assert_raises(TypeError, np.array, datetime.date(1960, 3, 12), - dtype='M8[s]') + # we can give a datetime.date time units + assert_equal(np.array(datetime.date(1960, 3, 12), dtype='M8[s]'), + np.array(np.datetime64('1960-03-12T00:00:00'))) def test_datetime_string_conversion(self): a = ['2011-03-16', '1920-01-01', '2013-05-19'] @@ -547,7 +552,7 @@ class TestDateTime(TestCase): a = np.array(['2011-03-16', '1920-01-01', '2013-05-19'], dtype='M') assert_equal(str(a), "['2011-03-16' '1920-01-01' '2013-05-19']") - a = np.array(['2011-03-16T13:55Z', '1920-01-01T03:12Z'], dtype='M') + a = np.array(['2011-03-16T13:55', '1920-01-01T03:12'], dtype='M') assert_equal(np.array2string(a, separator=', ', formatter={'datetime': lambda x: "'%s'" % np.datetime_as_string(x, timezone='UTC')}), @@ -667,14 +672,14 @@ class TestDateTime(TestCase): for unit in ['M8[as]', 'M8[16fs]', 'M8[ps]', 'M8[us]', 'M8[300as]', 'M8[20us]']: b = a.copy().view(dtype=unit) - b[0] = '-0001-01-01T00Z' - b[1] = '-0001-12-31T00Z' - b[2] = '0000-01-01T00Z' - b[3] = '0001-01-01T00Z' - b[4] = '1969-12-31T23:59:59.999999Z' - b[5] = '1970-01-01T00Z' - b[6] = '9999-12-31T23:59:59.999999Z' - b[7] = '10000-01-01T00Z' + b[0] = '-0001-01-01T00' + b[1] = '-0001-12-31T00' + b[2] = '0000-01-01T00' + b[3] = '0001-01-01T00' + b[4] = '1969-12-31T23:59:59.999999' + b[5] = '1970-01-01T00' + b[6] = '9999-12-31T23:59:59.999999' + b[7] = '10000-01-01T00' b[8] = 'NaT' assert_equal(b.astype(object).astype(unit), b, @@ -685,13 +690,13 @@ class TestDateTime(TestCase): assert_equal(np.array('1945-03-01', dtype='M8[M]'), np.array('1945-03-31', dtype='M8[M]')) assert_equal(np.array('1969-11-01', dtype='M8[M]'), - np.array('1969-11-30T23:59:59.99999Z', dtype='M').astype('M8[M]')) + np.array('1969-11-30T23:59:59.99999', dtype='M').astype('M8[M]')) assert_equal(np.array('1969-12-01', dtype='M8[M]'), - np.array('1969-12-31T23:59:59.99999Z', dtype='M').astype('M8[M]')) + np.array('1969-12-31T23:59:59.99999', dtype='M').astype('M8[M]')) assert_equal(np.array('1970-01-01', dtype='M8[M]'), - np.array('1970-01-31T23:59:59.99999Z', dtype='M').astype('M8[M]')) + np.array('1970-01-31T23:59:59.99999', dtype='M').astype('M8[M]')) assert_equal(np.array('1980-02-01', dtype='M8[M]'), - np.array('1980-02-29T23:59:59.99999Z', dtype='M').astype('M8[M]')) + np.array('1980-02-29T23:59:59.99999', dtype='M').astype('M8[M]')) def test_different_unit_comparison(self): # Check some years with date units @@ -720,32 +725,32 @@ class TestDateTime(TestCase): dt1 = np.dtype('M8[%s]' % unit1) for unit2 in ['h', 'm', 's', 'ms', 'us']: dt2 = np.dtype('M8[%s]' % unit2) - assert_equal(np.array('1945-03-12T18Z', dtype=dt1), - np.array('1945-03-12T18Z', dtype=dt2)) - assert_equal(np.array('1970-03-12T18Z', dtype=dt1), - np.array('1970-03-12T18Z', dtype=dt2)) - assert_equal(np.array('9999-03-12T18Z', dtype=dt1), - np.array('9999-03-12T18Z', dtype=dt2)) - assert_equal(np.array('10000-01-01T00Z', dtype=dt1), - np.array('10000-01-01T00Z', dtype=dt2)) - assert_equal(np.datetime64('1945-03-12T18Z', unit1), - np.datetime64('1945-03-12T18Z', unit2)) - assert_equal(np.datetime64('1970-03-12T18Z', unit1), - np.datetime64('1970-03-12T18Z', unit2)) - assert_equal(np.datetime64('9999-03-12T18Z', unit1), - np.datetime64('9999-03-12T18Z', unit2)) - assert_equal(np.datetime64('10000-01-01T00Z', unit1), - np.datetime64('10000-01-01T00Z', unit2)) + assert_equal(np.array('1945-03-12T18', dtype=dt1), + np.array('1945-03-12T18', dtype=dt2)) + assert_equal(np.array('1970-03-12T18', dtype=dt1), + np.array('1970-03-12T18', dtype=dt2)) + assert_equal(np.array('9999-03-12T18', dtype=dt1), + np.array('9999-03-12T18', dtype=dt2)) + assert_equal(np.array('10000-01-01T00', dtype=dt1), + np.array('10000-01-01T00', dtype=dt2)) + assert_equal(np.datetime64('1945-03-12T18', unit1), + np.datetime64('1945-03-12T18', unit2)) + assert_equal(np.datetime64('1970-03-12T18', unit1), + np.datetime64('1970-03-12T18', unit2)) + assert_equal(np.datetime64('9999-03-12T18', unit1), + np.datetime64('9999-03-12T18', unit2)) + assert_equal(np.datetime64('10000-01-01T00', unit1), + np.datetime64('10000-01-01T00', unit2)) # Check some days with units that won't overflow for unit1 in ['D', '12h', 'h', 'm', 's', '4s', 'ms', 'us']: dt1 = np.dtype('M8[%s]' % unit1) for unit2 in ['D', 'h', 'm', 's', 'ms', 'us']: dt2 = np.dtype('M8[%s]' % unit2) assert_(np.equal(np.array('1932-02-17', dtype='M').astype(dt1), - np.array('1932-02-17T00:00:00Z', dtype='M').astype(dt2), + np.array('1932-02-17T00:00:00', dtype='M').astype(dt2), casting='unsafe')) assert_(np.equal(np.array('10000-04-27', dtype='M').astype(dt1), - np.array('10000-04-27T00:00:00Z', dtype='M').astype(dt2), + np.array('10000-04-27T00:00:00', dtype='M').astype(dt2), casting='unsafe')) # Shouldn't be able to compare datetime and timedelta @@ -807,7 +812,7 @@ class TestDateTime(TestCase): # One-dimensional arrays (np.array(['2012-12-21'], dtype='M8[D]'), np.array(['2012-12-24'], dtype='M8[D]'), - np.array(['2012-12-21T11Z'], dtype='M8[h]'), + np.array(['2012-12-21T11'], dtype='M8[h]'), np.array(['NaT'], dtype='M8[D]'), np.array([3], dtype='m8[D]'), np.array([11], dtype='m8[h]'), @@ -815,7 +820,7 @@ class TestDateTime(TestCase): # NumPy scalars (np.datetime64('2012-12-21', '[D]'), np.datetime64('2012-12-24', '[D]'), - np.datetime64('2012-12-21T11Z', '[h]'), + np.datetime64('2012-12-21T11', '[h]'), np.datetime64('NaT', '[D]'), np.timedelta64(3, '[D]'), np.timedelta64(11, '[h]'), @@ -878,8 +883,8 @@ class TestDateTime(TestCase): (np.array(['2012-12-21'], dtype='M8[D]'), np.array(['2012-12-24'], dtype='M8[D]'), np.array(['1940-12-24'], dtype='M8[D]'), - np.array(['1940-12-24T00Z'], dtype='M8[h]'), - np.array(['1940-12-23T13Z'], dtype='M8[h]'), + np.array(['1940-12-24T00'], dtype='M8[h]'), + np.array(['1940-12-23T13'], dtype='M8[h]'), np.array(['NaT'], dtype='M8[D]'), np.array([3], dtype='m8[D]'), np.array([11], dtype='m8[h]'), @@ -888,8 +893,8 @@ class TestDateTime(TestCase): (np.datetime64('2012-12-21', '[D]'), np.datetime64('2012-12-24', '[D]'), np.datetime64('1940-12-24', '[D]'), - np.datetime64('1940-12-24T00Z', '[h]'), - np.datetime64('1940-12-23T13Z', '[h]'), + np.datetime64('1940-12-24T00', '[h]'), + np.datetime64('1940-12-23T13', '[h]'), np.datetime64('NaT', '[D]'), np.timedelta64(3, '[D]'), np.timedelta64(11, '[h]'), @@ -1071,12 +1076,12 @@ class TestDateTime(TestCase): def test_datetime_compare(self): # Test all the comparison operators - a = np.datetime64('2000-03-12T18:00:00.000000-0600') - b = np.array(['2000-03-12T18:00:00.000000-0600', - '2000-03-12T17:59:59.999999-0600', - '2000-03-12T18:00:00.000001-0600', - '1970-01-11T12:00:00.909090-0600', - '2016-01-11T12:00:00.909090-0600'], + a = np.datetime64('2000-03-12T18:00:00.000000') + b = np.array(['2000-03-12T18:00:00.000000', + '2000-03-12T17:59:59.999999', + '2000-03-12T18:00:00.000001', + '1970-01-11T12:00:00.909090', + '2016-01-11T12:00:00.909090'], dtype='datetime64[us]') assert_equal(np.equal(a, b), [1, 0, 0, 0, 0]) assert_equal(np.not_equal(a, b), [0, 1, 1, 1, 1]) @@ -1108,8 +1113,8 @@ class TestDateTime(TestCase): def test_datetime_minmax(self): # The metadata of the result should become the GCD # of the operand metadata - a = np.array('1999-03-12T13Z', dtype='M8[2m]') - b = np.array('1999-03-12T12Z', dtype='M8[s]') + a = np.array('1999-03-12T13', dtype='M8[2m]') + b = np.array('1999-03-12T12', dtype='M8[s]') assert_equal(np.minimum(a, b), b) assert_equal(np.minimum(a, b).dtype, np.dtype('M8[s]')) assert_equal(np.fmin(a, b), b) @@ -1123,7 +1128,7 @@ class TestDateTime(TestCase): assert_equal(np.minimum(a.view('i8'), b.view('i8')), a.view('i8')) # Interaction with NaT - a = np.array('1999-03-12T13Z', dtype='M8[2m]') + a = np.array('1999-03-12T13', dtype='M8[2m]') dtnat = np.array('NaT', dtype='M8[h]') assert_equal(np.minimum(a, dtnat), a) assert_equal(np.minimum(dtnat, a), a) @@ -1150,7 +1155,7 @@ class TestDateTime(TestCase): # TODO: Allowing unsafe casting by # default in ufuncs strikes again... :( a = np.array(3, dtype='m8[h]') - b = np.array('1999-03-12T12Z', dtype='M8[s]') + b = np.array('1999-03-12T12', dtype='M8[s]') #assert_raises(TypeError, np.minimum, a, b) #assert_raises(TypeError, np.maximum, a, b) #assert_raises(TypeError, np.fmin, a, b) @@ -1212,17 +1217,26 @@ class TestDateTime(TestCase): assert_equal(np.array(['-1980-02-29T01:02:03'], np.dtype('M8[s]')), np.array(['-1980-02-29 01:02:03'], np.dtype('M8[s]'))) # UTC specifier - assert_equal(np.array(['-1980-02-29T01:02:03Z'], np.dtype('M8[s]')), - np.array(['-1980-02-29 01:02:03Z'], np.dtype('M8[s]'))) + with assert_warns(DeprecationWarning): + assert_equal( + np.array(['-1980-02-29T01:02:03'], np.dtype('M8[s]')), + np.array(['-1980-02-29 01:02:03Z'], np.dtype('M8[s]'))) # Time zone offset - assert_equal(np.array(['1980-02-29T02:02:03Z'], np.dtype('M8[s]')), - np.array(['1980-02-29 00:32:03-0130'], np.dtype('M8[s]'))) - assert_equal(np.array(['1980-02-28T22:32:03Z'], np.dtype('M8[s]')), - np.array(['1980-02-29 00:02:03+01:30'], np.dtype('M8[s]'))) - assert_equal(np.array(['1980-02-29T02:32:03.506Z'], np.dtype('M8[s]')), - np.array(['1980-02-29 00:32:03.506-02'], np.dtype('M8[s]'))) - assert_equal(np.datetime64('1977-03-02T12:30-0230'), - np.datetime64('1977-03-02T15:00Z')) + with assert_warns(DeprecationWarning): + assert_equal( + np.array(['1980-02-29T02:02:03'], np.dtype('M8[s]')), + np.array(['1980-02-29 00:32:03-0130'], np.dtype('M8[s]'))) + with assert_warns(DeprecationWarning): + assert_equal( + np.array(['1980-02-28T22:32:03'], np.dtype('M8[s]')), + np.array(['1980-02-29 00:02:03+01:30'], np.dtype('M8[s]'))) + with assert_warns(DeprecationWarning): + assert_equal( + np.array(['1980-02-29T02:32:03.506'], np.dtype('M8[s]')), + np.array(['1980-02-29 00:32:03.506-02'], np.dtype('M8[s]'))) + with assert_warns(DeprecationWarning): + assert_equal(np.datetime64('1977-03-02T12:30-0230'), + np.datetime64('1977-03-02T15:00')) def test_string_parser_error_check(self): # Arbitrary bad string @@ -1291,19 +1305,24 @@ class TestDateTime(TestCase): assert_raises(ValueError, np.array, ['1980-02-03 01:01:60'], np.dtype('M8[us]')) # Timezone offset must within a reasonable range - assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+0661'], - np.dtype('M8[us]')) - assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+2500'], - np.dtype('M8[us]')) - assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-0070'], - np.dtype('M8[us]')) - assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-3000'], - np.dtype('M8[us]')) - assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-25:00'], - np.dtype('M8[us]')) + with assert_warns(DeprecationWarning): + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+0661'], + np.dtype('M8[us]')) + with assert_warns(DeprecationWarning): + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+2500'], + np.dtype('M8[us]')) + with assert_warns(DeprecationWarning): + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-0070'], + np.dtype('M8[us]')) + with assert_warns(DeprecationWarning): + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-3000'], + np.dtype('M8[us]')) + with assert_warns(DeprecationWarning): + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-25:00'], + np.dtype('M8[us]')) def test_creation_overflow(self): - date = '1980-03-23 20:00:00Z' + date = '1980-03-23 20:00:00' timesteps = np.array([date], dtype='datetime64[s]')[0].astype(np.int64) for unit in ['ms', 'us', 'ns']: timesteps *= 1000 @@ -1317,7 +1336,7 @@ class TestDateTime(TestCase): def test_datetime_as_string(self): # Check all the units with default string conversion date = '1959-10-13' - datetime = '1959-10-13T12:34:56.789012345678901234Z' + datetime = '1959-10-13T12:34:56.789012345678901234' assert_equal(np.datetime_as_string(np.datetime64(date, 'Y')), '1959') @@ -1326,45 +1345,45 @@ class TestDateTime(TestCase): assert_equal(np.datetime_as_string(np.datetime64(date, 'D')), '1959-10-13') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'h')), - '1959-10-13T12Z') + '1959-10-13T12') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'm')), - '1959-10-13T12:34Z') + '1959-10-13T12:34') assert_equal(np.datetime_as_string(np.datetime64(datetime, 's')), - '1959-10-13T12:34:56Z') + '1959-10-13T12:34:56') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'ms')), - '1959-10-13T12:34:56.789Z') + '1959-10-13T12:34:56.789') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'us')), - '1959-10-13T12:34:56.789012Z') + '1959-10-13T12:34:56.789012') - datetime = '1969-12-31T23:34:56.789012345678901234Z' + datetime = '1969-12-31T23:34:56.789012345678901234' assert_equal(np.datetime_as_string(np.datetime64(datetime, 'ns')), - '1969-12-31T23:34:56.789012345Z') + '1969-12-31T23:34:56.789012345') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'ps')), - '1969-12-31T23:34:56.789012345678Z') + '1969-12-31T23:34:56.789012345678') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'fs')), - '1969-12-31T23:34:56.789012345678901Z') + '1969-12-31T23:34:56.789012345678901') - datetime = '1969-12-31T23:59:57.789012345678901234Z' + datetime = '1969-12-31T23:59:57.789012345678901234' assert_equal(np.datetime_as_string(np.datetime64(datetime, 'as')), datetime) - datetime = '1970-01-01T00:34:56.789012345678901234Z' + datetime = '1970-01-01T00:34:56.789012345678901234' assert_equal(np.datetime_as_string(np.datetime64(datetime, 'ns')), - '1970-01-01T00:34:56.789012345Z') + '1970-01-01T00:34:56.789012345') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'ps')), - '1970-01-01T00:34:56.789012345678Z') + '1970-01-01T00:34:56.789012345678') assert_equal(np.datetime_as_string(np.datetime64(datetime, 'fs')), - '1970-01-01T00:34:56.789012345678901Z') + '1970-01-01T00:34:56.789012345678901') - datetime = '1970-01-01T00:00:05.789012345678901234Z' + datetime = '1970-01-01T00:00:05.789012345678901234' assert_equal(np.datetime_as_string(np.datetime64(datetime, 'as')), datetime) # String conversion with the unit= parameter - a = np.datetime64('2032-07-18T12:23:34.123456Z', 'us') + a = np.datetime64('2032-07-18T12:23:34.123456', 'us') assert_equal(np.datetime_as_string(a, unit='Y', casting='unsafe'), '2032') assert_equal(np.datetime_as_string(a, unit='M', casting='unsafe'), @@ -1373,62 +1392,66 @@ class TestDateTime(TestCase): '2032-07-18') assert_equal(np.datetime_as_string(a, unit='D', casting='unsafe'), '2032-07-18') - assert_equal(np.datetime_as_string(a, unit='h'), '2032-07-18T12Z') + assert_equal(np.datetime_as_string(a, unit='h'), '2032-07-18T12') assert_equal(np.datetime_as_string(a, unit='m'), - '2032-07-18T12:23Z') + '2032-07-18T12:23') assert_equal(np.datetime_as_string(a, unit='s'), - '2032-07-18T12:23:34Z') + '2032-07-18T12:23:34') assert_equal(np.datetime_as_string(a, unit='ms'), - '2032-07-18T12:23:34.123Z') + '2032-07-18T12:23:34.123') assert_equal(np.datetime_as_string(a, unit='us'), - '2032-07-18T12:23:34.123456Z') + '2032-07-18T12:23:34.123456') assert_equal(np.datetime_as_string(a, unit='ns'), - '2032-07-18T12:23:34.123456000Z') + '2032-07-18T12:23:34.123456000') assert_equal(np.datetime_as_string(a, unit='ps'), - '2032-07-18T12:23:34.123456000000Z') + '2032-07-18T12:23:34.123456000000') assert_equal(np.datetime_as_string(a, unit='fs'), - '2032-07-18T12:23:34.123456000000000Z') + '2032-07-18T12:23:34.123456000000000') assert_equal(np.datetime_as_string(a, unit='as'), - '2032-07-18T12:23:34.123456000000000000Z') + '2032-07-18T12:23:34.123456000000000000') # 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') + np.datetime64('2032-07-18T12:23:34.123456', 'us'), unit='auto'), + '2032-07-18T12:23:34.123456') assert_equal(np.datetime_as_string( - np.datetime64('2032-07-18T12:23:34.12Z', 'us'), unit='auto'), - '2032-07-18T12:23:34.120Z') + np.datetime64('2032-07-18T12:23:34.12', 'us'), unit='auto'), + '2032-07-18T12:23:34.120') assert_equal(np.datetime_as_string( - np.datetime64('2032-07-18T12:23:34Z', 'us'), unit='auto'), - '2032-07-18T12:23:34Z') + np.datetime64('2032-07-18T12:23:34', 'us'), unit='auto'), + '2032-07-18T12:23:34') assert_equal(np.datetime_as_string( - np.datetime64('2032-07-18T12:23:00Z', 'us'), unit='auto'), - '2032-07-18T12:23Z') + np.datetime64('2032-07-18T12:23:00', 'us'), unit='auto'), + '2032-07-18T12:23') # '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') + np.datetime64('2032-07-18T12:00:00', 'us'), unit='auto'), + '2032-07-18T12:00') assert_equal(np.datetime_as_string( - np.datetime64('2032-07-18T00:00:00Z', 'us'), unit='auto'), + np.datetime64('2032-07-18T00:00:00', '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'), + np.datetime64('2032-07-01T00:00:00', 'us'), unit='auto'), '2032-07-01') assert_equal(np.datetime_as_string( - np.datetime64('2032-01-01T00:00:00Z', 'us'), unit='auto'), + np.datetime64('2032-01-01T00:00:00', 'us'), unit='auto'), '2032-01-01') @dec.skipif(not _has_pytz, "The pytz module is not available.") def test_datetime_as_string_timezone(self): # timezone='local' vs 'UTC' - a = np.datetime64('2010-03-15T06:30Z', 'm') + a = np.datetime64('2010-03-15T06:30', 'm') + assert_equal(np.datetime_as_string(a), + '2010-03-15T06:30') + assert_equal(np.datetime_as_string(a, timezone='naive'), + '2010-03-15T06:30') assert_equal(np.datetime_as_string(a, timezone='UTC'), '2010-03-15T06:30Z') assert_(np.datetime_as_string(a, timezone='local') != - '2010-03-15T06:30Z') + '2010-03-15T06:30') - b = np.datetime64('2010-02-15T06:30Z', 'm') + b = np.datetime64('2010-02-15T06:30', 'm') assert_equal(np.datetime_as_string(a, timezone=tz('US/Central')), '2010-03-15T01:30-0500') @@ -1493,7 +1516,7 @@ class TestDateTime(TestCase): assert_raises(TypeError, np.arange, np.datetime64('2011-03-01', 'D'), np.timedelta64(5, 'M')) assert_raises(TypeError, np.arange, - np.datetime64('2012-02-03T14Z', 's'), + np.datetime64('2012-02-03T14', 's'), np.timedelta64(5, 'Y')) def test_datetime_arange_no_dtype(self): @@ -1845,21 +1868,23 @@ class TestDateTime(TestCase): def test_datetime_y2038(self): # Test parsing on either side of the Y2038 boundary - a = np.datetime64('2038-01-19T03:14:07Z') + a = np.datetime64('2038-01-19T03:14:07') assert_equal(a.view(np.int64), 2**31 - 1) - a = np.datetime64('2038-01-19T03:14:08Z') + a = np.datetime64('2038-01-19T03:14:08') assert_equal(a.view(np.int64), 2**31) # Test parsing on either side of the Y2038 boundary with # a manually specified timezone offset - a = np.datetime64('2038-01-19T04:14:07+0100') - assert_equal(a.view(np.int64), 2**31 - 1) - a = np.datetime64('2038-01-19T04:14:08+0100') - assert_equal(a.view(np.int64), 2**31) - - # Test parsing a date after Y2038 in the local timezone + with assert_warns(DeprecationWarning): + a = np.datetime64('2038-01-19T04:14:07+0100') + assert_equal(a.view(np.int64), 2**31 - 1) + with assert_warns(DeprecationWarning): + a = np.datetime64('2038-01-19T04:14:08+0100') + assert_equal(a.view(np.int64), 2**31) + + # Test parsing a date after Y2038 a = np.datetime64('2038-01-20T13:21:14') - assert_equal(str(a)[:-5], '2038-01-20T13:21:14') + assert_equal(str(a), '2038-01-20T13:21:14') class TestDateTimeData(TestCase): diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 65ddc1e77..f0998901d 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -5,6 +5,7 @@ to document how deprecations should eventually be turned into errors. """ from __future__ import division, absolute_import, print_function +import datetime import sys import operator import warnings @@ -12,7 +13,13 @@ import warnings import numpy as np from numpy.testing import ( run_module_suite, assert_raises, assert_warns, assert_no_warnings, - assert_array_equal, assert_) + assert_array_equal, assert_, dec) + +try: + import pytz + _has_pytz = True +except ImportError: + _has_pytz = False class _DeprecationTestCase(object): @@ -386,6 +393,26 @@ class TestFullDefaultDtype(object): assert_no_warnings(np.full, 1, 1, float) +class TestDatetime64Timezone(_DeprecationTestCase): + """Parsing of datetime64 with timezones deprecated in 1.11.0, because + datetime64 is now timezone naive rather than UTC only. + + It will be quite a while before we can remove this, because, at the very + least, a lot of existing code uses the 'Z' modifier to avoid conversion + from local time to UTC, even if otherwise it handles time in a timezone + naive fashion. + """ + def test_string(self): + self.assert_deprecated(np.datetime64, args=('2000-01-01T00+01',)) + self.assert_deprecated(np.datetime64, args=('2000-01-01T00Z',)) + + @dec.skipif(not _has_pytz, "The pytz module is not available.") + def test_datetime(self): + tz = pytz.timezone('US/Eastern') + dt = datetime.datetime(2000, 1, 1, 0, 0, tzinfo=tz) + self.assert_deprecated(np.datetime64, args=(dt,)) + + class TestNonCContiguousViewDeprecation(_DeprecationTestCase): """View of non-C-contiguous arrays deprecated in 1.11.0. |