Skip to content

Loading…

ENH: datetime: Unify datetime/timedelta type promotion #86

Closed
wants to merge 1 commit into from

1 participant

@mwiebe
NumPy member

This changes the datetime promotion to go to the more precise unit for M8 - M8, M8 +/- m8, m8 +/- M8.

Mark Wiebe ENH: datetime: Unify datetime/timedelta type promotion
Now it always goes to the more precise unit.
0800fe3
@mwiebe
NumPy member

Moved to datetime_dev pull request.

@mwiebe mwiebe closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 8, 2011
  1. ENH: datetime: Unify datetime/timedelta type promotion

    Mark Wiebe committed
    Now it always goes to the more precise unit.
View
13 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.
*
View
88 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");
View
102 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;
View
38 numpy/core/tests/test_datetime.py
@@ -450,7 +450,7 @@ def test_datetime_add(self):
# 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]'),
@@ -458,7 +458,7 @@ def test_datetime_add(self):
# 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]'),
@@ -503,27 +503,24 @@ def test_datetime_add(self):
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]'),
@@ -533,6 +530,7 @@ def test_datetime_subtract(self):
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]'),
@@ -567,14 +565,16 @@ def test_datetime_subtract(self):
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
Something went wrong with that request. Please try again.