diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 4eb1494c4d56c..3ce75d09f78e8 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1,4 +1,5 @@ import collections +import operator import warnings cimport cython @@ -55,6 +56,7 @@ from pandas._libs.tslibs.np_datetime cimport ( pandas_timedelta_to_timedeltastruct, pandas_timedeltastruct, ) +from pandas._libs.util cimport INT64_MAX from pandas._libs.tslibs.np_datetime import OutOfBoundsTimedelta @@ -217,12 +219,11 @@ cpdef int64_t delta_to_nanoseconds(delta) except? -1: + delta.microseconds ) * 1000 except OverflowError as err: - raise OutOfBoundsTimedelta(*err.args) from err - + msg = f"{delta} outside allowed range [{NPY_NAT + 1}ns, {INT64_MAX}ns]" + raise OutOfBoundsTimedelta(msg) from err raise TypeError(type(delta)) -@cython.overflowcheck(True) cdef object ensure_td64ns(object ts): """ Overflow-safe implementation of td64.astype("m8[ns]") @@ -241,24 +242,20 @@ cdef object ensure_td64ns(object ts): str unitstr td64_unit = get_datetime64_unit(ts) - if ( - td64_unit != NPY_DATETIMEUNIT.NPY_FR_ns - and td64_unit != NPY_DATETIMEUNIT.NPY_FR_GENERIC - ): - unitstr = npy_unit_to_abbrev(td64_unit) + if td64_unit == NPY_DATETIMEUNIT.NPY_FR_ns or td64_unit == NPY_DATETIMEUNIT.NPY_FR_GENERIC: + return ts - td64_value = get_timedelta64_value(ts) + unitstr = npy_unit_to_abbrev(td64_unit) + mult = precision_from_unit(unitstr)[0] - mult = precision_from_unit(unitstr)[0] + with cython.overflowcheck(True): try: - # NB: cython#1381 this cannot be *= - td64_value = td64_value * mult + td64_value = get_timedelta64_value(ts) * mult except OverflowError as err: - raise OutOfBoundsTimedelta(ts) from err + msg = f"{str(ts)} outside allowed range [{NPY_NAT + 1}ns, {INT64_MAX}ns]" + raise OutOfBoundsTimedelta(msg) from err - return np.timedelta64(td64_value, "ns") - - return ts + return np.timedelta64(td64_value, "ns") cdef convert_to_timedelta64(object ts, str unit): @@ -674,8 +671,7 @@ cdef bint _validate_ops_compat(other): def _op_unary_method(func, name): def f(self): - new_value = func(self.value) - return _timedelta_from_value_and_reso(new_value, self._reso) + return create_timedelta(func(self.value), "ignore", self._reso) f.__name__ = name return f @@ -724,13 +720,7 @@ def _binary_op_method_timedeltalike(op, name): if self._reso != other._reso: raise NotImplementedError - res = op(self.value, other.value) - if res == NPY_NAT: - # e.g. test_implementation_limits - # TODO: more generally could do an overflowcheck in op? - return NaT - - return _timedelta_from_value_and_reso(res, reso=self._reso) + return create_timedelta(op(self.value, other.value), "ignore", self._reso) f.__name__ = name return f @@ -861,7 +851,7 @@ cdef _to_py_int_float(v): def _timedelta_unpickle(value, reso): - return _timedelta_from_value_and_reso(value, reso) + return create_timedelta(value, "ignore", reso) cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso): @@ -892,6 +882,68 @@ cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso): return td_base +@cython.overflowcheck(True) +cdef object create_timedelta(object value, str in_unit, NPY_DATETIMEUNIT out_reso): + """ + Timedelta factory. + + Overflow-safe, and allows for the creation of Timedeltas with non-nano resos while + the public API for that gets hashed out (ref: GH#46587). For now, Timedelta.__new__ + just does arg validation and kwarg processing. + + _timedelta_from_value_and_reso faster if value already an int that can be safely + cast to an int64. + + Parameters + ---------- + value : Timedelta, timedelta, np.timedelta64, str, int, float + The same value types accepted by Timedelta.__new__ + in_unit : str + Denote the (np) unit of the input, if it's numeric + out_reso: NPY_DATETIMEUNIT + Desired resolution of new Timedelta + + Notes + ----- + Pass in_unit="ignore" (or "ns") with a numeric value to just do overflow checking + (and bypass the prior behavior of converting value -> td64[ns] -> int) + """ + cdef: + int64_t out_value + + if isinstance(value, _Timedelta): + return value + + try: + # if unit == "ns", no need to create an m8[ns] just to read the (same) value back + # if unit == "ignore", assume caller wants to invoke an overflow-safe version of + # _timedelta_from_value_and_reso, and that any float rounding is acceptable + if (is_integer_object(value) or is_float_object(value)) and ( + in_unit == "ns" or in_unit == "ignore" + ): + if util.is_nan(value): + return NaT + out_value = value + # is_timedelta_64_object may not give correct results w/ some versions? see e.g. + # github.com/pandas-dev/pandas/runs/6397652653?check_suite_focus=true#step:11:435 + elif isinstance(value, np.timedelta64): + out_value = ensure_td64ns(value).view(np.int64) + elif isinstance(value, str): + if value.startswith(("P", "-P")): + out_value = parse_iso_format_string(value) + else: + out_value = parse_timedelta_string(value) + else: + out_value = convert_to_timedelta64(value, in_unit).view(np.int64) + except OverflowError as err: + msg = f"{value} outside allowed range [{NPY_NAT + 1}ns, {INT64_MAX}ns]" + raise OutOfBoundsTimedelta(msg) from err + + if out_value == NPY_NAT: + return NaT + return _timedelta_from_value_and_reso(out_value, out_reso) + + # Similar to Timestamp/datetime, this is a construction requirement for # timedeltas that we need to do object instantiation in python. This will # serve as a C extension type that shadows the Python class, where we do any @@ -1375,7 +1427,7 @@ cdef class _Timedelta(timedelta): @classmethod def _from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso): # exposing as classmethod for testing - return _timedelta_from_value_and_reso(value, reso) + return create_timedelta(value, "ignore", reso) # Python front end to C extension type _Timedelta @@ -1438,99 +1490,49 @@ class Timedelta(_Timedelta): We see that either way we get the same result """ - _req_any_kwargs_new = {"weeks", "days", "hours", "minutes", "seconds", - "milliseconds", "microseconds", "nanoseconds"} + _allowed_kwargs = ( + "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", + "nanoseconds" + ) def __new__(cls, object value=_no_input, unit=None, **kwargs): - cdef _Timedelta td_base + cdef: + NPY_DATETIMEUNIT out_reso = NPY_FR_ns + # process kwargs iff no value passed if value is _no_input: - if not len(kwargs): - raise ValueError("cannot construct a Timedelta without a " - "value/unit or descriptive keywords " - "(days,seconds....)") - - kwargs = {key: _to_py_int_float(kwargs[key]) for key in kwargs} - - unsupported_kwargs = set(kwargs) - unsupported_kwargs.difference_update(cls._req_any_kwargs_new) - if unsupported_kwargs or not cls._req_any_kwargs_new.intersection(kwargs): + if not kwargs: + raise ValueError( + "cannot construct a Timedelta without a value/unit " + "or descriptive keywords (days,seconds....)" + ) + if not kwargs.keys() <= set(cls._allowed_kwargs): raise ValueError( "cannot construct a Timedelta from the passed arguments, " - "allowed keywords are " - "[weeks, days, hours, minutes, seconds, " - "milliseconds, microseconds, nanoseconds]" + f"allowed keywords are {cls._allowed_kwargs}" ) - - # GH43764, convert any input to nanoseconds first and then - # create the timestamp. This ensures that any potential - # nanosecond contributions from kwargs parsed as floats - # are taken into consideration. - seconds = int(( - ( - (kwargs.get('days', 0) + kwargs.get('weeks', 0) * 7) * 24 - + kwargs.get('hours', 0) - ) * 3600 - + kwargs.get('minutes', 0) * 60 - + kwargs.get('seconds', 0) - ) * 1_000_000_000 - ) - - value = np.timedelta64( - int(kwargs.get('nanoseconds', 0)) - + int(kwargs.get('microseconds', 0) * 1_000) - + int(kwargs.get('milliseconds', 0) * 1_000_000) - + seconds + # GH43764, convert any input to nanoseconds first, to ensure any potential + # nanosecond contributions from kwargs parsed as floats are included + ns = ( + _to_py_int_float(kwargs.get("weeks", 0)) * 7 * 24 * 3600 * 1_000_000_000 + + _to_py_int_float(kwargs.get("days", 0)) * 24 * 3600 * 1_000_000_000 + + _to_py_int_float(kwargs.get("hours", 0)) * 3600 * 1_000_000_000 + + _to_py_int_float(kwargs.get("minutes", 0)) * 60 * 1_000_000_000 + + _to_py_int_float(kwargs.get("seconds", 0)) * 1_000_000_000 + + _to_py_int_float(kwargs.get("milliseconds", 0)) * 1_000_000 + + _to_py_int_float(kwargs.get("microseconds", 0)) * 1_000 + + _to_py_int_float(kwargs.get("nanoseconds", 0)) ) + return create_timedelta(ns, "ns", out_reso) - if unit in {'Y', 'y', 'M'}: + if isinstance(value, str) and unit is not None: + raise ValueError("unit must not be specified if the value is a str") + elif unit in {"Y", "y", "M"}: raise ValueError( "Units 'M', 'Y', and 'y' are no longer supported, as they do not " "represent unambiguous timedelta values durations." ) - - # GH 30543 if pd.Timedelta already passed, return it - # check that only value is passed - if isinstance(value, _Timedelta) and unit is None and len(kwargs) == 0: - return value - elif isinstance(value, _Timedelta): - value = value.value - elif isinstance(value, str): - if unit is not None: - raise ValueError("unit must not be specified if the value is a str") - if (len(value) > 0 and value[0] == 'P') or ( - len(value) > 1 and value[:2] == '-P' - ): - value = parse_iso_format_string(value) - else: - value = parse_timedelta_string(value) - value = np.timedelta64(value) - elif PyDelta_Check(value): - value = convert_to_timedelta64(value, 'ns') - elif is_timedelta64_object(value): - value = ensure_td64ns(value) - elif is_tick_object(value): - value = np.timedelta64(value.nanos, 'ns') - elif is_integer_object(value) or is_float_object(value): - # unit=None is de-facto 'ns' - unit = parse_timedelta_unit(unit) - value = convert_to_timedelta64(value, unit) - elif checknull_with_nat(value): - return NaT - else: - raise ValueError( - "Value must be Timedelta, string, integer, " - f"float, timedelta or convertible, not {type(value).__name__}" - ) - - if is_timedelta64_object(value): - value = value.view('i8') - - # nat - if value == NPY_NAT: - return NaT - - return _timedelta_from_value_and_reso(value, NPY_FR_ns) + return create_timedelta(value, parse_timedelta_unit(unit), out_reso) def __setstate__(self, state): if len(state) == 1: @@ -1607,30 +1609,25 @@ class Timedelta(_Timedelta): # Arithmetic Methods # TODO: Can some of these be defined in the cython class? - __neg__ = _op_unary_method(lambda x: -x, '__neg__') - __pos__ = _op_unary_method(lambda x: x, '__pos__') - __abs__ = _op_unary_method(lambda x: abs(x), '__abs__') + __neg__ = _op_unary_method(operator.neg, "__neg__") + __pos__ = _op_unary_method(operator.pos, "__pos__") + __abs__ = _op_unary_method(operator.abs, "__abs__") - __add__ = _binary_op_method_timedeltalike(lambda x, y: x + y, '__add__') - __radd__ = _binary_op_method_timedeltalike(lambda x, y: x + y, '__radd__') - __sub__ = _binary_op_method_timedeltalike(lambda x, y: x - y, '__sub__') - __rsub__ = _binary_op_method_timedeltalike(lambda x, y: y - x, '__rsub__') + __add__ = _binary_op_method_timedeltalike(operator.add, "__add__") + __radd__ = _binary_op_method_timedeltalike(operator.add, "__radd__") + __sub__ = _binary_op_method_timedeltalike(operator.sub, "__sub__") + __rsub__ = _binary_op_method_timedeltalike(lambda x, y: y - x, "__rsub__") def __mul__(self, other): - if is_integer_object(other) or is_float_object(other): - if util.is_nan(other): - # np.nan * timedelta -> np.timedelta64("NaT"), in this case NaT - return NaT - - return _timedelta_from_value_and_reso( - (other * self.value), - reso=self._reso, - ) - - elif is_array(other): + if util.is_nan(other): + # np.nan * timedelta -> np.timedelta64("NaT"), in this case NaT + return NaT + if is_array(other): # ndarray-like return other * self.to_timedelta64() - + if is_integer_object(other) or is_float_object(other): + # can't call Timedelta b/c it doesn't (yet) expose reso + return create_timedelta(self.value * other, "ignore", self._reso) return NotImplemented __rmul__ = __mul__ @@ -1825,6 +1822,6 @@ cdef _broadcast_floordiv_td64( # resolution in ns -Timedelta.min = Timedelta(np.iinfo(np.int64).min + 1) -Timedelta.max = Timedelta(np.iinfo(np.int64).max) +Timedelta.min = Timedelta(NPY_NAT + 1) +Timedelta.max = Timedelta(INT64_MAX) Timedelta.resolution = Timedelta(nanoseconds=1) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index abdb4aebb625f..c25a9d08c60b8 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -90,7 +90,10 @@ from pandas._libs.tslibs.np_datetime cimport ( pydatetime_to_dt64, ) -from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime +from pandas._libs.tslibs.np_datetime import ( + OutOfBoundsDatetime, + OutOfBoundsTimedelta, +) from pandas._libs.tslibs.offsets cimport ( BaseOffset, @@ -435,14 +438,13 @@ cdef class _Timestamp(ABCTimestamp): # Timedelta try: return Timedelta(self.value - other.value) - except (OverflowError, OutOfBoundsDatetime) as err: - if isinstance(other, _Timestamp): - if both_timestamps: - raise OutOfBoundsDatetime( - "Result is too large for pandas.Timedelta. Convert inputs " - "to datetime.datetime with 'Timestamp.to_pydatetime()' " - "before subtracting." - ) from err + except OutOfBoundsTimedelta as err: + if both_timestamps: + raise OutOfBoundsTimedelta( + "Result is too large for pandas.Timedelta. Convert inputs " + "to datetime.datetime with 'Timestamp.to_pydatetime()' " + "before subtracting." + ) from err # We get here in stata tests, fall back to stdlib datetime # method and return stdlib timedelta object pass @@ -461,7 +463,7 @@ cdef class _Timestamp(ABCTimestamp): if PyDateTime_Check(other): try: return type(self)(other) - self - except (OverflowError, OutOfBoundsDatetime) as err: + except (OverflowError, OutOfBoundsDatetime, OutOfBoundsTimedelta) as err: # We get here in stata tests, fall back to stdlib datetime # method and return stdlib timedelta object pass diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 74aa7f045088e..955dc86285a20 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -22,6 +22,10 @@ import pandas._testing as tm from pandas.core import ops +TD_OVERFLOW_MSG = ( + r"outside allowed range \[-9223372036854775807ns, 9223372036854775807ns\]" +) + class TestTimedeltaAdditionSubtraction: """ @@ -99,11 +103,10 @@ def test_td_add_datetimelike_scalar(self, op): assert result is NaT def test_td_add_timestamp_overflow(self): - msg = "int too (large|big) to convert" - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timestamp("1700-01-01") + Timedelta(13 * 19999, unit="D") - with pytest.raises(OutOfBoundsTimedelta, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timestamp("1700-01-01") + timedelta(days=13 * 19999) @pytest.mark.parametrize("op", [operator.add, ops.radd]) diff --git a/pandas/tests/scalar/timedelta/test_constructors.py b/pandas/tests/scalar/timedelta/test_constructors.py index 7fc7bd3a5a74d..92937854679d0 100644 --- a/pandas/tests/scalar/timedelta/test_constructors.py +++ b/pandas/tests/scalar/timedelta/test_constructors.py @@ -12,19 +12,20 @@ to_timedelta, ) +TD_OVERFLOW_MSG = ( + r"outside allowed range \[-9223372036854775807ns, 9223372036854775807ns\]" +) + def test_construct_from_td64_with_unit(): # ignore the unit, as it may cause silently overflows leading to incorrect # results, and in non-overflow cases is irrelevant GH#46827 obj = np.timedelta64(123456789, "h") - with pytest.raises(OutOfBoundsTimedelta, match="123456789 hours"): - Timedelta(obj, unit="ps") - - with pytest.raises(OutOfBoundsTimedelta, match="123456789 hours"): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(obj, unit="ns") - with pytest.raises(OutOfBoundsTimedelta, match="123456789 hours"): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(obj) @@ -204,18 +205,16 @@ def test_td_from_repr_roundtrip(val): def test_overflow_on_construction(): - msg = "int too (large|big) to convert" - # GH#3374 value = Timedelta("1day").value * 20169940 - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(value) # xref GH#17637 - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(7 * 19999, unit="D") - with pytest.raises(OutOfBoundsTimedelta, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(timedelta(days=13 * 19999)) @@ -238,8 +237,7 @@ def test_construction_out_of_bounds_td64(val, unit, name): td64 = np.timedelta64(val, unit) assert td64.astype("m8[ns]").view("i8") < 0 # i.e. naive astype will be wrong - msg = str(val) + name - with pytest.raises(OutOfBoundsTimedelta, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(td64) # But just back in bounds and we are OK @@ -248,7 +246,7 @@ def test_construction_out_of_bounds_td64(val, unit, name): td64 *= -1 assert td64.astype("m8[ns]").view("i8") > 0 # i.e. naive astype will be wrong - with pytest.raises(OutOfBoundsTimedelta, match="-" + msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(td64) # But just back in bounds and we are OK diff --git a/pandas/tests/scalar/timedelta/test_timedelta.py b/pandas/tests/scalar/timedelta/test_timedelta.py index cf7211e82b799..87550022b414f 100644 --- a/pandas/tests/scalar/timedelta/test_timedelta.py +++ b/pandas/tests/scalar/timedelta/test_timedelta.py @@ -13,6 +13,7 @@ NaT, iNaT, ) +from pandas._libs.tslibs.np_datetime import OutOfBoundsTimedelta import pandas as pd from pandas import ( @@ -23,6 +24,10 @@ ) import pandas._testing as tm +TD_OVERFLOW_MSG = ( + r"outside allowed range \[-9223372036854775807ns, 9223372036854775807ns\]" +) + class TestNonNano: @pytest.fixture(params=[7, 8, 9]) @@ -658,21 +663,20 @@ def test_implementation_limits(self): # Beyond lower limit, a NAT before the Overflow assert (min_td - Timedelta(1, "ns")) is NaT - msg = "int too (large|big) to convert" - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): min_td - Timedelta(2, "ns") - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): max_td + Timedelta(1, "ns") # Same tests using the internal nanosecond values td = Timedelta(min_td.value - 1, "ns") assert td is NaT - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(min_td.value - 2, "ns") - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): Timedelta(max_td.value + 1, "ns") def test_total_seconds_precision(self): diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index b46962fb82896..5475b0d9042e1 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -8,7 +8,7 @@ import pytest from pandas._libs.tslibs import ( - OutOfBoundsDatetime, + OutOfBoundsTimedelta, Timedelta, Timestamp, offsets, @@ -17,6 +17,10 @@ import pandas._testing as tm +TD_OVERFLOW_MSG = ( + r"outside allowed range \[-9223372036854775807ns, 9223372036854775807ns\]" +) + class TestTimestampArithmetic: def test_overflow_offset(self): @@ -39,11 +43,6 @@ def test_overflow_offset_raises(self): stamp = Timestamp("2017-01-13 00:00:00") offset_overflow = 20169940 * offsets.Day(1) - msg = ( - "the add operation between " - r"\<-?\d+ \* Days\> and \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} " - "will overflow" - ) lmsg = "|".join( ["Python int too large to convert to C long", "int too big to convert"] ) @@ -51,7 +50,7 @@ def test_overflow_offset_raises(self): with pytest.raises(OverflowError, match=lmsg): stamp + offset_overflow - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): offset_overflow + stamp with pytest.raises(OverflowError, match=lmsg): @@ -61,12 +60,12 @@ def test_overflow_offset_raises(self): # used to crash, so check for proper overflow exception stamp = Timestamp("2000/1/1") - offset_overflow = to_offset("D") * 100**5 + offset_overflow = offsets.Day() * 100**5 with pytest.raises(OverflowError, match=lmsg): stamp + offset_overflow - with pytest.raises(OverflowError, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=TD_OVERFLOW_MSG): offset_overflow + stamp with pytest.raises(OverflowError, match=lmsg): @@ -78,7 +77,7 @@ def test_overflow_timestamp_raises(self): a = Timestamp("2101-01-01 00:00:00") b = Timestamp("1688-01-01 00:00:00") - with pytest.raises(OutOfBoundsDatetime, match=msg): + with pytest.raises(OutOfBoundsTimedelta, match=msg): a - b # but we're OK for timestamp and datetime.datetime diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index 0bd93a78227ff..e1307c6543a51 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -1848,13 +1848,8 @@ def test_to_datetime_overflow(self): # gh-17637 # we are overflowing Timedelta range here - msg = "|".join( - [ - "Python int too large to convert to C long", - "long too big to convert", - "int too big to convert", - ] - ) + # a fixture exists in tests/scalar; should it be moved to a higher level? + msg = R"outside allowed range \[-9223372036854775807ns, 9223372036854775807ns\]" with pytest.raises(OutOfBoundsTimedelta, match=msg): date_range(start="1/1/1700", freq="B", periods=100000) diff --git a/pandas/tests/tools/test_to_timedelta.py b/pandas/tests/tools/test_to_timedelta.py index 6c11ec42858c0..fcb2575f2251d 100644 --- a/pandas/tests/tools/test_to_timedelta.py +++ b/pandas/tests/tools/test_to_timedelta.py @@ -109,9 +109,7 @@ def test_to_timedelta_invalid_unit(self, arg): def test_to_timedelta_time(self): # time not supported ATM - msg = ( - "Value must be Timedelta, string, integer, float, timedelta or convertible" - ) + msg = "Invalid type for timedelta scalar" with pytest.raises(ValueError, match=msg): to_timedelta(time(second=1)) assert to_timedelta(time(second=1), errors="coerce") is pd.NaT @@ -264,10 +262,7 @@ def test_to_timedelta_zerodim(self, fixed_now_ts): dt64 = fixed_now_ts.to_datetime64() arg = np.array(dt64) - msg = ( - "Value must be Timedelta, string, integer, float, timedelta " - "or convertible, not datetime64" - ) + msg = "Invalid type for timedelta scalar" with pytest.raises(ValueError, match=msg): to_timedelta(arg) diff --git a/pandas/tests/tslibs/test_timedeltas.py b/pandas/tests/tslibs/test_timedeltas.py index d9e86d53f2587..d45d79b5e1896 100644 --- a/pandas/tests/tslibs/test_timedeltas.py +++ b/pandas/tests/tslibs/test_timedeltas.py @@ -1,5 +1,3 @@ -import re - import numpy as np import pytest @@ -65,14 +63,8 @@ def test_huge_nanoseconds_overflow(): "kwargs", [{"Seconds": 1}, {"seconds": 1, "Nanoseconds": 1}, {"Foo": 2}] ) def test_kwarg_assertion(kwargs): - err_message = ( - "cannot construct a Timedelta from the passed arguments, " - "allowed keywords are " - "[weeks, days, hours, minutes, seconds, " - "milliseconds, microseconds, nanoseconds]" - ) - - with pytest.raises(ValueError, match=re.escape(err_message)): + msg = "cannot construct a Timedelta from the passed arguments" + with pytest.raises(ValueError, match=msg): Timedelta(**kwargs)