Skip to content

Commit

Permalink
REF: de-duplicate some Timedelta helpers (#55501)
Browse files Browse the repository at this point in the history
* REF: implement disallow_ambiguous_unit

* REF: implement get_unit_for_round
  • Loading branch information
jbrockmendel authored Oct 13, 2023
1 parent 2f3b0ed commit b5b8be0
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 29 deletions.
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/timedeltas.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from numpy cimport int64_t
from .np_datetime cimport NPY_DATETIMEUNIT


cpdef int64_t get_unit_for_round(freq, NPY_DATETIMEUNIT creso) except? -1
# Exposed for tslib, not intended for outside use.
cpdef int64_t delta_to_nanoseconds(
delta, NPY_DATETIMEUNIT reso=*, bint round_ok=*
Expand Down
2 changes: 2 additions & 0 deletions pandas/_libs/tslibs/timedeltas.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ UnitChoices: TypeAlias = Literal[
]
_S = TypeVar("_S", bound=timedelta)

def get_unit_for_round(freq, creso: int) -> int: ...
def disallow_ambiguous_unit(unit: str | None) -> None: ...
def ints_to_pytimedelta(
arr: npt.NDArray[np.timedelta64],
box: bool = ...,
Expand Down
27 changes: 18 additions & 9 deletions pandas/_libs/tslibs/timedeltas.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,14 @@ def _binary_op_method_timedeltalike(op, name):
# ----------------------------------------------------------------------
# Timedelta Construction

cpdef disallow_ambiguous_unit(unit):
if unit in {"Y", "y", "M"}:
raise ValueError(
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
"represent unambiguous timedelta values durations."
)


cdef int64_t parse_iso_format_string(str ts) except? -1:
"""
Extracts and cleanses the appropriate values from a match object with
Expand Down Expand Up @@ -1815,11 +1823,7 @@ class Timedelta(_Timedelta):
)
raise OutOfBoundsTimedelta(msg) from err

if unit in {"Y", "y", "M"}:
raise ValueError(
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
"represent unambiguous timedelta values durations."
)
disallow_ambiguous_unit(unit)

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

from pandas._libs.tslibs.offsets import to_offset

to_offset(freq).nanos # raises on non-fixed freq
unit = delta_to_nanoseconds(to_offset(freq), self._creso)
unit = get_unit_for_round(freq, self._creso)

arr = np.array([self._value], dtype="i8")
try:
Expand Down Expand Up @@ -2292,3 +2293,11 @@ cdef bint _should_cast_to_timedelta(object obj):
return (
is_any_td_scalar(obj) or obj is None or obj is NaT or isinstance(obj, str)
)


cpdef int64_t get_unit_for_round(freq, NPY_DATETIMEUNIT creso) except? -1:
from pandas._libs.tslibs.offsets import to_offset

freq = to_offset(freq)
freq.nanos # raises on non-fixed freq
return delta_to_nanoseconds(freq, creso)
7 changes: 2 additions & 5 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ from pandas._libs.tslibs.np_datetime import (
from pandas._libs.tslibs.offsets cimport to_offset
from pandas._libs.tslibs.timedeltas cimport (
_Timedelta,
delta_to_nanoseconds,
get_unit_for_round,
is_any_td_scalar,
)

Expand Down Expand Up @@ -1896,17 +1896,14 @@ class Timestamp(_Timestamp):
int64_t nanos

freq = to_offset(freq, is_period=False)
freq.nanos # raises on non-fixed freq
nanos = delta_to_nanoseconds(freq, self._creso)
nanos = get_unit_for_round(freq, self._creso)
if nanos == 0:
if freq.nanos == 0:
raise ValueError("Division by zero in rounding")

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

# TODO: problem if nanos==0

if self.tz is not None:
value = self.tz_localize(None)._value
else:
Expand Down
6 changes: 2 additions & 4 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
Timedelta,
Timestamp,
astype_overflowsafe,
delta_to_nanoseconds,
get_unit_from_dtype,
iNaT,
ints_to_pydatetime,
Expand All @@ -49,6 +48,7 @@
round_nsint64,
)
from pandas._libs.tslibs.np_datetime import compare_mismatched_resolutions
from pandas._libs.tslibs.timedeltas import get_unit_for_round
from pandas._libs.tslibs.timestamps import integer_op_not_supported
from pandas._typing import (
ArrayLike,
Expand Down Expand Up @@ -2129,9 +2129,7 @@ def _round(self, freq, mode, ambiguous, nonexistent):

values = self.view("i8")
values = cast(np.ndarray, values)
offset = to_offset(freq)
offset.nanos # raises on non-fixed frequencies
nanos = delta_to_nanoseconds(offset, self._creso)
nanos = get_unit_for_round(freq, self._creso)
if nanos == 0:
# GH 52761
return self.copy()
Expand Down
7 changes: 2 additions & 5 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Timedelta,
to_offset,
)
from pandas._libs.tslibs.timedeltas import disallow_ambiguous_unit
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import (
Expand Down Expand Up @@ -170,11 +171,7 @@ def __new__(
if is_scalar(data):
cls._raise_scalar_data_error(data)

if unit in {"Y", "y", "M"}:
raise ValueError(
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
"represent unambiguous timedelta values durations."
)
disallow_ambiguous_unit(unit)
if dtype is not None:
dtype = pandas_dtype(dtype)

Expand Down
8 changes: 2 additions & 6 deletions pandas/core/tools/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from pandas._libs.tslibs.timedeltas import (
Timedelta,
disallow_ambiguous_unit,
parse_timedelta_unit,
)

Expand Down Expand Up @@ -178,16 +179,11 @@ def to_timedelta(
"""
if unit is not None:
unit = parse_timedelta_unit(unit)
disallow_ambiguous_unit(unit)

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

if unit in {"Y", "y", "M"}:
raise ValueError(
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
"represent unambiguous timedelta values durations."
)

if arg is None:
return arg
elif isinstance(arg, ABCSeries):
Expand Down

0 comments on commit b5b8be0

Please sign in to comment.