From d2b4bb6c9a3c335a6de947f51241b0532560c654 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 18 Dec 2025 01:59:44 -0800 Subject: [PATCH 1/2] BUG: pydatetime + Timedelta[non-nano] incorrect results (#63301) --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/_libs/tslibs/timedeltas.pyx | 18 ++++++++++++++++-- .../tests/scalar/timedelta/test_arithmetic.py | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index a1de10f61306a..dda20fe8aeb21 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -1160,6 +1160,7 @@ Timedelta - Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`) - Bug in :class:`Timedelta` constructor failing to raise when passed an invalid keyword (:issue:`53801`) - Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`) +- 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`) - Bug in multiplication operations with ``timedelta64`` dtype failing to raise ``TypeError`` when multiplying by ``bool`` objects or dtypes (:issue:`58054`) - Bug in multiplication operations with ``timedelta64`` dtype incorrectly raising when multiplying by numpy-nullable dtypes or pyarrow integer dtypes (:issue:`58054`) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 1e01ad9246aae..81e3bc6472cf2 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1022,9 +1022,23 @@ cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso): elif reso == NPY_DATETIMEUNIT.NPY_FR_us: td_base = _Timedelta.__new__(cls, microseconds=int(value)) elif reso == NPY_DATETIMEUNIT.NPY_FR_ms: - td_base = _Timedelta.__new__(cls, milliseconds=0) + if -86_399_999_913_600_000 <= value < 86_400_000_000_000_000: + # i.e. we are in range for pytimedelta. By passing the + # 'correct' value here we can + # make pydatetime + Timedelta operations work correctly, + # xref GH#53643 + td_base = _Timedelta.__new__(cls, milliseconds=value) + else: + td_base = _Timedelta.__new__(cls, milliseconds=0) elif reso == NPY_DATETIMEUNIT.NPY_FR_s: - td_base = _Timedelta.__new__(cls, seconds=0) + if -86_399_999_913_600 <= value < 86_400_000_000_000: + # i.e. we are in range for pytimedelta. By passing the + # 'correct' value here we can + # make pydatetime + Timedelta operations work correctly, + # xref GH#53643 + td_base = _Timedelta.__new__(cls, seconds=value) + else: + 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)) diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 20e46bbbe0803..d9eda82155b06 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -35,6 +35,21 @@ class TestTimedeltaAdditionSubtraction: __sub__, __rsub__ """ + def test_td_add_sub_pydatetime(self, unit): + # GH#53643 + td = Timedelta(hours=23).as_unit(unit) + dt = datetime(2016, 1, 1) + + expected = datetime(2016, 1, 1, 23) + result = dt + td + assert result == expected + result = td + dt + assert result == expected + + expected = datetime(2015, 12, 31, 1) + result = dt - td + assert result == expected + @pytest.mark.parametrize( "ten_seconds", [ From 0e978b68ba68e0f3b1f8b9f6b5a38072948638f0 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Dec 2025 05:56:43 -0500 Subject: [PATCH 2/2] BLD: newer versions of meson are pickier about types (#63406) --- pandas/_libs/meson.build | 2 +- pandas/_libs/tslibs/meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/meson.build b/pandas/_libs/meson.build index 33fc65e5034d0..d84c605911f04 100644 --- a/pandas/_libs/meson.build +++ b/pandas/_libs/meson.build @@ -160,7 +160,7 @@ foreach ext_name, ext_dict : libs_sources ext_dict.get('sources'), cython_args: cython_args, include_directories: [inc_np, inc_pd], - dependencies: ext_dict.get('deps', ''), + dependencies: ext_dict.get('deps', []), subdir: 'pandas/_libs', install: true, ) diff --git a/pandas/_libs/tslibs/meson.build b/pandas/_libs/tslibs/meson.build index ac43dc7db5fb7..f76f04dbf75fe 100644 --- a/pandas/_libs/tslibs/meson.build +++ b/pandas/_libs/tslibs/meson.build @@ -40,7 +40,7 @@ foreach ext_name, ext_dict : tslibs_sources ext_dict.get('sources'), cython_args: cython_args, include_directories: [inc_np, inc_pd], - dependencies: ext_dict.get('deps', ''), + dependencies: ext_dict.get('deps', []), subdir: 'pandas/_libs/tslibs', install: true, )