diff options
-rw-r--r-- | numpy/core/src/multiarray/_datetime.h | 13 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 88 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 102 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 38 |
4 files changed, 119 insertions, 122 deletions
diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h index 643c9b106..05dab36a4 100644 --- a/numpy/core/src/multiarray/_datetime.h +++ b/numpy/core/src/multiarray/_datetime.h @@ -94,19 +94,6 @@ datetime_metadata_divides( int strict_with_nonlinear_units); /* - * Computes the GCD of the two date-time metadata values. Raises - * an exception if there is no reasonable GCD, such as with - * years and days. - * - * Returns a capsule with the GCD metadata. - */ -NPY_NO_EXPORT PyObject * -compute_datetime_metadata_greatest_common_divisor( - PyArray_Descr *type1, - PyArray_Descr *type2, - int strict_with_nonlinear_units); - -/* * Computes the conversion factor to convert data with 'src_meta' metadata * into data with 'dst_meta' metadata, not taking into account the events. * diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 642341570..1a85209b5 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -1632,11 +1632,12 @@ datetime_metadata_divides( } -NPY_NO_EXPORT PyObject * +static PyObject * compute_datetime_metadata_greatest_common_divisor( PyArray_Descr *type1, PyArray_Descr *type2, - int strict_with_nonlinear_units) + int strict_with_nonlinear_units1, + int strict_with_nonlinear_units2) { PyArray_DatetimeMetaData *meta1, *meta2, *dt_data; NPY_DATETIMEUNIT base; @@ -1688,7 +1689,7 @@ compute_datetime_metadata_greatest_common_divisor( base = NPY_FR_M; num1 *= 12; } - else if (strict_with_nonlinear_units) { + else if (strict_with_nonlinear_units1) { goto incompatible_units; } else { @@ -1701,7 +1702,7 @@ compute_datetime_metadata_greatest_common_divisor( base = NPY_FR_M; num2 *= 12; } - else if (strict_with_nonlinear_units) { + else if (strict_with_nonlinear_units2) { goto incompatible_units; } else { @@ -1709,11 +1710,26 @@ compute_datetime_metadata_greatest_common_divisor( /* Don't multiply num2 since there is no even factor */ } } - else if (meta1->base == NPY_FR_M || - meta1->base == NPY_FR_B || - meta2->base == NPY_FR_M || - meta2->base == NPY_FR_B) { - if (strict_with_nonlinear_units) { + else if (meta1->base == NPY_FR_M) { + if (strict_with_nonlinear_units1) { + goto incompatible_units; + } + else { + base = meta2->base; + /* Don't multiply num1 since there is no even factor */ + } + } + else if (meta2->base == NPY_FR_M) { + if (strict_with_nonlinear_units2) { + goto incompatible_units; + } + else { + base = meta1->base; + /* Don't multiply num2 since there is no even factor */ + } + } + else if (meta1->base == NPY_FR_B || meta2->base == NPY_FR_B) { + if (strict_with_nonlinear_units1 || strict_with_nonlinear_units2) { goto incompatible_units; } else { @@ -1801,14 +1817,22 @@ units_overflow: { } /* - * Uses type1's type_num and the gcd of the metadata to create - * the result type. + * Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA. + * Applies the type promotion rules between the two types, returning + * the promoted type. */ -static PyArray_Descr * -datetime_gcd_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) +NPY_NO_EXPORT PyArray_Descr * +datetime_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) { + int type_num1, type_num2; PyObject *gcdmeta; PyArray_Descr *dtype; + int is_datetime; + + type_num1 = type1->type_num; + type_num2 = type2->type_num; + + is_datetime = (type_num1 == NPY_DATETIME || type_num2 == NPY_DATETIME); /* * Get the metadata GCD, being strict about nonlinear units for @@ -1816,13 +1840,15 @@ datetime_gcd_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) */ gcdmeta = compute_datetime_metadata_greatest_common_divisor( type1, type2, - type1->type_num == NPY_TIMEDELTA); + type_num1 == NPY_TIMEDELTA, + type_num2 == NPY_TIMEDELTA); if (gcdmeta == NULL) { return NULL; } /* Create a DATETIME or TIMEDELTA dtype */ - dtype = PyArray_DescrNewFromType(type1->type_num); + dtype = PyArray_DescrNewFromType(is_datetime ? NPY_DATETIME : + NPY_TIMEDELTA); if (dtype == NULL) { Py_DECREF(gcdmeta); return NULL; @@ -1847,39 +1873,7 @@ datetime_gcd_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) Py_DECREF(gcdmeta); return dtype; -} - -/* - * Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA. - * Applies the type promotion rules between the two types, returning - * the promoted type. - */ -NPY_NO_EXPORT PyArray_Descr * -datetime_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) -{ - int type_num1, type_num2; - - type_num1 = type1->type_num; - type_num2 = type2->type_num; - if (type_num1 == NPY_DATETIME) { - if (type_num2 == NPY_DATETIME) { - return datetime_gcd_type_promotion(type1, type2); - } - else if (type_num2 == NPY_TIMEDELTA) { - Py_INCREF(type1); - return type1; - } - } - else if (type_num1 == NPY_TIMEDELTA) { - if (type_num2 == NPY_DATETIME) { - Py_INCREF(type2); - return type2; - } - else if (type_num2 == NPY_TIMEDELTA) { - return datetime_gcd_type_promotion(type1, type2); - } - } PyErr_SetString(PyExc_RuntimeError, "Called datetime_type_promotion on non-datetype type"); diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 009876bab..e37dd0167 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -2243,8 +2243,8 @@ timedelta_dtype_with_copied_meta(PyArray_Descr *dtype) * int + m8[<A>] => m8[<A>] + m8[<A>] * M8[<A>] + int => M8[<A>] + m8[<A>] * int + M8[<A>] => m8[<A>] + M8[<A>] - * M8[<A>] + m8[<B>] => M8[<A>] + m8[<A>] - * m8[<A>] + M8[<B>] => m8[<B>] + M8[<B>] + * M8[<A>] + m8[<B>] => M8[gcd(<A>,<B>)] + m8[gcd(<A>,<B>)] + * m8[<A>] + M8[<B>] => m8[gcd(<A>,<B>)] + M8[gcd(<A>,<B>)] * TODO: Non-linear time unit cases require highly special-cased loops * M8[<A>] + m8[Y|M|B] * m8[Y|M|B] + M8[<A>] @@ -2287,16 +2287,20 @@ PyUFunc_AdditionTypeResolution(PyUFuncObject *ufunc, out_dtypes[2] = out_dtypes[0]; Py_INCREF(out_dtypes[2]); } - /* m8[<A>] + M8[<B>] => m8[<B>] + M8[<B>] */ + /* m8[<A>] + M8[<B>] => m8[gcd(<A>,<B>)] + M8[gcd(<A>,<B>)] */ else if (type_num2 == NPY_DATETIME) { - /* Make a new NPY_TIMEDELTA, and copy type2's metadata */ - out_dtypes[0] = timedelta_dtype_with_copied_meta( - PyArray_DESCR(operands[1])); + out_dtypes[1] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[1] == NULL) { + return -1; + } + /* Make a new NPY_TIMEDELTA, and copy the datetime's metadata */ + out_dtypes[0] = timedelta_dtype_with_copied_meta(out_dtypes[1]); if (out_dtypes[0] == NULL) { + Py_DECREF(out_dtypes[1]); + out_dtypes[1] = NULL; return -1; } - out_dtypes[1] = PyArray_DESCR(operands[1]); - Py_INCREF(out_dtypes[1]); out_dtypes[2] = out_dtypes[1]; Py_INCREF(out_dtypes[2]); } @@ -2317,10 +2321,25 @@ PyUFunc_AdditionTypeResolution(PyUFuncObject *ufunc, } } else if (type_num1 == NPY_DATETIME) { - /* M8[<A>] + m8[<B>] => M8[<A>] + m8[<A>] */ + /* M8[<A>] + m8[<B>] => M8[gcd(<A>,<B>)] + m8[gcd(<A>,<B>)] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + /* Make a new NPY_TIMEDELTA, and copy the datetime's metadata */ + out_dtypes[1] = timedelta_dtype_with_copied_meta(out_dtypes[0]); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } /* M8[<A>] + int => M8[<A>] + m8[<A>] */ - if (type_num2 == NPY_TIMEDELTA || - PyTypeNum_ISINTEGER(type_num2) || + else if (PyTypeNum_ISINTEGER(type_num2) || PyTypeNum_ISBOOL(type_num2)) { /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ out_dtypes[1] = timedelta_dtype_with_copied_meta( @@ -2421,7 +2440,7 @@ type_reso_error: { * m8[<A>] - int => m8[<A>] - m8[<A>] * int - m8[<A>] => m8[<A>] - m8[<A>] * M8[<A>] - int => M8[<A>] - m8[<A>] - * M8[<A>] - m8[<B>] => M8[<A>] - m8[<A>] + * M8[<A>] - m8[<B>] => M8[gcd(<A>,<B>)] - m8[gcd(<A>,<B>)] * TODO: Non-linear time unit cases require highly special-cased loops * M8[<A>] - m8[Y|M|B] */ @@ -2480,10 +2499,25 @@ PyUFunc_SubtractionTypeResolution(PyUFuncObject *ufunc, } } else if (type_num1 == NPY_DATETIME) { - /* M8[<A>] - m8[<B>] => M8[<A>] - m8[<A>] */ + /* M8[<A>] - m8[<B>] => M8[gcd(<A>,<B>)] - m8[gcd(<A>,<B>)] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + /* Make a new NPY_TIMEDELTA, and copy the datetime's metadata */ + out_dtypes[1] = timedelta_dtype_with_copied_meta(out_dtypes[0]); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } /* M8[<A>] - int => M8[<A>] - m8[<A>] */ - if (type_num2 == NPY_TIMEDELTA || - PyTypeNum_ISINTEGER(type_num2) || + else if (PyTypeNum_ISINTEGER(type_num2) || PyTypeNum_ISBOOL(type_num2)) { /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ out_dtypes[1] = timedelta_dtype_with_copied_meta( @@ -2498,39 +2532,21 @@ PyUFunc_SubtractionTypeResolution(PyUFuncObject *ufunc, type_num2 = NPY_TIMEDELTA; } - /* M8[<A>] - M8[<A>] (producing m8[<A>])*/ + /* M8[<A>] - M8[<B>] => M8[gcd(<A>,<B>)] - M8[gcd(<A>,<B>)] */ else if (type_num2 == NPY_DATETIME) { - PyArray_DatetimeMetaData *meta1, *meta2; - - meta1 = get_datetime_metadata_from_dtype( - PyArray_DESCR(operands[0])); - if (meta1 == NULL) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { return -1; } - meta2 = get_datetime_metadata_from_dtype( - PyArray_DESCR(operands[1])); - if (meta2 == NULL) { + /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ + out_dtypes[2] = timedelta_dtype_with_copied_meta(out_dtypes[0]); + if (out_dtypes[2] == NULL) { + Py_DECREF(out_dtypes[0]); return -1; } - - /* If the metadata matches up, the subtraction is ok */ - if (meta1->num == meta2->num && - meta1->base == meta2->base && - meta1->events == meta2->events) { - out_dtypes[0] = PyArray_DESCR(operands[1]); - Py_INCREF(out_dtypes[0]); - out_dtypes[1] = out_dtypes[0]; - Py_INCREF(out_dtypes[1]); - /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ - out_dtypes[2] = timedelta_dtype_with_copied_meta( - PyArray_DESCR(operands[0])); - if (out_dtypes[2] == NULL) { - return -1; - } - } - else { - goto type_reso_error; - } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); } else { goto type_reso_error; diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 316d03ecc..994066137 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -539,7 +539,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(['1940-12-24'], dtype='M8[D]'), + np.array(['2012-12-21T11Z'], dtype='M8[h]'), np.array(['NaT'], dtype='M8[D]'), np.array([3], dtype='m8[D]'), np.array([11], dtype='m8[h]'), @@ -547,7 +547,7 @@ class TestDateTime(TestCase): # NumPy scalars (np.datetime64('2012-12-21', '[D]'), np.datetime64('2012-12-24', '[D]'), - np.datetime64('1940-12-24', '[D]'), + np.datetime64('2012-12-21T11Z', '[h]'), np.datetime64('NaT', '[D]'), np.timedelta64(3, '[D]'), np.timedelta64(11, '[h]'), @@ -592,27 +592,24 @@ class TestDateTime(TestCase): assert_equal(tda + dtnat, dtnat) assert_equal((tda + dta).dtype, np.dtype('M8[D]')) - # In M8 + m8, the M8 controls the result type - assert_equal(dta + tdb, dta) - assert_equal((dta + tdb).dtype, np.dtype('M8[D]')) - assert_equal(dtc + tdb, dtc) - assert_equal((dtc + tdb).dtype, np.dtype('M8[D]')) - assert_equal(tdb + dta, dta) - assert_equal((tdb + dta).dtype, np.dtype('M8[D]')) - assert_equal(tdb + dtc, dtc) - assert_equal((tdb + dtc).dtype, np.dtype('M8[D]')) + # In M8 + m8, the result goes to higher precision + assert_equal(dta + tdb, dtc) + assert_equal((dta + tdb).dtype, np.dtype('M8[h]')) + assert_equal(tdb + dta, dtc) + assert_equal((tdb + dta).dtype, np.dtype('M8[h]')) # M8 + M8 assert_raises(TypeError, np.add, dta, dtb) def test_datetime_subtract(self): - for dta, dtb, dtc, dtd, dtnat, tda, tdb, tdc in \ + for dta, dtb, dtc, dtd, dte, dtnat, tda, tdb, tdc in \ [ # One-dimensional arrays (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-24'], dtype='M8[h]'), + np.array(['1940-12-23T13Z'], dtype='M8[h]'), np.array(['NaT'], dtype='M8[D]'), np.array([3], dtype='m8[D]'), np.array([11], dtype='m8[h]'), @@ -622,6 +619,7 @@ class TestDateTime(TestCase): np.datetime64('2012-12-24', '[D]'), np.datetime64('1940-12-24', '[D]'), np.datetime64('1940-12-24', '[h]'), + np.datetime64('1940-12-23T13Z', '[h]'), np.datetime64('NaT', '[D]'), np.timedelta64(3, '[D]'), np.timedelta64(11, '[h]'), @@ -656,14 +654,16 @@ class TestDateTime(TestCase): assert_equal(dtnat - tda, dtnat) assert_equal((dtb - tda).dtype, np.dtype('M8[D]')) - # In M8 - m8, the M8 controls the result type - assert_equal(dta - tdb, dta) - assert_equal((dta - tdb).dtype, np.dtype('M8[D]')) - assert_equal(dtc - tdb, dtc) - assert_equal((dtc - tdb).dtype, np.dtype('M8[D]')) + # In M8 - m8, the result goes to higher precision + assert_equal(dtc - tdb, dte) + assert_equal((dtc - tdb).dtype, np.dtype('M8[h]')) + + # M8 - M8 with different goes to higher precision + assert_equal(dtc - dtd, 0) + assert_equal((dtc - dtd).dtype, np.dtype('m8[h]')) + assert_equal(dtd - dtc, 0) + assert_equal((dtd - dtc).dtype, np.dtype('m8[h]')) - # M8 - M8 with different metadata - assert_raises(TypeError, np.subtract, dtc, dtd) # m8 - M8 assert_raises(TypeError, np.subtract, tda, dta) # bool - M8 |