Skip to content

Commit 98c6139

Browse files
phoflkraschkrasch
authored
Backport PR #49579 on Branch 1.5.x (BUG: Behaviour change in 1.5.0 when using Timedelta as Enum data type) (#49787)
BUG: Behaviour change in 1.5.0 when using Timedelta as Enum data type (#49579) Co-authored-by: krasch <dev@krasch.io> (cherry picked from commit 606499d) Co-authored-by: krasch <krasch@users.noreply.github.com>
1 parent 9196f8d commit 98c6139

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
lines changed

Diff for: doc/source/whatsnew/v1.5.2.rst

+1-1
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:

Diff for: pandas/_libs/tslibs/timedeltas.pyx

+12-11
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def ints_to_pytimedelta(ndarray m8values, box=False):
188188
res_val = <object>NaT
189189
else:
190190
if box:
191-
res_val = _timedelta_from_value_and_reso(value, reso=reso)
191+
res_val = _timedelta_from_value_and_reso(Timedelta, value, reso=reso)
192192
elif reso == NPY_DATETIMEUNIT.NPY_FR_ns:
193193
res_val = timedelta(microseconds=int(value) / 1000)
194194
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
@@ -737,7 +737,7 @@ cdef bint _validate_ops_compat(other):
737737
def _op_unary_method(func, name):
738738
def f(self):
739739
new_value = func(self.value)
740-
return _timedelta_from_value_and_reso(new_value, self._reso)
740+
return _timedelta_from_value_and_reso(Timedelta, new_value, self._reso)
741741
f.__name__ = name
742742
return f
743743

@@ -807,7 +807,7 @@ def _binary_op_method_timedeltalike(op, name):
807807
# TODO: more generally could do an overflowcheck in op?
808808
return NaT
809809

810-
return _timedelta_from_value_and_reso(res, reso=self._reso)
810+
return _timedelta_from_value_and_reso(Timedelta, res, reso=self._reso)
811811

812812
f.__name__ = name
813813
return f
@@ -938,10 +938,10 @@ cdef _to_py_int_float(v):
938938

939939

940940
def _timedelta_unpickle(value, reso):
941-
return _timedelta_from_value_and_reso(value, reso)
941+
return _timedelta_from_value_and_reso(Timedelta, value, reso)
942942

943943

944-
cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
944+
cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso):
945945
# Could make this a classmethod if/when cython supports cdef classmethods
946946
cdef:
947947
_Timedelta td_base
@@ -951,13 +951,13 @@ cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
951951
# We pass 0 instead, and override seconds, microseconds, days.
952952
# In principle we could pass 0 for ns and us too.
953953
if reso == NPY_FR_ns:
954-
td_base = _Timedelta.__new__(Timedelta, microseconds=int(value) // 1000)
954+
td_base = _Timedelta.__new__(cls, microseconds=int(value) // 1000)
955955
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
956-
td_base = _Timedelta.__new__(Timedelta, microseconds=int(value))
956+
td_base = _Timedelta.__new__(cls, microseconds=int(value))
957957
elif reso == NPY_DATETIMEUNIT.NPY_FR_ms:
958-
td_base = _Timedelta.__new__(Timedelta, milliseconds=0)
958+
td_base = _Timedelta.__new__(cls, milliseconds=0)
959959
elif reso == NPY_DATETIMEUNIT.NPY_FR_s:
960-
td_base = _Timedelta.__new__(Timedelta, seconds=0)
960+
td_base = _Timedelta.__new__(cls, seconds=0)
961961
# Other resolutions are disabled but could potentially be implemented here:
962962
# elif reso == NPY_DATETIMEUNIT.NPY_FR_m:
963963
# td_base = _Timedelta.__new__(Timedelta, minutes=int(value))
@@ -1532,7 +1532,7 @@ cdef class _Timedelta(timedelta):
15321532
@classmethod
15331533
def _from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso):
15341534
# exposing as classmethod for testing
1535-
return _timedelta_from_value_and_reso(value, reso)
1535+
return _timedelta_from_value_and_reso(cls, value, reso)
15361536

15371537
def _as_unit(self, str unit, bint round_ok=True):
15381538
dtype = np.dtype(f"m8[{unit}]")
@@ -1708,7 +1708,7 @@ class Timedelta(_Timedelta):
17081708
if value == NPY_NAT:
17091709
return NaT
17101710

1711-
return _timedelta_from_value_and_reso(value, NPY_FR_ns)
1711+
return _timedelta_from_value_and_reso(cls, value, NPY_FR_ns)
17121712

17131713
def __setstate__(self, state):
17141714
if len(state) == 1:
@@ -1800,6 +1800,7 @@ class Timedelta(_Timedelta):
18001800
return NaT
18011801

18021802
return _timedelta_from_value_and_reso(
1803+
Timedelta,
18031804
<int64_t>(other * self.value),
18041805
reso=self._reso,
18051806
)

Diff for: pandas/tests/scalar/timedelta/test_constructors.py

+9
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,12 @@ def test_string_without_numbers(value):
402402
)
403403
with pytest.raises(ValueError, match=msg):
404404
Timedelta(value)
405+
406+
407+
def test_subclass_respected():
408+
# GH#49579
409+
class MyCustomTimedelta(Timedelta):
410+
pass
411+
412+
td = MyCustomTimedelta("1 minute")
413+
assert isinstance(td, MyCustomTimedelta)

0 commit comments

Comments
 (0)