Skip to content

Commit

Permalink
BUG: fix casting rules for generic datetime64/timedelta64 units
Browse files Browse the repository at this point in the history
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.
shoyer committed Oct 14, 2015

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 9cc55dc commit 33adec2
Showing 2 changed files with 35 additions and 8 deletions.
22 changes: 14 additions & 8 deletions numpy/core/src/multiarray/datetime.c
Original file line number Diff line number Diff line change
@@ -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) &&
21 changes: 21 additions & 0 deletions numpy/core/tests/test_datetime.py
Original file line number Diff line number Diff line change
@@ -114,6 +114,27 @@ def test_datetime_casting_rules(self):
# 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'),

0 comments on commit 33adec2

Please sign in to comment.