""" Tests for scalar Timedelta arithmetic ops """ from datetime import datetime, timedelta import operator import numpy as np import pytest from pandas.compat.numpy import is_numpy_dev import pandas as pd from pandas import NaT, Timedelta, Timestamp, compat, offsets import pandas._testing as tm from pandas.core import ops class TestTimedeltaAdditionSubtraction: """ Tests for Timedelta methods: __add__, __radd__, __sub__, __rsub__ """ @pytest.mark.parametrize( "ten_seconds", [ Timedelta(10, unit="s"), timedelta(seconds=10), np.timedelta64(10, "s"), np.timedelta64(10000000000, "ns"), offsets.Second(10), ], ) def test_td_add_sub_ten_seconds(self, ten_seconds): # GH#6808 base = Timestamp("20130101 09:01:12.123456") expected_add = Timestamp("20130101 09:01:22.123456") expected_sub = Timestamp("20130101 09:01:02.123456") result = base + ten_seconds assert result == expected_add result = base - ten_seconds assert result == expected_sub @pytest.mark.parametrize( "one_day_ten_secs", [ Timedelta("1 day, 00:00:10"), Timedelta("1 days, 00:00:10"), timedelta(days=1, seconds=10), np.timedelta64(1, "D") + np.timedelta64(10, "s"), offsets.Day() + offsets.Second(10), ], ) def test_td_add_sub_one_day_ten_seconds(self, one_day_ten_secs): # GH#6808 base = Timestamp("20130102 09:01:12.123456") expected_add = Timestamp("20130103 09:01:22.123456") expected_sub = Timestamp("20130101 09:01:02.123456") result = base + one_day_ten_secs assert result == expected_add result = base - one_day_ten_secs assert result == expected_sub @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_datetimelike_scalar(self, op): # GH#19738 td = Timedelta(10, unit="d") result = op(td, datetime(2016, 1, 1)) if op is operator.add: # datetime + Timedelta does _not_ call Timedelta.__radd__, # so we get a datetime back instead of a Timestamp assert isinstance(result, Timestamp) assert result == Timestamp(2016, 1, 11) result = op(td, Timestamp("2018-01-12 18:09")) assert isinstance(result, Timestamp) assert result == Timestamp("2018-01-22 18:09") result = op(td, np.datetime64("2018-01-12")) assert isinstance(result, Timestamp) assert result == Timestamp("2018-01-22") result = op(td, NaT) assert result is NaT def test_td_add_timestamp_overflow(self): msg = "int too (large|big) to convert" with pytest.raises(OverflowError, match=msg): Timestamp("1700-01-01") + Timedelta(13 * 19999, unit="D") with pytest.raises(OverflowError, match=msg): Timestamp("1700-01-01") + timedelta(days=13 * 19999) @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_td(self, op): td = Timedelta(10, unit="d") result = op(td, Timedelta(days=10)) assert isinstance(result, Timedelta) assert result == Timedelta(days=20) @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_pytimedelta(self, op): td = Timedelta(10, unit="d") result = op(td, timedelta(days=9)) assert isinstance(result, Timedelta) assert result == Timedelta(days=19) @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_timedelta64(self, op): td = Timedelta(10, unit="d") result = op(td, np.timedelta64(-4, "D")) assert isinstance(result, Timedelta) assert result == Timedelta(days=6) @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_offset(self, op): td = Timedelta(10, unit="d") result = op(td, offsets.Hour(6)) assert isinstance(result, Timedelta) assert result == Timedelta(days=10, hours=6) def test_td_sub_td(self): td = Timedelta(10, unit="d") expected = Timedelta(0, unit="ns") result = td - td assert isinstance(result, Timedelta) assert result == expected def test_td_sub_pytimedelta(self): td = Timedelta(10, unit="d") expected = Timedelta(0, unit="ns") result = td - td.to_pytimedelta() assert isinstance(result, Timedelta) assert result == expected result = td.to_pytimedelta() - td assert isinstance(result, Timedelta) assert result == expected def test_td_sub_timedelta64(self): td = Timedelta(10, unit="d") expected = Timedelta(0, unit="ns") result = td - td.to_timedelta64() assert isinstance(result, Timedelta) assert result == expected result = td.to_timedelta64() - td assert isinstance(result, Timedelta) assert result == expected def test_td_sub_nat(self): # In this context pd.NaT is treated as timedelta-like td = Timedelta(10, unit="d") result = td - NaT assert result is NaT def test_td_sub_td64_nat(self): td = Timedelta(10, unit="d") td_nat = np.timedelta64("NaT") result = td - td_nat assert result is NaT result = td_nat - td assert result is NaT def test_td_sub_offset(self): td = Timedelta(10, unit="d") result = td - offsets.Hour(1) assert isinstance(result, Timedelta) assert result == Timedelta(239, unit="h") def test_td_add_sub_numeric_raises(self): td = Timedelta(10, unit="d") msg = "unsupported operand type" for other in [2, 2.0, np.int64(2), np.float64(2)]: with pytest.raises(TypeError, match=msg): td + other with pytest.raises(TypeError, match=msg): other + td with pytest.raises(TypeError, match=msg): td - other with pytest.raises(TypeError, match=msg): other - td def test_td_rsub_nat(self): td = Timedelta(10, unit="d") result = NaT - td assert result is NaT result = np.datetime64("NaT") - td assert result is NaT def test_td_rsub_offset(self): result = offsets.Hour(1) - Timedelta(10, unit="d") assert isinstance(result, Timedelta) assert result == Timedelta(-239, unit="h") def test_td_sub_timedeltalike_object_dtype_array(self): # GH#21980 arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")]) exp = np.array([Timestamp("20121231 9:01"), Timestamp("20121229 9:02")]) res = arr - Timedelta("1D") tm.assert_numpy_array_equal(res, exp) def test_td_sub_mixed_most_timedeltalike_object_dtype_array(self): # GH#21980 now = Timestamp.now() arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")]) exp = np.array( [ now - Timedelta("1D"), Timedelta("0D"), np.timedelta64(2, "h") - Timedelta("1D"), ] ) res = arr - Timedelta("1D") tm.assert_numpy_array_equal(res, exp) def test_td_rsub_mixed_most_timedeltalike_object_dtype_array(self): # GH#21980 now = Timestamp.now() arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")]) msg = r"unsupported operand type\(s\) for \-: 'Timedelta' and 'Timestamp'" with pytest.raises(TypeError, match=msg): Timedelta("1D") - arr @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_timedeltalike_object_dtype_array(self, op): # GH#21980 arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")]) exp = np.array([Timestamp("20130102 9:01"), Timestamp("20121231 9:02")]) res = op(arr, Timedelta("1D")) tm.assert_numpy_array_equal(res, exp) @pytest.mark.parametrize("op", [operator.add, ops.radd]) def test_td_add_mixed_timedeltalike_object_dtype_array(self, op): # GH#21980 now = Timestamp.now() arr = np.array([now, Timedelta("1D")]) exp = np.array([now + Timedelta("1D"), Timedelta("2D")]) res = op(arr, Timedelta("1D")) tm.assert_numpy_array_equal(res, exp) # TODO: moved from index tests following #24365, may need de-duplication def test_ops_ndarray(self): td = Timedelta("1 day") # timedelta, timedelta other = pd.to_timedelta(["1 day"]).values expected = pd.to_timedelta(["2 days"]).values tm.assert_numpy_array_equal(td + other, expected) tm.assert_numpy_array_equal(other + td, expected) msg = r"unsupported operand type\(s\) for \+: 'Timedelta' and 'int'" with pytest.raises(TypeError, match=msg): td + np.array([1]) msg = ( r"unsupported operand type\(s\) for \+: 'numpy.ndarray' and 'Timedelta'|" "Concatenation operation is not implemented for NumPy arrays" ) with pytest.raises(TypeError, match=msg): np.array([1]) + td expected = pd.to_timedelta(["0 days"]).values tm.assert_numpy_array_equal(td - other, expected) tm.assert_numpy_array_equal(-other + td, expected) msg = r"unsupported operand type\(s\) for -: 'Timedelta' and 'int'" with pytest.raises(TypeError, match=msg): td - np.array([1]) msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timedelta'" with pytest.raises(TypeError, match=msg): np.array([1]) - td expected = pd.to_timedelta(["2 days"]).values tm.assert_numpy_array_equal(td * np.array([2]), expected) tm.assert_numpy_array_equal(np.array([2]) * td, expected) msg = ( "ufunc '?multiply'? cannot use operands with types " r"dtype\('= off assert not td < off assert not td > off assert not td == 2 * off assert td != 2 * off assert td <= 2 * off assert td < 2 * off assert not td >= 2 * off assert not td > 2 * off def test_comparison_object_array(self): # analogous to GH#15183 td = Timedelta("2 days") other = Timedelta("3 hours") arr = np.array([other, td], dtype=object) res = arr == td expected = np.array([False, True], dtype=bool) assert (res == expected).all() # 2D case arr = np.array([[other, td], [td, other]], dtype=object) res = arr != td expected = np.array([[True, False], [False, True]], dtype=bool) assert res.shape == expected.shape assert (res == expected).all() def test_compare_timedelta_ndarray(self): # GH#11835 periods = [Timedelta("0 days 01:00:00"), Timedelta("0 days 01:00:00")] arr = np.array(periods) result = arr[0] > arr expected = np.array([False, False]) tm.assert_numpy_array_equal(result, expected) def test_compare_td64_ndarray(self): # GG#33441 arr = np.arange(5).astype("timedelta64[ns]") td = Timedelta(arr[1]) expected = np.array([False, True, False, False, False], dtype=bool) result = td == arr tm.assert_numpy_array_equal(result, expected) result = arr == td tm.assert_numpy_array_equal(result, expected) result = td != arr tm.assert_numpy_array_equal(result, ~expected) result = arr != td tm.assert_numpy_array_equal(result, ~expected) @pytest.mark.skip(reason="GH#20829 is reverted until after 0.24.0") def test_compare_custom_object(self): """ Make sure non supported operations on Timedelta returns NonImplemented and yields to other operand (GH#20829). """ class CustomClass: def __init__(self, cmp_result=None): self.cmp_result = cmp_result def generic_result(self): if self.cmp_result is None: return NotImplemented else: return self.cmp_result def __eq__(self, other): return self.generic_result() def __gt__(self, other): return self.generic_result() t = Timedelta("1s") assert not (t == "string") assert not (t == 1) assert not (t == CustomClass()) assert not (t == CustomClass(cmp_result=False)) assert t < CustomClass(cmp_result=True) assert not (t < CustomClass(cmp_result=False)) assert t == CustomClass(cmp_result=True) @pytest.mark.parametrize("val", ["string", 1]) def test_compare_unknown_type(self, val): # GH#20829 t = Timedelta("1s") msg = "not supported between instances of 'Timedelta' and '(int|str)'" with pytest.raises(TypeError, match=msg): t >= val with pytest.raises(TypeError, match=msg): t > val with pytest.raises(TypeError, match=msg): t <= val with pytest.raises(TypeError, match=msg): t < val def test_ops_notimplemented(): class Other: pass other = Other() td = Timedelta("1 day") assert td.__add__(other) is NotImplemented assert td.__sub__(other) is NotImplemented assert td.__truediv__(other) is NotImplemented assert td.__mul__(other) is NotImplemented assert td.__floordiv__(other) is NotImplemented def test_ops_error_str(): # GH#13624 td = Timedelta("1 day") for left, right in [(td, "a"), ("a", td)]: msg = "|".join( [ "unsupported operand type", r'can only concatenate str \(not "Timedelta"\) to str', "must be str, not Timedelta", ] ) with pytest.raises(TypeError, match=msg): left + right msg = "not supported between instances of" with pytest.raises(TypeError, match=msg): left > right assert not left == right assert left != right