diff --git a/doc/release/1.16.0-notes.rst b/doc/release/1.16.0-notes.rst index 0ba4636d9317..8730c5860b4c 100644 --- a/doc/release/1.16.0-notes.rst +++ b/doc/release/1.16.0-notes.rst @@ -42,6 +42,11 @@ Future Changes * NumPy 1.17 will drop support for Python 2.7. +Expired deprecations +==================== + +* NaT comparisons now return ``False`` without a warning, finishing a + deprecation cycle begun in NumPy 1.11. Compatibility notes =================== @@ -54,6 +59,15 @@ whenever the ``Scripts`` directory is in the path. Folks needing compatibility with earler versions of Numpy should run ``f2py`` as a module: ``python -m numpy.f2py [...]``. +NaT comparisons +--------------- +Consistent with the behavior of NaN, all comparisons other than inequality +checks with datetime64 or timedelta64 NaT ("not-a-time") values now always +return ``False``, and inequality checks with NaT now always return ``True``. +This includes comparisons beteween NaT values. For compatibility with the +old behavior, use ``np.isnat`` to explicitly check for NaT or convert +datetime64/timedelta64 arrays with ``.astype(np.int64)`` before making +comparisons. C API changes ============= @@ -149,4 +163,3 @@ modules, they are now python wrappers to the single `np.core/_multiarray_math` c-extension module. .. _`NEP 15` : http://www.numpy.org/neps/nep-0015-merge-multiarray-umath.html - diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 66b69f555006..e62942efd461 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1327,27 +1327,12 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) { - npy_bool give_future_warning = 0; BINARY_LOOP { const @type@ in1 = *(@type@ *)ip1; const @type@ in2 = *(@type@ *)ip2; - const npy_bool res = in1 @OP@ in2; - *((npy_bool *)op1) = res; - - if ((in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) && res) { - give_future_warning = 1; - } - } - if (give_future_warning) { - NPY_ALLOW_C_API_DEF - NPY_ALLOW_C_API; - /* 2016-01-18, 1.11 */ - if (DEPRECATE_FUTUREWARNING( - "In the future, 'NAT @OP@ x' and 'x @OP@ NAT' " - "will always be False.") < 0) { - /* nothing to do, we return anyway */ - } - NPY_DISABLE_C_API; + *((npy_bool *)op1) = (in1 @OP@ in2 && + in1 != NPY_DATETIME_NAT && + in2 != NPY_DATETIME_NAT); } } /**end repeat1**/ @@ -1355,26 +1340,12 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @TYPE@_not_equal(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) { - npy_bool give_future_warning = 0; BINARY_LOOP { const @type@ in1 = *(@type@ *)ip1; const @type@ in2 = *(@type@ *)ip2; - *((npy_bool *)op1) = in1 != in2; - - if (in1 == NPY_DATETIME_NAT && in2 == NPY_DATETIME_NAT) { - give_future_warning = 1; - } - } - if (give_future_warning) { - NPY_ALLOW_C_API_DEF - NPY_ALLOW_C_API; - /* 2016-01-18, 1.11 */ - if (DEPRECATE_FUTUREWARNING( - "In the future, NAT != NAT will be True " - "rather than False.") < 0) { - /* nothing to do, we return anyway */ - } - NPY_DISABLE_C_API; + *((npy_bool *)op1) = (in1 != in2 || + in1 == NPY_DATETIME_NAT || + in2 == NPY_DATETIME_NAT); } } diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index a5e1f73ce87b..c4918f955075 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -7,7 +7,7 @@ import datetime import pytest from numpy.testing import ( - assert_, assert_equal, assert_raises, assert_warns, suppress_warnings + assert_, assert_equal, assert_raises, assert_warns, suppress_warnings, ) # Use pytz to test out various time zones if available @@ -130,13 +130,10 @@ def test_datetime_casting_rules(self): def test_compare_generic_nat(self): # regression tests for gh-6452 - assert_equal(np.datetime64('NaT'), - np.datetime64('2000') + np.timedelta64('NaT')) - # nb. we may want to make NaT != NaT true in the future - with suppress_warnings() as sup: - sup.filter(FutureWarning, ".*NAT ==") - assert_(np.datetime64('NaT') == np.datetime64('NaT', 'us')) - assert_(np.datetime64('NaT', 'us') == np.datetime64('NaT')) + assert_(np.datetime64('NaT') != + np.datetime64('2000') + np.timedelta64('NaT')) + 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 @@ -1154,47 +1151,23 @@ def test_datetime_compare_nat(self): td_nat = np.timedelta64('NaT', 'h') td_other = np.timedelta64(1, 'h') - with suppress_warnings() as sup: - # The assert warns contexts will again see the warning: - sup.filter(FutureWarning, ".*NAT") - - for op in [np.equal, np.less, np.less_equal, - np.greater, np.greater_equal]: - if op(dt_nat, dt_nat): - assert_warns(FutureWarning, op, dt_nat, dt_nat) - if op(dt_nat, dt_other): - assert_warns(FutureWarning, op, dt_nat, dt_other) - if op(dt_other, dt_nat): - assert_warns(FutureWarning, op, dt_other, dt_nat) - if op(td_nat, td_nat): - assert_warns(FutureWarning, op, td_nat, td_nat) - if op(td_nat, td_other): - assert_warns(FutureWarning, op, td_nat, td_other) - if op(td_other, td_nat): - assert_warns(FutureWarning, op, td_other, td_nat) - - assert_warns(FutureWarning, np.not_equal, dt_nat, dt_nat) - assert_warns(FutureWarning, np.not_equal, td_nat, td_nat) - - with suppress_warnings() as sup: - sup.record(FutureWarning) - assert_(np.not_equal(dt_nat, dt_other)) - assert_(np.not_equal(dt_other, dt_nat)) - assert_(np.not_equal(td_nat, td_other)) - assert_(np.not_equal(td_other, td_nat)) - assert_equal(len(sup.log), 0) - - def test_datetime_futurewarning_once_nat(self): - # Test that the futurewarning is only given once per inner loop - arr1 = np.array(['NaT', 'NaT', '2000-01-01'] * 2, dtype='M8[s]') - arr2 = np.array(['NaT', '2000-01-01', 'NaT'] * 2, dtype='M8[s]') - # All except less, because for less it can't be wrong (NaT is min) for op in [np.equal, np.less, np.less_equal, np.greater, np.greater_equal]: - with suppress_warnings() as sup: - rec = sup.record(FutureWarning, ".*NAT") - op(arr1, arr2) - assert_(len(rec) == 1, "failed for {}".format(op)) + assert_(not op(dt_nat, dt_nat)) + assert_(not op(dt_nat, dt_other)) + assert_(not op(dt_other, dt_nat)) + + assert_(not op(td_nat, td_nat)) + assert_(not op(td_nat, td_other)) + assert_(not op(td_other, td_nat)) + + assert_(np.not_equal(dt_nat, dt_nat)) + assert_(np.not_equal(dt_nat, dt_other)) + assert_(np.not_equal(dt_other, dt_nat)) + + assert_(np.not_equal(td_nat, td_nat)) + assert_(np.not_equal(td_nat, td_other)) + assert_(np.not_equal(td_other, td_nat)) def test_datetime_minmax(self): # The metadata of the result should become the GCD