Skip to content

Commit 64837c4

Browse files
wip: lots more tests, consolidate overflow checking
1 parent 4247286 commit 64837c4

File tree

3 files changed

+269
-283
lines changed

3 files changed

+269
-283
lines changed

pandas/_libs/tslibs/timedeltas.pyx

+42-43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import collections
2+
import operator
23
import warnings
34

45
cimport cython
@@ -215,20 +216,16 @@ cpdef int64_t delta_to_nanoseconds(delta) except? -1:
215216
return get_timedelta64_value(ensure_td64ns(delta))
216217

217218
if PyDelta_Check(delta):
218-
try:
219-
return (
220-
delta.days * 24 * 3600 * 1_000_000
221-
+ delta.seconds * 1_000_000
222-
+ delta.microseconds
223-
) * 1000
224-
except OverflowError as err:
225-
msg = f"{delta} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
226-
raise OutOfBoundsTimedelta(msg) from err
219+
microseconds = (
220+
delta.days * 24 * 3600 * 1_000_000
221+
+ delta.seconds * 1_000_000
222+
+ delta.microseconds
223+
)
224+
return calc_int_int(operator.mul, microseconds, 1000)
227225

228226
raise TypeError(type(delta))
229227

230228

231-
@cython.overflowcheck(True)
232229
cdef object ensure_td64ns(object ts):
233230
"""
234231
Overflow-safe implementation of td64.astype("m8[ns]")
@@ -247,25 +244,14 @@ cdef object ensure_td64ns(object ts):
247244
str unitstr
248245

249246
td64_unit = get_datetime64_unit(ts)
250-
if (
251-
td64_unit != NPY_DATETIMEUNIT.NPY_FR_ns
252-
and td64_unit != NPY_DATETIMEUNIT.NPY_FR_GENERIC
253-
):
254-
unitstr = npy_unit_to_abbrev(td64_unit)
255-
256-
td64_value = get_timedelta64_value(ts)
257-
258-
mult = precision_from_unit(unitstr)[0]
259-
try:
260-
# NB: cython#1381 this cannot be *=
261-
td64_value = td64_value * mult
262-
except OverflowError as err:
263-
msg = f"{ts} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
264-
raise OutOfBoundsTimedelta(msg) from err
247+
if td64_unit in (NPY_DATETIMEUNIT.NPY_FR_ns, NPY_DATETIMEUNIT.NPY_FR_GENERIC):
248+
return ts
265249

266-
return np.timedelta64(td64_value, "ns")
250+
unitstr = npy_unit_to_abbrev(td64_unit)
251+
mult = precision_from_unit(unitstr)[0]
252+
td64_value = calc_int_int(operator.mul, get_timedelta64_value(ts), mult)
267253

268-
return ts
254+
return np.timedelta64(td64_value, "ns")
269255

270256

271257
cdef convert_to_timedelta64(object ts, str unit):
@@ -686,13 +672,27 @@ def _op_unary_method(func, name):
686672
return f
687673

688674

689-
cpdef int64_t calculate(object op, object a, object b) except? -1:
675+
cpdef int64_t calc_int_int(object op, object a, object b) except? -1:
690676
"""
691-
Calculate op(a, b), raising if either operand or the resulting value cannot be
692-
safely cast to an int64_t.
677+
Calculate op(a, b), raising if either operand or the result cannot be safely cast
678+
to an int64_t.
693679
"""
694680
try:
695-
return ops.calculate(op, a, b)
681+
return ops.calc_int_int(op, a, b)
682+
except OverflowError as ex:
683+
msg = f"outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
684+
raise OutOfBoundsTimedelta(msg) from ex
685+
686+
687+
cpdef int64_t calc_int_float(object op, object a, object b) except? -1:
688+
"""
689+
Calculate op(int, double), raising if any of the following aren't safe conversions:
690+
- a to int64_t
691+
- b to double
692+
- result to int64_t
693+
"""
694+
try:
695+
return ops.calc_int_float(op, a, b)
696696
except OverflowError as ex:
697697
msg = f"outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
698698
raise OutOfBoundsTimedelta(msg) from ex
@@ -742,7 +742,7 @@ def _binary_op_method_timedeltalike(op, name):
742742
if self._reso != other._reso:
743743
raise NotImplementedError
744744

745-
result = calculate(op, self.value, other.value)
745+
result = calc_int_int(op, self.value, other.value)
746746
if result == NPY_NAT:
747747
return NaT
748748
return _timedelta_from_value_and_reso(result, self._reso)
@@ -1601,19 +1601,18 @@ class Timedelta(_Timedelta):
16011601
__rsub__ = _binary_op_method_timedeltalike(lambda x, y: y - x, '__rsub__')
16021602

16031603
def __mul__(self, other):
1604-
if is_integer_object(other) or is_float_object(other):
1605-
if util.is_nan(other):
1606-
# np.nan * timedelta -> np.timedelta64("NaT"), in this case NaT
1607-
return NaT
1608-
1609-
return _timedelta_from_value_and_reso(
1610-
<int64_t>(other * self.value),
1611-
reso=self._reso,
1612-
)
1613-
1614-
elif is_array(other):
1604+
if util.is_nan(other):
1605+
# np.nan * timedelta -> np.timedelta64("NaT"), in this case NaT
1606+
return NaT
1607+
if is_array(other):
16151608
# ndarray-like
16161609
return other * self.to_timedelta64()
1610+
if is_integer_object(other):
1611+
value = calc_int_int(operator.mul, self.value, other)
1612+
return _timedelta_from_value_and_reso(value, self._reso)
1613+
if is_float_object(other):
1614+
value = calc_int_float(operator.mul, self.value, other)
1615+
return _timedelta_from_value_and_reso(value, self._reso)
16171616

16181617
return NotImplemented
16191618

0 commit comments

Comments
 (0)