Skip to content

Commit 606499d

Browse files
kraschkrasch
and
krasch
authored
BUG: Behaviour change in 1.5.0 when using Timedelta as Enum data type (#49579)
* Add cls argument to _timedelta_from_value_and_reso * Add test * Add fix to changelog Co-authored-by: krasch <dev@krasch.io>
1 parent b01cc53 commit 606499d

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
lines changed

doc/source/whatsnew/v1.5.2.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Fixed regressions
2020
from being passed using the ``colormap`` argument if Matplotlib 3.6+ is used (:issue:`49374`)
2121
- Fixed regression in :func:`date_range` returning an invalid set of periods for ``CustomBusinessDay`` frequency and ``start`` date with timezone (:issue:`49441`)
2222
- Fixed performance regression in groupby operations (:issue:`49676`)
23-
-
23+
- Fixed regression in :class:`Timedelta` constructor returning object of wrong type when subclassing ``Timedelta`` (:issue:`49579`)
2424

2525
.. ---------------------------------------------------------------------------
2626
.. _whatsnew_152.bug_fixes:

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def ints_to_pytimedelta(ndarray m8values, box=False):
189189
res_val = <object>NaT
190190
else:
191191
if box:
192-
res_val = _timedelta_from_value_and_reso(value, reso=reso)
192+
res_val = _timedelta_from_value_and_reso(Timedelta, value, reso=reso)
193193
elif reso == NPY_DATETIMEUNIT.NPY_FR_ns:
194194
res_val = timedelta(microseconds=int(value) / 1000)
195195
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
@@ -741,7 +741,7 @@ cdef bint _validate_ops_compat(other):
741741
def _op_unary_method(func, name):
742742
def f(self):
743743
new_value = func(self.value)
744-
return _timedelta_from_value_and_reso(new_value, self._creso)
744+
return _timedelta_from_value_and_reso(Timedelta, new_value, self._creso)
745745
f.__name__ = name
746746
return f
747747

@@ -804,7 +804,7 @@ def _binary_op_method_timedeltalike(op, name):
804804
# TODO: more generally could do an overflowcheck in op?
805805
return NaT
806806

807-
return _timedelta_from_value_and_reso(res, reso=self._creso)
807+
return _timedelta_from_value_and_reso(Timedelta, res, reso=self._creso)
808808

809809
f.__name__ = name
810810
return f
@@ -935,10 +935,10 @@ cdef _to_py_int_float(v):
935935

936936

937937
def _timedelta_unpickle(value, reso):
938-
return _timedelta_from_value_and_reso(value, reso)
938+
return _timedelta_from_value_and_reso(Timedelta, value, reso)
939939

940940

941-
cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
941+
cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso):
942942
# Could make this a classmethod if/when cython supports cdef classmethods
943943
cdef:
944944
_Timedelta td_base
@@ -949,13 +949,13 @@ cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
949949
# We pass 0 instead, and override seconds, microseconds, days.
950950
# In principle we could pass 0 for ns and us too.
951951
if reso == NPY_FR_ns:
952-
td_base = _Timedelta.__new__(Timedelta, microseconds=int(value) // 1000)
952+
td_base = _Timedelta.__new__(cls, microseconds=int(value) // 1000)
953953
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
954-
td_base = _Timedelta.__new__(Timedelta, microseconds=int(value))
954+
td_base = _Timedelta.__new__(cls, microseconds=int(value))
955955
elif reso == NPY_DATETIMEUNIT.NPY_FR_ms:
956-
td_base = _Timedelta.__new__(Timedelta, milliseconds=0)
956+
td_base = _Timedelta.__new__(cls, milliseconds=0)
957957
elif reso == NPY_DATETIMEUNIT.NPY_FR_s:
958-
td_base = _Timedelta.__new__(Timedelta, seconds=0)
958+
td_base = _Timedelta.__new__(cls, seconds=0)
959959
# Other resolutions are disabled but could potentially be implemented here:
960960
# elif reso == NPY_DATETIMEUNIT.NPY_FR_m:
961961
# td_base = _Timedelta.__new__(Timedelta, minutes=int(value))
@@ -1528,7 +1528,7 @@ cdef class _Timedelta(timedelta):
15281528
@classmethod
15291529
def _from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso):
15301530
# exposing as classmethod for testing
1531-
return _timedelta_from_value_and_reso(value, reso)
1531+
return _timedelta_from_value_and_reso(cls, value, reso)
15321532

15331533
def as_unit(self, str unit, bint round_ok=True):
15341534
"""
@@ -1763,7 +1763,7 @@ class Timedelta(_Timedelta):
17631763
if value == NPY_NAT:
17641764
return NaT
17651765

1766-
return _timedelta_from_value_and_reso(value, NPY_FR_ns)
1766+
return _timedelta_from_value_and_reso(cls, value, NPY_FR_ns)
17671767

17681768
def __setstate__(self, state):
17691769
if len(state) == 1:
@@ -1855,6 +1855,7 @@ class Timedelta(_Timedelta):
18551855
return NaT
18561856

18571857
return _timedelta_from_value_and_reso(
1858+
Timedelta,
18581859
<int64_t>(other * self.value),
18591860
reso=self._creso,
18601861
)

pandas/tests/scalar/timedelta/test_constructors.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,12 @@ def test_timedelta_new_npnat():
503503
# GH#48898
504504
nat = np.timedelta64("NaT", "h")
505505
assert Timedelta(nat) is NaT
506+
507+
508+
def test_subclass_respected():
509+
# GH#49579
510+
class MyCustomTimedelta(Timedelta):
511+
pass
512+
513+
td = MyCustomTimedelta("1 minute")
514+
assert isinstance(td, MyCustomTimedelta)

0 commit comments

Comments
 (0)