Skip to content
Merged
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ Timedelta
- Bug in :class:`TimedeltaIndex`, :class:`Series`, and :class:`DataFrame` floor-division with ``timedelta64`` dtypes and ``NaT`` in the denominator (:issue:`35529`)
- Bug in parsing of ISO 8601 durations in :class:`Timedelta`, :meth:`pd.to_datetime` (:issue:`37159`, fixes :issue:`29773` and :issue:`36204`)
- Bug in :func:`to_timedelta` with a read-only array incorrectly raising (:issue:`34857`)
- Bug in :class:`Timedelta` incorrectly truncating to sub-second portion of a string input when it has precision higher than nanoseconds (:issue:`36738`)

Timezones
^^^^^^^^^
Expand Down
9 changes: 7 additions & 2 deletions pandas/_libs/tslibs/timedeltas.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,11 @@ cdef inline int64_t parse_timedelta_string(str ts) except? -1:
m = 10**(3 -len(frac)) * 1000 * 1000
elif len(frac) > 3 and len(frac) <= 6:
m = 10**(6 -len(frac)) * 1000
else:
elif len(frac) > 6 and len(frac) <= 9:
m = 10**(9 -len(frac))

else:
m = 1
frac = frac[:9]
r = <int64_t>int(''.join(frac)) * m
result += timedelta_as_neg(r, neg)

Expand Down Expand Up @@ -1143,6 +1145,9 @@ class Timedelta(_Timedelta):
Notes
-----
The ``.value`` attribute is always in ns.

If the precision is higher than nanoseconds, the precision of the duration is
truncated to nanoseconds.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarify this is for a string input

do additional digits round or just drop (and if just drop, are we sure thats the desired behavior?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropd additional digits. Was not sure either so I started with the easier implementation
#36771 (comment)

"""

def __new__(cls, object value=_no_input, unit=None, **kwargs):
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/tools/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ def to_timedelta(arg, unit=None, errors="raise"):
to_datetime : Convert argument to datetime.
convert_dtypes : Convert dtypes.

Notes
-----
If the precision is higher than nanoseconds, the precision of the duration is
truncated to nanoseconds for string inputs.

Examples
--------
Parsing a single string to a Timedelta:
Expand Down
17 changes: 17 additions & 0 deletions pandas/tests/tools/test_to_timedelta.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,20 @@ def test_to_timedelta_nullable_int64_dtype(self):
result = to_timedelta(Series([1, None], dtype="Int64"), unit="days")

tm.assert_series_equal(result, expected)

@pytest.mark.parametrize(
("input", "expected"),
[
("8:53:08.71800000001", "8:53:08.718"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a case with the trailing digit being 9 to clarify the truncating/rounding distinction

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

("8:53:08.718001", "8:53:08.718001"),
("8:53:08.7180000001", "8:53:08.7180000001"),
("-8:53:08.71800000001", "-8:53:08.718"),
("8:53:08.7180000089", "8:53:08.718000008"),
],
)
@pytest.mark.parametrize("func", [pd.Timedelta, pd.to_timedelta])
def test_to_timedelta_precision_over_nanos(self, input, expected, func):
# GH: 36738
expected = pd.Timedelta(expected)
result = func(input)
assert result == expected