Skip to content

Commit 90c7817

Browse files
authored
ENH: TDA.total_seconds support non-nano (#47421)
* ENH: TDA.total_seconds support non-nano * fix doctest * mypy fixup
1 parent f81ac72 commit 90c7817

File tree

7 files changed

+34
-6
lines changed

7 files changed

+34
-6
lines changed

pandas/_libs/tslibs/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
"astype_overflowsafe",
3030
"get_unit_from_dtype",
3131
"periods_per_day",
32+
"periods_per_second",
3233
]
3334

3435
from pandas._libs.tslibs import dtypes
3536
from pandas._libs.tslibs.conversion import localize_pydatetime
3637
from pandas._libs.tslibs.dtypes import (
3738
Resolution,
3839
periods_per_day,
40+
periods_per_second,
3941
)
4042
from pandas._libs.tslibs.nattype import (
4143
NaT,

pandas/_libs/tslibs/dtypes.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ cdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit)
77
cdef NPY_DATETIMEUNIT abbrev_to_npy_unit(str abbrev)
88
cdef NPY_DATETIMEUNIT freq_group_code_to_npy_unit(int freq) nogil
99
cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=*) except? -1
10-
cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1
10+
cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1
1111
cdef int64_t get_conversion_factor(NPY_DATETIMEUNIT from_unit, NPY_DATETIMEUNIT to_unit) except? -1
1212

1313
cdef dict attrname_to_abbrevs

pandas/_libs/tslibs/dtypes.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ _attrname_to_abbrevs: dict[str, str]
66
_period_code_map: dict[str, int]
77

88
def periods_per_day(reso: int) -> int: ...
9+
def periods_per_second(reso: int) -> int: ...
910

1011
class PeriodDtypeBase:
1112
_dtype_code: int # PeriodDtypeCode

pandas/_libs/tslibs/dtypes.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=NPY_DATETIMEUNIT.NPY_FR_ns)
403403
return day_units
404404

405405

406-
cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1:
406+
cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1:
407407
if reso == NPY_DATETIMEUNIT.NPY_FR_ns:
408408
return 1_000_000_000
409409
elif reso == NPY_DATETIMEUNIT.NPY_FR_us:

pandas/core/arrays/timedeltas.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
Timedelta,
2222
astype_overflowsafe,
2323
iNaT,
24+
periods_per_second,
2425
to_offset,
2526
)
2627
from pandas._libs.tslibs.conversion import precision_from_unit
@@ -818,10 +819,11 @@ def total_seconds(self) -> npt.NDArray[np.float64]:
818819
dtype='timedelta64[ns]', freq=None)
819820
820821
>>> idx.total_seconds()
821-
Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0],
822+
Float64Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0],
822823
dtype='float64')
823824
"""
824-
return self._maybe_mask_results(1e-9 * self.asi8, fill_value=None)
825+
pps = periods_per_second(self._reso)
826+
return self._maybe_mask_results(self.asi8 / pps, fill_value=None)
825827

826828
def to_pytimedelta(self) -> npt.NDArray[np.object_]:
827829
"""
@@ -832,7 +834,7 @@ def to_pytimedelta(self) -> npt.NDArray[np.object_]:
832834
-------
833835
timedeltas : ndarray[object]
834836
"""
835-
return tslibs.ints_to_pytimedelta(self._ndarray)
837+
return ints_to_pytimedelta(self._ndarray)
836838

837839
days = _field_accessor("days", "days", "Number of days for each element.")
838840
seconds = _field_accessor(

pandas/tests/arrays/test_timedeltas.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_non_nano(self, unit, reso):
3333
assert tda[0]._reso == reso
3434

3535
@pytest.mark.parametrize("field", TimedeltaArray._field_ops)
36-
def test_fields(self, unit, reso, field):
36+
def test_fields(self, unit, field):
3737
arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]")
3838
tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype)
3939

@@ -44,6 +44,28 @@ def test_fields(self, unit, reso, field):
4444
expected = getattr(tda_nano, field)
4545
tm.assert_numpy_array_equal(result, expected)
4646

47+
def test_to_pytimedelta(self, unit):
48+
arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]")
49+
tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype)
50+
51+
as_nano = arr.astype("m8[ns]")
52+
tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype)
53+
54+
result = tda.to_pytimedelta()
55+
expected = tda_nano.to_pytimedelta()
56+
tm.assert_numpy_array_equal(result, expected)
57+
58+
def test_total_seconds(self, unit):
59+
arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]")
60+
tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype)
61+
62+
as_nano = arr.astype("m8[ns]")
63+
tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype)
64+
65+
result = tda.total_seconds()
66+
expected = tda_nano.total_seconds()
67+
tm.assert_numpy_array_equal(result, expected)
68+
4769

4870
class TestTimedeltaArray:
4971
@pytest.mark.parametrize("dtype", [int, np.int32, np.int64, "uint32", "uint64"])

pandas/tests/tslibs/test_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def test_namespace():
5454
"astype_overflowsafe",
5555
"get_unit_from_dtype",
5656
"periods_per_day",
57+
"periods_per_second",
5758
]
5859

5960
expected = set(submodules + api)

0 commit comments

Comments
 (0)