Skip to content

Commit

Permalink
BUG: TimedeltaIndex.__repr__ with non-nano and round values
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed Oct 4, 2023
1 parent a5c7946 commit d0067ba
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 11 deletions.
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v2.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ Datetimelike

Timedelta
^^^^^^^^^
-
- Bug in rendering (``__repr__``) of :class:`TimedeltaIndex` and :class:`Series` with timedelta64 values with non-nanosecond resolution entries that are all multiples of 24 hours failing to use the compact representation used in the nanosecond cases (:issue:`??`)
-

Timezones
Expand Down
26 changes: 26 additions & 0 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
iNaT,
ints_to_pydatetime,
ints_to_pytimedelta,
periods_per_day,
to_offset,
)
from pandas._libs.tslibs.fields import (
Expand Down Expand Up @@ -2312,6 +2313,31 @@ def interpolate(
return self
return type(self)._simple_new(out_data, dtype=self.dtype)

# --------------------------------------------------------------
# Unsorted

@property
def _is_dates_only(self) -> bool:
"""
Check if we are round times at midnight (and no timezone), which will
be given a more compact __repr__ than other cases. For TimedeltaArray
we are checking for multiples of 24H.
"""
if not lib.is_np_dtype(self.dtype):
# i.e. we have a timezone
return False

values_int = self.asi8
consider_values = values_int != iNaT
dtype = cast(np.dtype, self.dtype) # since we checked tz above
reso = get_unit_from_dtype(dtype)
ppd = periods_per_day(reso)

# TODO: can we reuse is_date_array_normalized? would need a skipna kwd
# (first attempt at this was less performant than this implementation)
even_days = np.logical_and(consider_values, values_int % ppd != 0).sum() == 0
return even_days


# -------------------------------------------------------------------
# Shared Constructor Helpers
Expand Down
11 changes: 1 addition & 10 deletions pandas/io/formats/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@
SequenceNotStr,
StorageOptions,
WriteBuffer,
npt,
)

from pandas import (
Expand Down Expand Up @@ -1814,15 +1813,7 @@ def get_format_timedelta64(
If box, then show the return in quotes
"""
values_int = values.view(np.int64)
values_int = cast("npt.NDArray[np.int64]", values_int)

consider_values = values_int != iNaT

one_day_nanos = 86400 * 10**9
not_midnight = values_int % one_day_nanos != 0
both = np.logical_and(consider_values, not_midnight)
even_days = both.sum() == 0
even_days = values._is_dates_only

if even_days:
format = None
Expand Down
11 changes: 11 additions & 0 deletions pandas/tests/indexes/timedeltas/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@


class TestTimedeltaIndexRendering:
def test_repr_round_days_non_nano(self):
# we should get "1 days", not "1 days 00:00:00" with non-nano
tdi = TimedeltaIndex(["1 days"], freq="D").as_unit("s")
result = repr(tdi)
expected = "TimedeltaIndex(['1 days'], dtype='timedelta64[s]', freq='D')"
assert result == expected

result2 = repr(Series(tdi))
expected2 = "0 1 days\ndtype: timedelta64[s]"
assert result2 == expected2

@pytest.mark.parametrize("method", ["__repr__", "__str__"])
def test_representation(self, method):
idx1 = TimedeltaIndex([], freq="D")
Expand Down

0 comments on commit d0067ba

Please sign in to comment.