diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 2c77b87f8efb6..ec61b22eab318 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -714,6 +714,8 @@ Timedelta ^^^^^^^^^ - Bug in division of all-``NaT`` :class:`TimeDeltaIndex`, :class:`Series` or :class:`DataFrame` column with object-dtype arraylike of numbers failing to infer the result as timedelta64-dtype (:issue:`39750`) - Bug in floor division of ``timedelta64[ns]`` data with a scalar returning garbage values (:issue:`44466`) +- Bug in :class:`Timedelta` now properly taking into account any nanoseconds contribution of any kwarg (:issue:`43764`) +- Timezones ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 2ac635ad4fb9c..a908eefd41768 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -180,7 +180,7 @@ cpdef int64_t delta_to_nanoseconds(delta) except? -1: if PyDelta_Check(delta): try: return ( - delta.days * 24 * 60 * 60 * 1_000_000 + delta.days * 24 * 3600 * 1_000_000 + delta.seconds * 1_000_000 + delta.microseconds ) * 1000 @@ -1257,6 +1257,9 @@ class Timedelta(_Timedelta): truncated to nanoseconds. """ + _req_any_kwargs_new = {"weeks", "days", "hours", "minutes", "seconds", + "milliseconds", "microseconds", "nanoseconds"} + def __new__(cls, object value=_no_input, unit=None, **kwargs): cdef _Timedelta td_base @@ -1267,12 +1270,7 @@ class Timedelta(_Timedelta): "(days,seconds....)") kwargs = {key: _to_py_int_float(kwargs[key]) for key in kwargs} - - nano = convert_to_timedelta64(kwargs.pop('nanoseconds', 0), 'ns') - try: - value = nano + convert_to_timedelta64(timedelta(**kwargs), - 'ns') - except TypeError as e: + if not cls._req_any_kwargs_new.intersection(kwargs): raise ValueError( "cannot construct a Timedelta from the passed arguments, " "allowed keywords are " @@ -1280,6 +1278,27 @@ class Timedelta(_Timedelta): "milliseconds, microseconds, nanoseconds]" ) + # GH43764, convert any input to nanoseconds first and then + # create the timestamp. This ensures that any potential + # nanosecond contributions from kwargs parsed as floats + # are taken into consideration. + seconds = int(( + ( + (kwargs.get('days', 0) + kwargs.get('weeks', 0) * 7) * 24 + + kwargs.get('hours', 0) + ) * 3600 + + kwargs.get('minutes', 0) * 60 + + kwargs.get('seconds', 0) + ) * 1_000_000_000 + ) + + value = np.timedelta64( + int(kwargs.get('nanoseconds', 0)) + + int(kwargs.get('microseconds', 0) * 1_000) + + int(kwargs.get('milliseconds', 0) * 1_000_000) + + seconds + ) + if unit in {'Y', 'y', 'M'}: raise ValueError( "Units 'M', 'Y', and 'y' are no longer supported, as they do not " diff --git a/pandas/tests/tslibs/test_timedeltas.py b/pandas/tests/tslibs/test_timedeltas.py index 25450bd64a298..c72d279580cca 100644 --- a/pandas/tests/tslibs/test_timedeltas.py +++ b/pandas/tests/tslibs/test_timedeltas.py @@ -15,6 +15,15 @@ (np.timedelta64(14, "D"), 14 * 24 * 3600 * 1e9), (Timedelta(minutes=-7), -7 * 60 * 1e9), (Timedelta(minutes=-7).to_pytimedelta(), -7 * 60 * 1e9), + (Timedelta(seconds=1234e-9), 1234), # GH43764, GH40946 + ( + Timedelta(seconds=1e-9, milliseconds=1e-5, microseconds=1e-1), + 111, + ), # GH43764 + ( + Timedelta(days=1, seconds=1e-9, milliseconds=1e-5, microseconds=1e-1), + 24 * 3600e9 + 111, + ), # GH43764 (offsets.Nano(125), 125), (1, 1), (np.int64(2), 2),