Skip to content

Commit b5b8be0

Browse files
authored
REF: de-duplicate some Timedelta helpers (#55501)
* REF: implement disallow_ambiguous_unit * REF: implement get_unit_for_round
1 parent 2f3b0ed commit b5b8be0

File tree

7 files changed

+29
-29
lines changed

7 files changed

+29
-29
lines changed

pandas/_libs/tslibs/timedeltas.pxd

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from numpy cimport int64_t
44
from .np_datetime cimport NPY_DATETIMEUNIT
55

66

7+
cpdef int64_t get_unit_for_round(freq, NPY_DATETIMEUNIT creso) except? -1
78
# Exposed for tslib, not intended for outside use.
89
cpdef int64_t delta_to_nanoseconds(
910
delta, NPY_DATETIMEUNIT reso=*, bint round_ok=*

pandas/_libs/tslibs/timedeltas.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ UnitChoices: TypeAlias = Literal[
6868
]
6969
_S = TypeVar("_S", bound=timedelta)
7070

71+
def get_unit_for_round(freq, creso: int) -> int: ...
72+
def disallow_ambiguous_unit(unit: str | None) -> None: ...
7173
def ints_to_pytimedelta(
7274
arr: npt.NDArray[np.timedelta64],
7375
box: bool = ...,

pandas/_libs/tslibs/timedeltas.pyx

+18-9
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,14 @@ def _binary_op_method_timedeltalike(op, name):
827827
# ----------------------------------------------------------------------
828828
# Timedelta Construction
829829

830+
cpdef disallow_ambiguous_unit(unit):
831+
if unit in {"Y", "y", "M"}:
832+
raise ValueError(
833+
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
834+
"represent unambiguous timedelta values durations."
835+
)
836+
837+
830838
cdef int64_t parse_iso_format_string(str ts) except? -1:
831839
"""
832840
Extracts and cleanses the appropriate values from a match object with
@@ -1815,11 +1823,7 @@ class Timedelta(_Timedelta):
18151823
)
18161824
raise OutOfBoundsTimedelta(msg) from err
18171825

1818-
if unit in {"Y", "y", "M"}:
1819-
raise ValueError(
1820-
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
1821-
"represent unambiguous timedelta values durations."
1822-
)
1826+
disallow_ambiguous_unit(unit)
18231827

18241828
# GH 30543 if pd.Timedelta already passed, return it
18251829
# check that only value is passed
@@ -1932,10 +1936,7 @@ class Timedelta(_Timedelta):
19321936
int64_t result, unit
19331937
ndarray[int64_t] arr
19341938

1935-
from pandas._libs.tslibs.offsets import to_offset
1936-
1937-
to_offset(freq).nanos # raises on non-fixed freq
1938-
unit = delta_to_nanoseconds(to_offset(freq), self._creso)
1939+
unit = get_unit_for_round(freq, self._creso)
19391940

19401941
arr = np.array([self._value], dtype="i8")
19411942
try:
@@ -2292,3 +2293,11 @@ cdef bint _should_cast_to_timedelta(object obj):
22922293
return (
22932294
is_any_td_scalar(obj) or obj is None or obj is NaT or isinstance(obj, str)
22942295
)
2296+
2297+
2298+
cpdef int64_t get_unit_for_round(freq, NPY_DATETIMEUNIT creso) except? -1:
2299+
from pandas._libs.tslibs.offsets import to_offset
2300+
2301+
freq = to_offset(freq)
2302+
freq.nanos # raises on non-fixed freq
2303+
return delta_to_nanoseconds(freq, creso)

pandas/_libs/tslibs/timestamps.pyx

+2-5
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ from pandas._libs.tslibs.np_datetime import (
107107
from pandas._libs.tslibs.offsets cimport to_offset
108108
from pandas._libs.tslibs.timedeltas cimport (
109109
_Timedelta,
110-
delta_to_nanoseconds,
110+
get_unit_for_round,
111111
is_any_td_scalar,
112112
)
113113

@@ -1896,17 +1896,14 @@ class Timestamp(_Timestamp):
18961896
int64_t nanos
18971897

18981898
freq = to_offset(freq, is_period=False)
1899-
freq.nanos # raises on non-fixed freq
1900-
nanos = delta_to_nanoseconds(freq, self._creso)
1899+
nanos = get_unit_for_round(freq, self._creso)
19011900
if nanos == 0:
19021901
if freq.nanos == 0:
19031902
raise ValueError("Division by zero in rounding")
19041903

19051904
# e.g. self.unit == "s" and sub-second freq
19061905
return self
19071906

1908-
# TODO: problem if nanos==0
1909-
19101907
if self.tz is not None:
19111908
value = self.tz_localize(None)._value
19121909
else:

pandas/core/arrays/datetimelike.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
Timedelta,
3737
Timestamp,
3838
astype_overflowsafe,
39-
delta_to_nanoseconds,
4039
get_unit_from_dtype,
4140
iNaT,
4241
ints_to_pydatetime,
@@ -49,6 +48,7 @@
4948
round_nsint64,
5049
)
5150
from pandas._libs.tslibs.np_datetime import compare_mismatched_resolutions
51+
from pandas._libs.tslibs.timedeltas import get_unit_for_round
5252
from pandas._libs.tslibs.timestamps import integer_op_not_supported
5353
from pandas._typing import (
5454
ArrayLike,
@@ -2129,9 +2129,7 @@ def _round(self, freq, mode, ambiguous, nonexistent):
21292129

21302130
values = self.view("i8")
21312131
values = cast(np.ndarray, values)
2132-
offset = to_offset(freq)
2133-
offset.nanos # raises on non-fixed frequencies
2134-
nanos = delta_to_nanoseconds(offset, self._creso)
2132+
nanos = get_unit_for_round(freq, self._creso)
21352133
if nanos == 0:
21362134
# GH 52761
21372135
return self.copy()

pandas/core/indexes/timedeltas.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Timedelta,
1414
to_offset,
1515
)
16+
from pandas._libs.tslibs.timedeltas import disallow_ambiguous_unit
1617
from pandas.util._exceptions import find_stack_level
1718

1819
from pandas.core.dtypes.common import (
@@ -170,11 +171,7 @@ def __new__(
170171
if is_scalar(data):
171172
cls._raise_scalar_data_error(data)
172173

173-
if unit in {"Y", "y", "M"}:
174-
raise ValueError(
175-
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
176-
"represent unambiguous timedelta values durations."
177-
)
174+
disallow_ambiguous_unit(unit)
178175
if dtype is not None:
179176
dtype = pandas_dtype(dtype)
180177

pandas/core/tools/timedeltas.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from pandas._libs.tslibs.timedeltas import (
1919
Timedelta,
20+
disallow_ambiguous_unit,
2021
parse_timedelta_unit,
2122
)
2223

@@ -178,16 +179,11 @@ def to_timedelta(
178179
"""
179180
if unit is not None:
180181
unit = parse_timedelta_unit(unit)
182+
disallow_ambiguous_unit(unit)
181183

182184
if errors not in ("ignore", "raise", "coerce"):
183185
raise ValueError("errors must be one of 'ignore', 'raise', or 'coerce'.")
184186

185-
if unit in {"Y", "y", "M"}:
186-
raise ValueError(
187-
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
188-
"represent unambiguous timedelta values durations."
189-
)
190-
191187
if arg is None:
192188
return arg
193189
elif isinstance(arg, ABCSeries):

0 commit comments

Comments
 (0)