Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0d2d6ff

Browse files
authored
BUG: Raise warning for integer arithmetic with timedelta64/datetime64 arrays (#31290)
1 parent 61371bf commit 0d2d6ff

5 files changed

Lines changed: 101 additions & 6 deletions

File tree

numpy/_core/src/umath/ufunc_type_resolution.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,12 @@ PyUFunc_AdditionTypeResolver(PyUFuncObject *ufunc,
818818
/* m8[<A>] + int => m8[<A>] + m8[<A>] */
819819
else if (PyTypeNum_ISINTEGER(type_num2) ||
820820
PyTypeNum_ISBOOL(type_num2)) {
821+
if (DEPRECATE(
822+
"The 'generic' unit for NumPy timedelta is deprecated, "
823+
"and will raise an error in the future. "
824+
"Please convert the integer with an explicit unit.") < 0) {
825+
return -1;
826+
}
821827
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
822828
PyArray_DESCR(operands[0]));
823829
if (out_dtypes[0] == NULL) {
@@ -855,6 +861,12 @@ PyUFunc_AdditionTypeResolver(PyUFuncObject *ufunc,
855861
/* M8[<A>] + int => M8[<A>] + m8[<A>] */
856862
else if (PyTypeNum_ISINTEGER(type_num2) ||
857863
PyTypeNum_ISBOOL(type_num2)) {
864+
if (DEPRECATE(
865+
"The 'generic' unit for NumPy timedelta is deprecated, "
866+
"and will raise an error in the future. "
867+
"Please convert the integer with an explicit unit.") < 0) {
868+
return -1;
869+
}
858870
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
859871
PyArray_DESCR(operands[0]));
860872
if (out_dtypes[0] == NULL) {
@@ -880,6 +892,12 @@ PyUFunc_AdditionTypeResolver(PyUFuncObject *ufunc,
880892
else if (PyTypeNum_ISINTEGER(type_num1) || PyTypeNum_ISBOOL(type_num1)) {
881893
/* int + m8[<A>] => m8[<A>] + m8[<A>] */
882894
if (type_num2 == NPY_TIMEDELTA) {
895+
if (DEPRECATE(
896+
"The 'generic' unit for NumPy timedelta is deprecated, "
897+
"and will raise an error in the future. "
898+
"Please convert the integer with an explicit unit.") < 0) {
899+
return -1;
900+
}
883901
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
884902
PyArray_DESCR(operands[1]));
885903
if (out_dtypes[0] == NULL) {
@@ -893,6 +911,12 @@ PyUFunc_AdditionTypeResolver(PyUFuncObject *ufunc,
893911
type_num1 = NPY_TIMEDELTA;
894912
}
895913
else if (type_num2 == NPY_DATETIME) {
914+
if (DEPRECATE(
915+
"The 'generic' unit for NumPy timedelta is deprecated, "
916+
"and will raise an error in the future. "
917+
"Please convert the integer with an explicit unit.") < 0) {
918+
return -1;
919+
}
896920
/* Make a new NPY_TIMEDELTA, and copy type2's metadata */
897921
out_dtypes[0] = timedelta_dtype_with_copied_meta(
898922
PyArray_DESCR(operands[1]));
@@ -991,6 +1015,12 @@ PyUFunc_SubtractionTypeResolver(PyUFuncObject *ufunc,
9911015
/* m8[<A>] - int => m8[<A>] - m8[<A>] */
9921016
else if (PyTypeNum_ISINTEGER(type_num2) ||
9931017
PyTypeNum_ISBOOL(type_num2)) {
1018+
if (DEPRECATE(
1019+
"The 'generic' unit for NumPy timedelta is deprecated, "
1020+
"and will raise an error in the future. "
1021+
"Please convert the integer with an explicit unit.") < 0) {
1022+
return -1;
1023+
}
9941024
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
9951025
PyArray_DESCR(operands[0]));
9961026
if (out_dtypes[0] == NULL) {
@@ -1028,6 +1058,12 @@ PyUFunc_SubtractionTypeResolver(PyUFuncObject *ufunc,
10281058
/* M8[<A>] - int => M8[<A>] - m8[<A>] */
10291059
else if (PyTypeNum_ISINTEGER(type_num2) ||
10301060
PyTypeNum_ISBOOL(type_num2)) {
1061+
if (DEPRECATE(
1062+
"The 'generic' unit for NumPy timedelta is deprecated, "
1063+
"and will raise an error in the future. "
1064+
"Please convert the integer with an explicit unit.") < 0) {
1065+
return -1;
1066+
}
10311067
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
10321068
PyArray_DESCR(operands[0]));
10331069
if (out_dtypes[0] == NULL) {
@@ -1069,6 +1105,12 @@ PyUFunc_SubtractionTypeResolver(PyUFuncObject *ufunc,
10691105
else if (PyTypeNum_ISINTEGER(type_num1) || PyTypeNum_ISBOOL(type_num1)) {
10701106
/* int - m8[<A>] => m8[<A>] - m8[<A>] */
10711107
if (type_num2 == NPY_TIMEDELTA) {
1108+
if (DEPRECATE(
1109+
"The 'generic' unit for NumPy timedelta is deprecated, "
1110+
"and will raise an error in the future. "
1111+
"Please convert the integer with an explicit unit.") < 0) {
1112+
return -1;
1113+
}
10721114
out_dtypes[0] = NPY_DT_CALL_ensure_canonical(
10731115
PyArray_DESCR(operands[1]));
10741116
if (out_dtypes[0] == NULL) {

numpy/_core/tests/test_datetime.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2375,7 +2375,11 @@ def test_timedelta_arange(self):
23752375
dtype="m8",
23762376
)
23772377
assert_equal(a.dtype, np.dtype('m8[s]'))
2378-
assert_equal(a, np.timedelta64(0, 's') + np.arange(3, 10, 2))
2378+
with pytest.warns(
2379+
DeprecationWarning,
2380+
match=self.generic_unit_deprecation_message
2381+
):
2382+
assert_equal(a, np.timedelta64(0, 's') + np.arange(3, 10, 2))
23792383

23802384
# Step of 0 is disallowed
23812385
assert_raises(ValueError, np.arange, np.timedelta64(0, 's'),

numpy/_core/tests/test_deprecations.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,8 @@ def test_round_emits_deprecation_warning_scalar(self):
435435

436436
class TestDeprecatedGenericTimedelta(_DeprecationTestCase):
437437
# Deprecated in Numpy 2.5, 2025-11
438-
# See gh-29619
438+
# See gh-29619 for scalar operations
439+
# See gh-31255 for array operations
439440
message = "The 'generic' unit for NumPy timedelta is deprecated"
440441

441442
@pytest.mark.parametrize('value', [
@@ -460,7 +461,7 @@ def test_raise_warning_for_timedelta_with_generic_unit(self, value: int | str):
460461
def test_raise_warning_for_operation_with_generic_unit(
461462
self, value: int, generic_value: int, op: Callable
462463
):
463-
self.assert_deprecated(op, args=(value, generic_value))
464+
self.assert_deprecated(op, num=1, args=(value, generic_value))
464465

465466
def test_raise_warning_for_default_constructor(self):
466467
self.assert_deprecated(lambda: np.timedelta64())
@@ -470,6 +471,54 @@ def test_raise_warning_for_NAT_construction(self):
470471
self.assert_deprecated(lambda: np.datetime64('NaT'))
471472
self.assert_deprecated(lambda: np.datetime64(None))
472473

474+
# Array operations
475+
@pytest.mark.parametrize('timedelta_arr', [
476+
np.array([1, 2, 3], dtype="m8[s]"),
477+
np.array([1], dtype="m8[ms]"),
478+
])
479+
@pytest.mark.parametrize('int_arr', [
480+
np.array([1, 2, 3]),
481+
np.array([1]),
482+
])
483+
@pytest.mark.parametrize("op", [np.add, np.subtract])
484+
def test_timedelta_array_integer_array_arithmetic(
485+
self, timedelta_arr, int_arr, op
486+
):
487+
"""Test that timedelta64 array + integer array triggers deprecation."""
488+
# timedelta op int
489+
self.assert_deprecated(op, num=None, args=(timedelta_arr, int_arr))
490+
# int op timedelta
491+
self.assert_deprecated(op, num=None, args=(int_arr, timedelta_arr))
492+
493+
@pytest.mark.parametrize('datetime_arr', [
494+
np.array(['2020-01-01', '2020-01-02'], dtype="M8[D]"),
495+
np.array(['2020-01-01T00:00:00'], dtype="M8[s]"),
496+
])
497+
@pytest.mark.parametrize('int_arr', [
498+
np.array([1, 2]),
499+
np.array([1]),
500+
])
501+
@pytest.mark.parametrize("op", [np.add, np.subtract])
502+
def test_datetime_array_integer_array_arithmetic(
503+
self, datetime_arr, int_arr, op
504+
):
505+
"""Test that datetime64 array + integer array triggers deprecation."""
506+
# datetime op int
507+
self.assert_deprecated(op, num=None, args=(datetime_arr, int_arr))
508+
# int op datetime
509+
if op == np.add:
510+
self.assert_deprecated(op, num=None, args=(int_arr, datetime_arr))
511+
512+
def test_non_associative_case_warns(self):
513+
"""Verify the specific non-associative case from gh-31255 warns."""
514+
a = np.array([1], dtype="m8[s]")
515+
b = np.array([1])
516+
c = np.array([1], dtype="m8[ms]")
517+
518+
# Both intermediate operations should trigger warnings
519+
self.assert_deprecated(np.add, num=None, args=(a, b))
520+
self.assert_deprecated(np.add, num=None, args=(b, c))
521+
473522

474523
class TestTriDeprecationWithNonInteger(_DeprecationTestCase):
475524
# Deprecation in NumPy 2.5, 2026-03

numpy/lib/tests/test_histograms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ def test_some_nan_values(self):
303303

304304
def test_datetime(self):
305305
begin = np.datetime64('2000-01-01', 'D')
306-
offsets = np.array([0, 0, 1, 1, 2, 3, 5, 10, 20])
307-
bins = np.array([0, 2, 7, 20])
306+
offsets = np.array([0, 0, 1, 1, 2, 3, 5, 10, 20], dtype='m8[D]')
307+
bins = np.array([0, 2, 7, 20], dtype='m8[D]')
308308
dates = begin + offsets
309309
date_bins = begin + bins
310310

numpy/ma/tests/test_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3376,7 +3376,7 @@ def test_allclose_timedelta(self):
33763376
# Allclose currently works for timedelta64 as long as `atol` is
33773377
# an integer or also a timedelta64
33783378
a = np.array([[1, 2, 3, 4]], dtype="m8[ns]")
3379-
assert allclose(a, a, atol=0)
3379+
assert allclose(a, a, atol=np.timedelta64(0, "ns"))
33803380
assert allclose(a, a, atol=np.timedelta64(1, "ns"))
33813381

33823382
def test_allany(self):

0 commit comments

Comments
 (0)