summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorStephan Hoyer <shoyer@climate.com>2015-10-12 16:44:00 -0700
committerStephan Hoyer <shoyer@climate.com>2015-10-13 20:59:49 -0700
commit33adec24a1403df5c47afe235ac1869a8f489489 (patch)
tree86d2ed7f93c128f516413fb89598c24e132465bc /numpy
parent9cc55dc7720a949cb3e6578805fe6f70906a700e (diff)
downloadnumpy-33adec24a1403df5c47afe235ac1869a8f489489.tar.gz
BUG: fix casting rules for generic datetime64/timedelta64 units
Fixes GH6452 There are two types of datetime64/timedelta64 objects with generic times units: * NaT * unit-less timedelta64 objects Both of these should be safely castable to any more specific dtype. However, more specific dtypes should not be safely castable to generic units. Otherwise, the result of `np.datetime64('NaT')` or `np.timedelta(1)` is entirely useless, because they can't be used in any arithmetic operations or comparisons. This is a regression from NumPy 1.9, where these sort of operations worked because the default casting rules with ufuncs were less strict.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/multiarray/datetime.c22
-rw-r--r--numpy/core/tests/test_datetime.py21
2 files changed, 35 insertions, 8 deletions
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index 9e4e00e9c..264178d30 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -1232,12 +1232,18 @@ datetime_metadata_divides(
{
npy_uint64 num1, num2;
- /* Generic units divide into anything */
- if (divisor->base == NPY_FR_GENERIC) {
+ /*
+ * Any unit can always divide into generic units. In other words, we
+ * should be able to convert generic units into any more specific unit.
+ */
+ if (dividend->base == NPY_FR_GENERIC) {
return 1;
}
- /* Non-generic units never divide into generic units */
- else if (dividend->base == NPY_FR_GENERIC) {
+ /*
+ * However, generic units cannot always divide into more specific units.
+ * We cannot safely convert datetimes with units back into generic units.
+ */
+ else if (divisor->base == NPY_FR_GENERIC) {
return 0;
}
@@ -1330,7 +1336,7 @@ can_cast_datetime64_units(NPY_DATETIMEUNIT src_unit,
*/
case NPY_SAME_KIND_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
- return src_unit == dst_unit;
+ return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= NPY_FR_D && dst_unit <= NPY_FR_D) ||
@@ -1344,7 +1350,7 @@ can_cast_datetime64_units(NPY_DATETIMEUNIT src_unit,
*/
case NPY_SAFE_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
- return src_unit == dst_unit;
+ return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= dst_unit) &&
@@ -1380,7 +1386,7 @@ can_cast_timedelta64_units(NPY_DATETIMEUNIT src_unit,
*/
case NPY_SAME_KIND_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
- return src_unit == dst_unit;
+ return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= NPY_FR_M && dst_unit <= NPY_FR_M) ||
@@ -1394,7 +1400,7 @@ can_cast_timedelta64_units(NPY_DATETIMEUNIT src_unit,
*/
case NPY_SAFE_CASTING:
if (src_unit == NPY_FR_GENERIC || dst_unit == NPY_FR_GENERIC) {
- return src_unit == dst_unit;
+ return src_unit == NPY_FR_GENERIC;
}
else {
return (src_unit <= dst_unit) &&
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index 5fa281867..98da6638d 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -114,6 +114,27 @@ class TestDateTime(TestCase):
# Can cast safely if the integer multiplier does divide
assert_(np.can_cast('M8[6h]', 'M8[3h]', casting='safe'))
+ # We can always cast types with generic units (corresponding to NaT) to
+ # more specific types
+ assert_(np.can_cast('m8', 'm8[h]', casting='same_kind'))
+ assert_(np.can_cast('m8', 'm8[h]', casting='safe'))
+ assert_(np.can_cast('M8', 'M8[h]', casting='same_kind'))
+ assert_(np.can_cast('M8', 'M8[h]', casting='safe'))
+ # but not the other way around
+ assert_(not np.can_cast('m8[h]', 'm8', casting='same_kind'))
+ assert_(not np.can_cast('m8[h]', 'm8', casting='safe'))
+ assert_(not np.can_cast('M8[h]', 'M8', casting='same_kind'))
+ assert_(not np.can_cast('M8[h]', 'M8', casting='safe'))
+
+ def test_compare_generic_nat(self):
+ # regression tests for GH6452
+ assert_equal(np.datetime64('NaT'),
+ np.datetime64('2000') + np.timedelta64('NaT'))
+ # nb. we may want to make NaT != NaT true in the future; this test
+ # verifies the existing behavior (and that it should not warn)
+ assert_(np.datetime64('NaT') == np.datetime64('NaT', 'us'))
+ assert_(np.datetime64('NaT', 'us') == np.datetime64('NaT'))
+
def test_datetime_scalar_construction(self):
# Construct with different units
assert_equal(np.datetime64('1950-03-12', 'D'),