diff options
author | Stephan Hoyer <shoyer@climate.com> | 2015-10-12 16:44:00 -0700 |
---|---|---|
committer | Stephan Hoyer <shoyer@climate.com> | 2015-10-13 20:59:49 -0700 |
commit | 33adec24a1403df5c47afe235ac1869a8f489489 (patch) | |
tree | 86d2ed7f93c128f516413fb89598c24e132465bc | |
parent | 9cc55dc7720a949cb3e6578805fe6f70906a700e (diff) | |
download | numpy-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.
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 22 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 21 |
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'), |