summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h8
-rw-r--r--numpy/core/src/multiarray/_datetime.h17
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src62
-rw-r--r--numpy/core/src/multiarray/datetime.c503
-rw-r--r--numpy/core/tests/test_datetime.py100
5 files changed, 657 insertions, 33 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 9dd060aac..75a9e9b6e 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -685,13 +685,13 @@ typedef struct {
} PyArray_DatetimeMetaData;
typedef struct {
- npy_longlong year;
- int month, day, hour, min, sec, us, ps, as;
+ npy_int64 year;
+ npy_int32 month, day, hour, min, sec, us, ps, as;
} npy_datetimestruct;
typedef struct {
- npy_longlong day;
- int sec, us, ps, as;
+ npy_int64 day;
+ npy_int32 sec, us, ps, as;
} npy_timedeltastruct;
#if PY_VERSION_HEX >= 0x03000000
diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h
index 77cf10e48..a577bfe3d 100644
--- a/numpy/core/src/multiarray/_datetime.h
+++ b/numpy/core/src/multiarray/_datetime.h
@@ -94,4 +94,21 @@ convert_datetime_metadata_tuple_to_metacobj(PyObject *tuple);
NPY_NO_EXPORT PyObject *
append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret);
+/*
+ * Parses (almost) standard ISO 8601 date strings. The differences are:
+ *
+ * + The date "20100312" is parsed as the year 20100312, not as
+ * equivalent to "2010-03-12". The '-' in the dates are not optional.
+ * + Only seconds may have a decimal point, with up to 18 digits after it
+ * (maximum attoseconds precision).
+ * + Either a 'T' as in ISO 8601 or a ' ' may be used to separate
+ * the date and the time. Both are treated equivalently.
+ *
+ * 'str' must be a NULL-terminated string, and 'len' must be its length.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT int
+parse_iso_8601_date(char *str, int len, npy_datetimestruct *out);
+
#endif
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index 6ca982345..5a3748b8f 100644
--- a/numpy/core/src/multiarray/arraytypes.c.src
+++ b/numpy/core/src/multiarray/arraytypes.c.src
@@ -1129,32 +1129,50 @@ TIMEDELTA_getitem(char *ip, PyArrayObject *ap) {
static int
DATETIME_setitem(PyObject *op, char *ov, PyArrayObject *ap) {
/* ensure alignment */
- datetime temp;
+ npy_datetime temp;
if (PyArray_IsScalar(op, Datetime)) {
- /* This needs to convert based on type */
+ /* TODO: This needs to convert based on type */
temp = ((PyDatetimeScalarObject *)op)->obval;
}
-#if defined(NPY_PY3K)
- else if (PyUString_Check(op)) {
-#else
- else if (PyUString_Check(op) || PyUnicode_Check(op)) {
-#endif
- /* FIXME: Converts to DateTime first and therefore does not handle extended notation */
- /* import _mx_datetime_parser
- * res = _mx_datetime_parser(name)
- * Convert from datetime to Int
- */
- PyObject *res, *module;
-
- module = PyImport_ImportModule("numpy.core._mx_datetime_parser");
- if (module == NULL) { return -1; }
- res = PyObject_CallMethod(module, "datetime_from_string", "O", op);
- Py_DECREF(module);
- if (res == NULL) { return -1; }
- temp = PyDateTime_AsInt64(res, ap->descr);
- Py_DECREF(res);
- if (PyErr_Occurred()) return -1;
+ else if (PyBytes_Check(op) || PyUnicode_Check(op)) {
+ PyObject *bytes = NULL;
+ char *str = NULL;
+ int len = 0;
+ npy_datetimestruct dt;
+ PyArray_DatetimeMetaData *meta = NULL;
+
+ /* Convert to an ASCII string for the date parser */
+ if (PyUnicode_Check(op)) {
+ bytes = PyUnicode_AsASCIIString(op);
+ if (bytes == NULL) {
+ return -1;
+ }
+ }
+ else {
+ bytes = op;
+ Py_INCREF(bytes);
+ }
+ if (PyBytes_AsStringAndSize(bytes, &str, &len) == -1) {
+ Py_DECREF(bytes);
+ return -1;
+ }
+
+ /* Parse the ISO date */
+ if (parse_iso_8601_date(str, len, &dt) < 0) {
+ Py_DECREF(bytes);
+ return -1;
+ }
+
+ /* Get the datetime units metadata */
+ meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap));
+ if (meta == NULL) {
+ Py_DECREF(bytes);
+ return -1;
+ }
+
+ /* TODO: convert the datetimestruct to the npy_datetime */
+ return 0;
}
else if (PyInt_Check(op)) {
temp = PyInt_AS_LONG(op);
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index 2a29015cd..48405d55d 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -81,7 +81,7 @@ static int days_in_month[2][12] = {
/* Return 1/0 iff year points to a leap year in calendar. */
static int
-is_leapyear(long year)
+is_leapyear(npy_int64 year)
{
return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
}
@@ -508,8 +508,6 @@ PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d)
return ret;
}
-
-
/*NUMPY_API
* Fill the datetime struct from the value and resolution unit.
*/
@@ -526,9 +524,10 @@ PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr,
hmsstruct hms;
/*
- * Note that what looks like val / N and val % N for positive numbers maps to
- * [val - (N-1)] / N and [N-1 + (val+1) % N] for negative numbers (with the 2nd
- * value, the remainder, being positive in both cases).
+ * Note that what looks like val / N and val % N for positive numbers
+ * maps to [val - (N-1)] / N and [N-1 + (val+1) % N] for negative
+ * numbers (with the 2nd value, the remainder, being positive in
+ * both cases).
*/
if (fr == NPY_FR_Y) {
year = 1970 + val;
@@ -554,7 +553,7 @@ PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr,
/* Number of business days since Thursday, 1-1-70 */
npy_longlong absdays;
/*
- * A buisness day is M T W Th F (i.e. all but Sat and Sun.)
+ * A business day is M T W Th F (i.e. all but Sat and Sun.)
* Convert the business day to the number of actual days.
*
* Must convert [0,1,2,3,4,5,6,7,...] to
@@ -1667,4 +1666,494 @@ append_metastr_to_datetime_typestr(PyArray_Descr *self, PyObject *ret)
return ret;
}
+/*
+ * Adjusts a datetimestruct based on a time zone offset. Assumes
+ * the current values are valid.
+ */
+NPY_NO_EXPORT void
+datetimestruct_timezone_offset(npy_datetimestruct *dts, int minutes)
+{
+ int isleap;
+
+ /* MINUTES */
+ dts->min += minutes;
+ while (dts->min < 0) {
+ dts->min += 60;
+ dts->hour--;
+ }
+ while (dts->min >= 60) {
+ dts->min -= 60;
+ dts->hour++;
+ }
+
+ /* HOURS */
+ while (dts->hour < 0) {
+ dts->hour += 24;
+ dts->day--;
+ }
+ while (dts->hour >= 24) {
+ dts->hour -= 24;
+ dts->day++;
+ }
+
+ /* DAYS */
+ if (dts->day < 0) {
+ dts->month--;
+ if (dts->month < 0) {
+ dts->year--;
+ dts->month = 11;
+ }
+ isleap = is_leapyear(dts->year);
+ dts->day += days_in_month[isleap][dts->month];
+ }
+ else if (dts->day > 28) {
+ isleap = is_leapyear(dts->year);
+ if (dts->day >= days_in_month[isleap][dts->month]) {
+ dts->day -= days_in_month[isleap][dts->month];
+ dts->month++;
+ if (dts->month >= 12) {
+ dts->year++;
+ dts->month = 0;
+ }
+ }
+ }
+}
+
+/*
+ * Gets the offset (in minutes) between local time and UTC, as:
+ *
+ * UTC = local + offset
+ *
+ * Windows appears to have reasonable access to the time zone, but
+ * gcc requires you to either understand all the nuances, or call
+ * the function localtime_r, getting the time in a nonstandard field.
+ *
+ * Returns -1 on failure.
+ */
+NPY_NO_EXPORT int
+get_timezone_minutes_offset()
+{
+#if defined(_WIN32)
+ TIME_ZONE_INFORMATION tzinfo;
+
+ if (GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_INVALID) {
+ PyErr_SetString(PyExc_WindowsError, "Failed to get local time zone");
+ return -1;
+ }
+ return tzinfo.Bias / 60;
+#elif defined(__GNUC__)
+ time_t rawtime;
+ struct tm tmfortz;
+
+ time(&rawtime);
+ /* Using localtime_r instead of localtime to avoid threading issues */
+ if (localtime_r(&rawtime, &tmfortz) == NULL) {
+ PyErr_SetString(PyExc_OSError, "Failed to use localtime_r to "
+ "get local time zone");
+ return -1;
+ }
+ return -tmfortz.tm_gmtoff / 60;
+#else
+#error TODO: Need to write platform-specific time zone code here! Please contact numpy-discussion@scipy.org with full information about your platform.
+#endif
+}
+
+/*
+ * Parses (almost) standard ISO 8601 date strings. The differences are:
+ *
+ * + The date "20100312" is parsed as the year 20100312, not as
+ * equivalent to "2010-03-12". The '-' in the dates are not optional.
+ * + Only seconds may have a decimal point, with up to 18 digits after it
+ * (maximum attoseconds precision).
+ * + Either a 'T' as in ISO 8601 or a ' ' may be used to separate
+ * the date and the time. Both are treated equivalently.
+ * + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats.
+ * + Doesn't handle leap seconds (seconds value gets 60 in these cases).
+ * + Doesn't handle 24:00:00 as synonym for midnight (00:00:00)
+ *
+ * 'str' must be a NULL-terminated string, and 'len' must be its length.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT int
+parse_iso_8601_date(char *str, int len, npy_datetimestruct *out)
+{
+ int year_leap = 0;
+ int i;
+ char *substr, sublen;
+
+ /* Initialize the output to all zeros */
+ memset(out, 0, sizeof(npy_datetimestruct));
+
+ /* The empty string and case-variants of "NaT" parse to not-a-time */
+ if (len <= 0 || (len == 3 &&
+ tolower(str[0]) == 'n' &&
+ tolower(str[1]) == 'a' &&
+ tolower(str[2]) == 't')) {
+ out->year = NPY_MIN_INT64;
+ return 0;
+ }
+
+ /*
+ * The string "today" resolves to midnight of today's local date in UTC.
+ * This is perhaps a little weird, but done so that further truncation
+ * to a 'datetime64[D]' type produces the date you expect, rather than
+ * switching to an adjacent day depending on the current time and your
+ * timezone.
+ */
+ if (len == 5 && tolower(str[0]) == 't' &&
+ tolower(str[1]) == 'o' &&
+ tolower(str[2]) == 'd' &&
+ tolower(str[3]) == 'a' &&
+ tolower(str[4]) == 'y') {
+ time_t rawtime = 0;
+ int minutes_offset;
+ time(&rawtime);
+ /* Convert the seconds from 1970 into the npy_datetimestruct */
+ PyArray_DatetimeToDatetimeStruct(rawtime, NPY_FR_s, out);
+ /* Adjust it into local time */
+ minutes_offset = get_timezone_minutes_offset();
+ if (minutes_offset == -1) {
+ return -1;
+ }
+ datetimestruct_timezone_offset(out, -minutes_offset);
+ out->hour = 0;
+ out->min = 0;
+ out->sec = 0;
+ out->us = 0;
+ out->ps = 0;
+ out->as = 0;
+ return 0;
+ }
+
+ /* The string "now" resolves to the current time */
+ if (len == 3 && tolower(str[0]) == 'n' &&
+ tolower(str[1]) == 'o' &&
+ tolower(str[1]) == 'w') {
+ time_t rawtime = 0;
+ time(&rawtime);
+ PyArray_DatetimeToDatetimeStruct(rawtime, NPY_FR_s, out);
+ return 0;
+ }
+
+ substr = str;
+ sublen = len;
+
+ /* Skip leading whitespace */
+ while (sublen > 0 && isspace(*substr)) {
+ ++substr;
+ --sublen;
+ }
+
+ /* Leading '-' sign for negative year */
+ if (*substr == '-') {
+ ++substr;
+ --sublen;
+ }
+
+ if (sublen == 0) {
+ goto parse_error;
+ }
+
+ /* PARSE THE YEAR (digits until the '-' character) */
+ out->year = 0;
+ while (sublen > 0 && isdigit(*substr)) {
+ out->year = 10 * out->year + (*substr - '0');
+ ++substr;
+ --sublen;
+ }
+
+ /* Negate the year if necessary */
+ if (str[0] == '-') {
+ out->year = -out->year;
+ }
+ /* Check whether it's a leap-year */
+ year_leap = is_leapyear(out->year);
+
+ /* Next character must be a '-' or the end of the string */
+ if (sublen == 0) {
+ goto finish;
+ }
+ else if (*substr == '-') {
+ ++substr;
+ --sublen;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Can't have a trailing '-' */
+ if (sublen == 0) {
+ goto parse_error;
+ }
+
+ /* PARSE THE MONTH (2 digits) */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ out->month = 10 * (substr[0] - '0') + (substr[1] - '0');
+ /* Store the month as range [0,11] */
+ out->month--;
+
+ if (out->month < 0 || out->month > 11) {
+ PyErr_Format(PyExc_ValueError,
+ "Month out of range in datetime string \"%s\"", str);
+ goto error;
+ }
+ substr += 2;
+ sublen -= 2;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Next character must be a '-' or the end of the string */
+ if (sublen == 0) {
+ goto finish;
+ }
+ else if (*substr == '-') {
+ ++substr;
+ --sublen;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Can't have a trailing '-' */
+ if (sublen == 0) {
+ goto parse_error;
+ }
+
+ /* PARSE THE DAY (2 digits) */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ out->day = 10 * (substr[0] - '0') + (substr[1] - '0');
+ /* Store the day as range [0,len-1] */
+ out->day--;
+
+ if (out->day < 0 || out->day >= days_in_month[year_leap][out->month]) {
+ PyErr_Format(PyExc_ValueError,
+ "Day out of range in datetime string \"%s\"", str);
+ goto error;
+ }
+ substr += 2;
+ sublen -= 2;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Next character must be a 'T', ' ', or end of string */
+ if (sublen == 0) {
+ goto finish;
+ }
+ else if (*substr != 'T' && *substr != ' ') {
+ goto parse_error;
+ }
+ else {
+ ++substr;
+ --sublen;
+ }
+
+ /* PARSE THE HOURS (2 digits) */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ out->hour = 10 * (substr[0] - '0') + (substr[1] - '0');
+
+ if (out->hour < 0 || out->hour >= 24) {
+ PyErr_Format(PyExc_ValueError,
+ "Hours out of range in datetime string \"%s\"", str);
+ goto error;
+ }
+ substr += 2;
+ sublen -= 2;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Next character must be a ':' or the end of the string */
+ if (sublen > 0 && *substr == ':') {
+ ++substr;
+ --sublen;
+ }
+ else {
+ goto parse_timezone;
+ }
+
+ /* Can't have a trailing ':' */
+ if (sublen == 0) {
+ goto parse_error;
+ }
+
+ /* PARSE THE MINUTES (2 digits) */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ out->min = 10 * (substr[0] - '0') + (substr[1] - '0');
+
+ if (out->hour < 0 || out->min >= 60) {
+ PyErr_Format(PyExc_ValueError,
+ "Minutes out of range in datetime string \"%s\"", str);
+ goto error;
+ }
+ substr += 2;
+ sublen -= 2;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Next character must be a ':' or the end of the string */
+ if (sublen > 0 && *substr == ':') {
+ ++substr;
+ --sublen;
+ }
+ else {
+ goto parse_timezone;
+ }
+
+ /* Can't have a trailing ':' */
+ if (sublen == 0) {
+ goto parse_error;
+ }
+
+ /* PARSE THE SECONDS (2 digits) */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ out->sec = 10 * (substr[0] - '0') + (substr[1] - '0');
+
+ if (out->sec < 0 || out->sec >= 60) {
+ PyErr_Format(PyExc_ValueError,
+ "Seconds out of range in datetime string \"%s\"", str);
+ goto error;
+ }
+ substr += 2;
+ sublen -= 2;
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* Next character may be a '.' indicating fractional seconds */
+ if (sublen > 0 && *substr == '.') {
+ ++substr;
+ --sublen;
+ }
+ else {
+ goto parse_timezone;
+ }
+
+ /* PARSE THE MICROSECONDS (0 to 6 digits) */
+ for (i = 0; i < 6; ++i) {
+ out->us *= 10;
+ if (sublen > 0 && isdigit(*substr)) {
+ out->us += (*substr - '0');
+ }
+ }
+
+ if (sublen == 0 || !isdigit(*substr)) {
+ goto parse_timezone;
+ }
+
+ /* PARSE THE PICOSECONDS (0 to 6 digits) */
+ for (i = 0; i < 6; ++i) {
+ out->ps *= 10;
+ if (sublen > 0 && isdigit(*substr)) {
+ out->ps += (*substr - '0');
+ }
+ }
+
+ if (sublen == 0 || !isdigit(*substr)) {
+ goto parse_timezone;
+ }
+
+ /* PARSE THE ATTOSECONDS (0 to 6 digits) */
+ for (i = 0; i < 6; ++i) {
+ out->as *= 10;
+ if (sublen > 0 && isdigit(*substr)) {
+ out->as += (*substr - '0');
+ }
+ }
+
+parse_timezone:
+ if (sublen == 0) {
+ /* TODO: Convert from local time zone, as ISO states? */
+ goto finish;
+ }
+
+ /* UTC specifier */
+ if (*substr == 'Z') {
+ if (sublen == 1) {
+ goto finish;
+ }
+ else {
+ ++substr;
+ goto parse_error;
+ }
+ }
+ /* Time zone offset */
+ else if (*substr == '-' || *substr == '+') {
+ int offset_neg = 0, offset_hour = 0, offset_minute = 0;
+ if (*substr == '-') {
+ offset_neg = 1;
+ }
+ ++substr;
+ --sublen;
+
+ /* The hours offset */
+ if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ offset_hour = 10 * (substr[0] - '0') + (substr[1] - '0');
+ substr += 2;
+ sublen -= 2;
+ if (offset_hour >= 24) {
+ PyErr_Format(PyExc_ValueError,
+ "Timezone hours offset out of range "
+ "in datetime string \"%s\"", str);
+ goto error;
+ }
+ }
+ else {
+ goto parse_error;
+ }
+
+ /* The minutes offset is optional */
+ if (sublen > 0) {
+ /* Optional ':' */
+ if (*substr == ':') {
+ ++substr;
+ --sublen;
+ }
+
+ /* The minutes offset (at the end of the string) */
+ if (sublen == 2 && isdigit(substr[0]) && isdigit(substr[1])) {
+ offset_minute = 10 * (substr[0] - '0') + (substr[1] - '0');
+ if (offset_minute >= 60) {
+ PyErr_Format(PyExc_ValueError,
+ "Timezone minutes offset out of range "
+ "in datetime string \"%s\"", str);
+ goto error;
+ }
+ }
+ else {
+ goto parse_error;
+ }
+ }
+
+ /* Apply the time zone offset */
+ if (offset_neg) {
+ offset_hour = -offset_hour;
+ offset_minute = -offset_minute;
+ }
+ datetimestruct_timezone_offset(out, 60 * offset_hour + offset_minute);
+ }
+ else {
+ goto parse_error;
+ }
+
+finish:
+ return 0;
+
+parse_error:
+ PyErr_Format(PyExc_ValueError,
+ "Error parsing datetime string \"%s\" at position %d",
+ str, (int)(substr-str));
+ return -1;
+
+error:
+ return -1;
+}
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index f9209bf71..f223690e7 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -125,6 +125,106 @@ class TestDateTime(TestCase):
def test_divisor_conversion_as(self):
self.assertRaises(ValueError, lambda : np.dtype('M8[as/10]'))
+ def test_string_parser_variants(self):
+ """
+ # Different month formats
+ assert_equal(np.array(['1980-02-29'], np.dtype('M8')),
+ np.array(['1980-Feb-29'], np.dtype('M8')))
+ assert_equal(np.array(['1980-02-29'], np.dtype('M8')),
+ np.array(['1980-feb-29'], np.dtype('M8')))
+ assert_equal(np.array(['1980-02-29'], np.dtype('M8')),
+ np.array(['1980-FEB-29'], np.dtype('M8')))
+ """
+ # Allow space instead of 'T' between date and time
+ assert_equal(np.array(['1980-02-29T01:02:03'], np.dtype('M8')),
+ np.array(['1980-02-29 01:02:03'], np.dtype('M8')))
+ # Allow negative years
+ assert_equal(np.array(['-1980-02-29T01:02:03'], np.dtype('M8')),
+ np.array(['-1980-02-29 01:02:03'], np.dtype('M8')))
+ # UTC specifier
+ assert_equal(np.array(['-1980-02-29T01:02:03Z'], np.dtype('M8')),
+ np.array(['-1980-02-29 01:02:03Z'], np.dtype('M8')))
+ # Time zone offset
+ assert_equal(np.array(['-1980-02-29T01:02:03Z'], np.dtype('M8')),
+ np.array(['-1980-02-29 00:32:03+0130'], np.dtype('M8')))
+ assert_equal(np.array(['-1980-02-28T22:32:03Z'], np.dtype('M8')),
+ np.array(['-1980-02-29 00:02:03-01:30'], np.dtype('M8')))
+ assert_equal(np.array(['-1980-02-29T02:32:03.506Z'], np.dtype('M8')),
+ np.array(['-1980-02-29 00:32:03.506+02'], np.dtype('M8')))
+
+ def test_string_parser_error_check(self):
+ # Arbitrary bad string
+ assert_raises(ValueError, np.array, ['badvalue'], np.dtype('M8'))
+ # Character after year must be '-'
+ assert_raises(ValueError, np.array, ['1980X'], np.dtype('M8'))
+ # Cannot have trailing '-'
+ assert_raises(ValueError, np.array, ['1980-'], np.dtype('M8'))
+ # Month must be in range [1,12]
+ assert_raises(ValueError, np.array, ['1980-00'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-13'], np.dtype('M8'))
+ # Month must have two digits
+ assert_raises(ValueError, np.array, ['1980-1'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-1-02'], np.dtype('M8'))
+ # 'Mor' is not a valid month
+ assert_raises(ValueError, np.array, ['1980-Mor'], np.dtype('M8'))
+ # Cannot have trailing '-'
+ assert_raises(ValueError, np.array, ['1980-01-'], np.dtype('M8'))
+ # Day must be in range [1,len(month)]
+ assert_raises(ValueError, np.array, ['1980-01-0'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-01-00'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-01-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1979-02-29'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-30'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-03-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-04-31'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-05-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-06-31'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-07-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-08-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-09-31'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-10-32'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-11-31'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-12-32'], np.dtype('M8'))
+ # Cannot have trailing characters
+ assert_raises(ValueError, np.array, ['1980-02-03%'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 q'], np.dtype('M8'))
+
+ # Hours must be in range [0, 23]
+ assert_raises(ValueError, np.array, ['1980-02-03 25'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03T25'], np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 24:01'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03T24:01'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 -1'], np.dtype('M8'))
+ # No trailing ':'
+ assert_raises(ValueError, np.array, ['1980-02-03 01:'], np.dtype('M8'))
+ # Minutes must be in range [0, 59]
+ assert_raises(ValueError, np.array, ['1980-02-03 01:-1'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:60'],
+ np.dtype('M8'))
+ # No trailing ':'
+ assert_raises(ValueError, np.array, ['1980-02-03 01:60:'],
+ np.dtype('M8'))
+ # Seconds must be in range [0, 59]
+ assert_raises(ValueError, np.array, ['1980-02-03 01:10:-1'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:60'],
+ np.dtype('M8'))
+ # Timezone offset must within a reasonable range
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+0661'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+2500'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-0070'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-3000'],
+ np.dtype('M8'))
+ assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-25:00'],
+ np.dtype('M8'))
+
+
def test_creation_overflow(self):
date = '1980-03-23 20:00:00'
timesteps = np.array([date], dtype='datetime64[s]')[0].astype(np.int64)