diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 4c3e53ddcfa26..f500613fc363e 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -225,7 +225,8 @@ Other enhancements - :class:`ExtensionDtype` and :class:`ExtensionArray` are now (de)serialized when exporting a :class:`DataFrame` with :meth:`DataFrame.to_json` using ``orient='table'`` (:issue:`20612`, :issue:`44705`). - Add support for `Zstandard `_ compression to :meth:`DataFrame.to_pickle`/:meth:`read_pickle` and friends (:issue:`43925`) - :meth:`DataFrame.to_sql` now returns an ``int`` of the number of written rows (:issue:`23998`) - +- :meth:`Timedelta.total_seconds()` now properly taking into account any nanoseconds contribution (:issue:`40946`) +- .. --------------------------------------------------------------------------- diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index a6dc8cc16b229..ba370940cd957 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -504,7 +504,7 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, tzinfo tz, if obj.tzinfo is not None and not is_utc(obj.tzinfo): offset = get_utcoffset(obj.tzinfo, ts) - obj.value -= int(offset.total_seconds() * 1e9) + obj.value -= int(offset.total_seconds() * 1_000_000_000) if isinstance(ts, ABCTimestamp): obj.value += ts.nanosecond diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index 6ca43aebed89c..f8f46d19968af 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -442,7 +442,31 @@ class NaTType(_NaT): Monday == 1 ... Sunday == 7. """, ) - total_seconds = _make_nan_func("total_seconds", timedelta.total_seconds.__doc__) + total_seconds = _make_nan_func( + "total_seconds", + """ + Total seconds in the duration with default us precision + (for compatibility with `datetime.timedelta`). + + Parameters + ---------- + ns_precision : bool, default False + Return the duration with ns precision. + + Examples + -------- + >>> td = pd.Timedelta(days=6, minutes=50, seconds=3, + ... milliseconds=10, microseconds=10, nanoseconds=12) + >>> td + Timedelta('6 days 00:50:03.010010012') + + >>> td.total_seconds() + 521403.01001 + + >>> td.total_seconds(ns_precision=True) + 521403.010010012 + """ + ) month_name = _make_nan_func( "month_name", """ diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index a908eefd41768..b88344481bfe4 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1539,6 +1539,34 @@ class Timedelta(_Timedelta): div = other // self return div, other - div * self + # GH45129 + def total_seconds(self, *, ns_precision=False) -> float: + """ + Total seconds in the duration with default us precision + (for compatibility with `datetime.timedelta`). + + Parameters + ---------- + ns_precision : bool, default False + Return the duration with ns precision. + + Examples + -------- + >>> td = pd.Timedelta(days=6, minutes=50, seconds=3, + ... milliseconds=10, microseconds=10, nanoseconds=12) + >>> td + Timedelta('6 days 00:50:03.010010012') + + >>> td.total_seconds() + 521403.01001 + + >>> td.total_seconds(ns_precision=True) + 521403.010010012 + """ + if ns_precision: + return self.value / 1_000_000_000 + return super().total_seconds() + cdef bint is_any_td_scalar(object obj): """ diff --git a/pandas/tests/tslibs/test_timedeltas.py b/pandas/tests/tslibs/test_timedeltas.py index c72d279580cca..7107d26866678 100644 --- a/pandas/tests/tslibs/test_timedeltas.py +++ b/pandas/tests/tslibs/test_timedeltas.py @@ -46,3 +46,24 @@ def test_huge_nanoseconds_overflow(): # GH 32402 assert delta_to_nanoseconds(Timedelta(1e10)) == 1e10 assert delta_to_nanoseconds(Timedelta(nanoseconds=1e10)) == 1e10 + + +# GH40946 +@pytest.mark.parametrize( + "obj, expected_ns, expected_us", + [ + (Timedelta("1us"), 1e-6, 1e-6), + (Timedelta("500ns"), 5e-7, 0.0), + (Timedelta(nanoseconds=500), 5e-7, 0.0), + (Timedelta(seconds=1, nanoseconds=500), 1 + 5e-7, 1.0), + (Timedelta(seconds=1e-9, milliseconds=1e-5, microseconds=1e-1), 111e-9, 0.0), + ( + Timedelta(days=1, seconds=1e-9, milliseconds=1e-5, microseconds=1e-1), + 24 * 3600 + 111e-9, + 24 * 3600, + ), + ], +) +def test_total_seconds(obj: Timedelta, expected_ns, expected_us): + assert obj.total_seconds() == expected_us + assert obj.total_seconds(ns_precision=True) == expected_ns