Skip to content

Commit 6e59a2d

Browse files
committed
BUG: pydatetime + Timedelta[non-nano] incorrect results
1 parent 499c5d4 commit 6e59a2d

File tree

3 files changed

+32
-2
lines changed

3 files changed

+32
-2
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,7 @@ Timedelta
11311131
- Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`)
11321132
- Bug in :class:`Timedelta` constructor failing to raise when passed an invalid keyword (:issue:`53801`)
11331133
- Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`)
1134+
- Bug in adding or subtracting a :class:`Timedelta` object with non-nanosecond unit to a python ``datetime.datetime`` object giving incorrect results; this now works correctly for Timedeltas inside the ``datetime.timedelta`` implementation bounds (:issue:`53643`)
11341135
- Bug in multiplication operations with ``timedelta64`` dtype failing to raise ``TypeError`` when multiplying by ``bool`` objects or dtypes (:issue:`58054`)
11351136
- Bug in multiplication operations with ``timedelta64`` dtype incorrectly raising when multiplying by numpy-nullable dtypes or pyarrow integer dtypes (:issue:`58054`)
11361137

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,9 +1022,23 @@ cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso):
10221022
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:
10231023
td_base = _Timedelta.__new__(cls, microseconds=int(value))
10241024
elif reso == NPY_DATETIMEUNIT.NPY_FR_ms:
1025-
td_base = _Timedelta.__new__(cls, milliseconds=0)
1025+
if value > -86_399_999_913_600_000 and value < 86_400_000_000_000_000:
1026+
# i.e. we are in range for pytimedelta. By passing the
1027+
# 'correct' value here we can
1028+
# make pydatetime + Timedelta operations work correctly,
1029+
# xref GH#53643
1030+
td_base = _Timedelta.__new__(cls, milliseconds=value)
1031+
else:
1032+
td_base = _Timedelta.__new__(cls, milliseconds=0)
10261033
elif reso == NPY_DATETIMEUNIT.NPY_FR_s:
1027-
td_base = _Timedelta.__new__(cls, seconds=0)
1034+
if value > -86_399_999_913_600 and value < 86_400_000_000_000:
1035+
# i.e. we are in range for pytimedelta. By passing the
1036+
# 'correct' value here we can
1037+
# make pydatetime + Timedelta operations work correctly,
1038+
# xref GH#53643
1039+
td_base = _Timedelta.__new__(cls, seconds=value)
1040+
else:
1041+
td_base = _Timedelta.__new__(cls, seconds=0)
10281042
# Other resolutions are disabled but could potentially be implemented here:
10291043
# elif reso == NPY_DATETIMEUNIT.NPY_FR_m:
10301044
# td_base = _Timedelta.__new__(Timedelta, minutes=int(value))

pandas/tests/scalar/timedelta/test_arithmetic.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ class TestTimedeltaAdditionSubtraction:
3535
__sub__, __rsub__
3636
"""
3737

38+
def test_td_add_sub_pydatetime(self, unit):
39+
# GH#53643
40+
td = Timedelta(hours=23).as_unit(unit)
41+
dt = datetime(2016, 1, 1)
42+
43+
expected = datetime(2016, 1, 1, 23)
44+
result = dt + td
45+
assert result == expected
46+
result = td + dt
47+
assert result == expected
48+
49+
expected = datetime(2015, 12, 31, 1)
50+
result = dt - td
51+
assert result == expected
52+
3853
@pytest.mark.parametrize(
3954
"ten_seconds",
4055
[

0 commit comments

Comments
 (0)