diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst index c1fde3d13ef74..e0bacc4584bfa 100644 --- a/doc/source/whatsnew/v1.5.2.rst +++ b/doc/source/whatsnew/v1.5.2.rst @@ -20,7 +20,7 @@ Fixed regressions from being passed using the ``colormap`` argument if Matplotlib 3.6+ is used (:issue:`49374`) - Fixed regression in :func:`date_range` returning an invalid set of periods for ``CustomBusinessDay`` frequency and ``start`` date with timezone (:issue:`49441`) - Fixed performance regression in groupby operations (:issue:`49676`) -- +- Fixed regression in :class:`Timedelta` constructor returning object of wrong type when subclassing ``Timedelta`` (:issue:`49579`) .. --------------------------------------------------------------------------- .. _whatsnew_152.bug_fixes: diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index d084b4944b6ed..5cc97a722b7a6 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -189,7 +189,7 @@ def ints_to_pytimedelta(ndarray m8values, box=False): res_val = NaT else: if box: - res_val = _timedelta_from_value_and_reso(value, reso=reso) + res_val = _timedelta_from_value_and_reso(Timedelta, value, reso=reso) elif reso == NPY_DATETIMEUNIT.NPY_FR_ns: res_val = timedelta(microseconds=int(value) / 1000) elif reso == NPY_DATETIMEUNIT.NPY_FR_us: @@ -741,7 +741,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._creso) + return _timedelta_from_value_and_reso(Timedelta, new_value, self._creso) f.__name__ = name return f @@ -804,7 +804,7 @@ def _binary_op_method_timedeltalike(op, name): # TODO: more generally could do an overflowcheck in op? return NaT - return _timedelta_from_value_and_reso(res, reso=self._creso) + return _timedelta_from_value_and_reso(Timedelta, res, reso=self._creso) f.__name__ = name return f @@ -935,10 +935,10 @@ cdef _to_py_int_float(v): def _timedelta_unpickle(value, reso): - return _timedelta_from_value_and_reso(value, reso) + return _timedelta_from_value_and_reso(Timedelta, value, reso) -cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso): +cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso): # Could make this a classmethod if/when cython supports cdef classmethods cdef: _Timedelta td_base @@ -949,13 +949,13 @@ cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso): # We pass 0 instead, and override seconds, microseconds, days. # In principle we could pass 0 for ns and us too. if reso == NPY_FR_ns: - td_base = _Timedelta.__new__(Timedelta, microseconds=int(value) // 1000) + td_base = _Timedelta.__new__(cls, microseconds=int(value) // 1000) elif reso == NPY_DATETIMEUNIT.NPY_FR_us: - td_base = _Timedelta.__new__(Timedelta, microseconds=int(value)) + td_base = _Timedelta.__new__(cls, microseconds=int(value)) elif reso == NPY_DATETIMEUNIT.NPY_FR_ms: - td_base = _Timedelta.__new__(Timedelta, milliseconds=0) + td_base = _Timedelta.__new__(cls, milliseconds=0) elif reso == NPY_DATETIMEUNIT.NPY_FR_s: - td_base = _Timedelta.__new__(Timedelta, seconds=0) + td_base = _Timedelta.__new__(cls, seconds=0) # Other resolutions are disabled but could potentially be implemented here: # elif reso == NPY_DATETIMEUNIT.NPY_FR_m: # td_base = _Timedelta.__new__(Timedelta, minutes=int(value)) @@ -1528,7 +1528,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 _timedelta_from_value_and_reso(cls, value, reso) def as_unit(self, str unit, bint round_ok=True): """ @@ -1763,7 +1763,7 @@ class Timedelta(_Timedelta): if value == NPY_NAT: return NaT - return _timedelta_from_value_and_reso(value, NPY_FR_ns) + return _timedelta_from_value_and_reso(cls, value, NPY_FR_ns) def __setstate__(self, state): if len(state) == 1: @@ -1855,6 +1855,7 @@ class Timedelta(_Timedelta): return NaT return _timedelta_from_value_and_reso( + Timedelta, (other * self.value), reso=self._creso, ) diff --git a/pandas/tests/scalar/timedelta/test_constructors.py b/pandas/tests/scalar/timedelta/test_constructors.py index dd671e3c9e094..e4120478370d1 100644 --- a/pandas/tests/scalar/timedelta/test_constructors.py +++ b/pandas/tests/scalar/timedelta/test_constructors.py @@ -503,3 +503,12 @@ def test_timedelta_new_npnat(): # GH#48898 nat = np.timedelta64("NaT", "h") assert Timedelta(nat) is NaT + + +def test_subclass_respected(): + # GH#49579 + class MyCustomTimedelta(Timedelta): + pass + + td = MyCustomTimedelta("1 minute") + assert isinstance(td, MyCustomTimedelta)