Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/arrays/timedeltas.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

538 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5import textwrap 

6from typing import ( 

7 TYPE_CHECKING, 

8 Self, 

9 cast, 

10) 

11 

12import numpy as np 

13 

14from pandas._libs import ( 

15 lib, 

16 tslibs, 

17) 

18from pandas._libs.tslibs import ( 

19 Day, 

20 NaT, 

21 NaTType, 

22 Resolution, 

23 Tick, 

24 Timedelta, 

25 astype_overflowsafe, 

26 get_resolution, 

27 get_supported_dtype, 

28 iNaT, 

29 is_supported_dtype, 

30 mul_overflowsafe, 

31 periods_per_second, 

32) 

33from pandas._libs.tslibs.conversion import cast_from_unit_vectorized 

34from pandas._libs.tslibs.fields import ( 

35 get_timedelta_days, 

36 get_timedelta_field, 

37) 

38from pandas._libs.tslibs.timedeltas import ( 

39 array_to_timedelta64, 

40 contains_str, 

41 floordiv_object_array, 

42 ints_to_pytimedelta, 

43 parse_timedelta_unit, 

44 truediv_object_array, 

45) 

46from pandas.compat.numpy import function as nv 

47from pandas.errors import ( 

48 OutOfBoundsDatetime, 

49 OutOfBoundsTimedelta, 

50) 

51from pandas.util._decorators import set_module 

52from pandas.util._validators import validate_endpoints 

53 

54from pandas.core.dtypes.common import ( 

55 TD64NS_DTYPE, 

56 is_float_dtype, 

57 is_integer_dtype, 

58 is_object_dtype, 

59 is_scalar, 

60 is_string_dtype, 

61 pandas_dtype, 

62) 

63from pandas.core.dtypes.dtypes import ( 

64 ArrowDtype, 

65 BaseMaskedDtype, 

66 ExtensionDtype, 

67) 

68from pandas.core.dtypes.missing import isna 

69 

70from pandas.core import ( 

71 nanops, 

72 roperator, 

73) 

74from pandas.core.array_algos import datetimelike_accumulations 

75from pandas.core.arrays import datetimelike as dtl 

76from pandas.core.arrays._ranges import generate_regular_range 

77from pandas.core.arrays.integer import IntegerArray 

78import pandas.core.common as com 

79from pandas.core.construction import extract_array 

80from pandas.core.ops.common import unpack_zerodim_and_defer 

81 

82if TYPE_CHECKING: 

83 from collections.abc import Callable 

84 

85 from pandas._typing import ( 

86 AxisInt, 

87 DateTimeErrorChoices, 

88 DtypeObj, 

89 NpDtype, 

90 TimeUnit, 

91 npt, 

92 ) 

93 

94 from pandas import DataFrame 

95 

96 

97def _exact_if_integral(other): 

98 """ 

99 Return the exact int equivalent of an integral float, otherwise `other`. 

100 

101 GH#66551 float64 carries only a 53-bit mantissa, so applying a float operand 

102 in floating point rounds results that int64 represents exactly. An integral 

103 float has an exact int equivalent, so the int path can be used instead. 

104 inf and nan are not integral and so stay in float64. 

105 

106 Zero is left alone: it needs no exactness fix, and routing it to the int 

107 path would turn division by 0.0 from NaT into a raise. 

108 """ 

109 if lib.is_float(other) and other != 0 and other.is_integer(): 

110 return int(other) 

111 return other 

112 

113 

114def _field_accessor(name: str, alias: str, docstring: str): 

115 def f(self) -> np.ndarray: 

116 values = self.asi8 

117 if alias == "days": 

118 result = get_timedelta_days(values, reso=self._creso) 

119 else: 

120 # error: Incompatible types in assignment ( 

121 # expression has type "ndarray[Any, dtype[signedinteger[_32Bit]]]", 

122 # variable has type "ndarray[Any, dtype[signedinteger[_64Bit]]] 

123 result = get_timedelta_field(values, alias, reso=self._creso) # type: ignore[assignment] 

124 if self._hasna: 

125 result = self._maybe_mask_results( 

126 result, fill_value=None, convert="float64" 

127 ) 

128 

129 return result 

130 

131 f.__name__ = name 

132 f.__doc__ = f"\n{docstring}\n" 

133 return property(f) 

134 

135 

136@set_module("pandas.arrays") 

137class TimedeltaArray(dtl.TimelikeOps): 

138 """ 

139 Pandas ExtensionArray for timedelta data. 

140 

141 .. warning:: 

142 

143 TimedeltaArray is currently experimental, and its API may change 

144 without warning. In particular, :attr:`TimedeltaArray.dtype` is 

145 expected to change to be an instance of an ``ExtensionDtype`` 

146 subclass. 

147 

148 Parameters 

149 ---------- 

150 data : array-like 

151 The timedelta data. 

152 dtype : numpy.dtype 

153 Valid ``numpy`` dtypes are ``timedelta64[ns]``, ``timedelta64[us]``, 

154 ``timedelta64[ms]``, and ``timedelta64[s]``. 

155 freq : Offset, optional 

156 Frequency of the data. 

157 copy : bool, default False 

158 Whether to copy the underlying array of data. 

159 

160 Attributes 

161 ---------- 

162 None 

163 

164 Methods 

165 ------- 

166 None 

167 

168 See Also 

169 -------- 

170 Timedelta : Represents a duration, the difference between two dates or times. 

171 TimedeltaIndex : Immutable Index of timedelta64 data. 

172 to_timedelta : Convert argument to timedelta. 

173 

174 Examples 

175 -------- 

176 >>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(["1h", "2h"])) 

177 <TimedeltaArray> 

178 ['0 days 01:00:00', '0 days 02:00:00'] 

179 Length: 2, dtype: timedelta64[us] 

180 """ 

181 

182 _typ = "timedeltaarray" 

183 _recognized_scalars = (timedelta, np.timedelta64, Tick) 

184 _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: lib.is_np_dtype(x, "m") 

185 

186 @property 

187 def _internal_fill_value(self) -> np.timedelta64: 

188 return np.timedelta64("NaT", self.unit) 

189 

190 @property 

191 def _scalar_type(self) -> type[Timedelta]: 

192 return Timedelta 

193 

194 __array_priority__ = 1000 

195 # define my properties & methods for delegation 

196 _other_ops: list[str] = [] 

197 _bool_ops: list[str] = [] 

198 _field_ops: list[str] = ["days", "seconds", "microseconds", "nanoseconds"] 

199 _datetimelike_ops: list[str] = _field_ops + _bool_ops + ["unit"] 

200 _datetimelike_methods: list[str] = [ 

201 "to_pytimedelta", 

202 "total_seconds", 

203 "round", 

204 "floor", 

205 "ceil", 

206 "as_unit", 

207 ] 

208 

209 # Note: ndim must be defined to ensure NaT.__richcmp__(TimedeltaArray) 

210 # operates pointwise. 

211 

212 def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType: 

213 y = x.view("i8") 

214 if y == NaT._value: 

215 return NaT 

216 return Timedelta._from_value_and_reso(y, reso=self._creso) 

217 

218 @property 

219 # error: Return type "dtype" of "dtype" incompatible with return type 

220 # "ExtensionDtype" in supertype "ExtensionArray" 

221 def dtype(self) -> np.dtype[np.timedelta64]: # type: ignore[override] 

222 """ 

223 The dtype for the TimedeltaArray. 

224 

225 .. warning:: 

226 

227 A future version of pandas will change dtype to be an instance 

228 of a :class:`pandas.api.extensions.ExtensionDtype` subclass, 

229 not a ``numpy.dtype``. 

230 

231 Returns 

232 ------- 

233 numpy.dtype 

234 """ 

235 return self._ndarray.dtype 

236 

237 @property # NB: override with cache_readonly in immutable subclasses 

238 def _resolution_obj(self) -> Resolution: 

239 return get_resolution(self.asi8, tz=None, reso=self._creso) 

240 

241 # ---------------------------------------------------------------- 

242 # Constructors 

243 

244 @classmethod 

245 def _validate_dtype(cls, values, dtype): 

246 # used in TimeLikeOps.__init__ 

247 dtype = _validate_td64_dtype(dtype) 

248 _validate_td64_dtype(values.dtype) 

249 if dtype != values.dtype: 

250 raise ValueError("Values resolution does not match dtype.") 

251 return dtype 

252 

253 # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked" 

254 @classmethod 

255 def _simple_new( # type: ignore[override] 

256 cls, 

257 values: npt.NDArray[np.timedelta64], 

258 dtype: np.dtype[np.timedelta64] = TD64NS_DTYPE, 

259 ) -> Self: 

260 # Require td64 dtype, not unit-less, matching values.dtype 

261 assert lib.is_np_dtype(dtype, "m") 

262 assert not tslibs.is_unitless(dtype) 

263 assert isinstance(values, np.ndarray), type(values) 

264 assert dtype == values.dtype 

265 

266 return super()._simple_new(values=values, dtype=dtype) 

267 

268 @classmethod 

269 def _from_sequence(cls, data, *, dtype=None, copy: bool = False) -> Self: 

270 if dtype: 

271 dtype = _validate_td64_dtype(dtype) 

272 

273 data, copy = dtl.ensure_arraylike_for_datetimelike( 

274 data, copy, cls_name="TimedeltaArray" 

275 ) 

276 

277 unit = None 

278 if dtype is not None: 

279 if data.dtype == object: 

280 # GH#68639 the unit applies to the numeric entries, matching 

281 # to_timedelta(data, unit=...), except that a str alongside 

282 # them would make array_to_timedelta64 reject the unit 

283 apply_unit = not contains_str(data) 

284 else: 

285 apply_unit = data.dtype.kind in "iuf" 

286 if apply_unit: 

287 unit = np.datetime_data(dtype)[0] 

288 

289 data = sequence_to_td64ns(data, copy=copy, unit=unit) 

290 

291 if dtype is not None: 

292 data = astype_overflowsafe(data, dtype=dtype, copy=False) 

293 

294 return cls._simple_new(data, dtype=data.dtype) 

295 

296 @classmethod 

297 def _generate_range( 

298 cls, start, end, periods, freq, closed=None, *, unit: TimeUnit 

299 ) -> Self: 

300 periods = dtl.validate_periods(periods) 

301 if freq is None and any(x is None for x in [periods, start, end]): 

302 raise ValueError("Must provide freq argument if no data is supplied") 

303 

304 if com.count_not_none(start, end, periods, freq) != 3: 

305 raise ValueError( 

306 "Of the four parameters: start, end, periods, " 

307 "and freq, exactly three must be specified" 

308 ) 

309 

310 if start is not None: 

311 start = Timedelta(start).as_unit("ns") 

312 

313 if end is not None: 

314 end = Timedelta(end).as_unit("ns") 

315 

316 if unit not in ["s", "ms", "us", "ns"]: 

317 raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'") 

318 

319 if start is not None and unit is not None: 

320 start = start.as_unit(unit, round_ok=False) 

321 if end is not None and unit is not None: 

322 end = end.as_unit(unit, round_ok=False) 

323 

324 left_closed, right_closed = validate_endpoints(closed) 

325 

326 if freq is not None: 

327 index = generate_regular_range(start, end, periods, freq, unit=unit) 

328 else: 

329 index = np.linspace(start._value, end._value, periods).astype("i8") 

330 

331 if not left_closed: 

332 index = index[1:] 

333 if not right_closed: 

334 index = index[:-1] 

335 

336 td64values = index.view(f"m8[{unit}]") 

337 return cls._simple_new(td64values, dtype=td64values.dtype) 

338 

339 # ---------------------------------------------------------------- 

340 # DatetimeLike Interface 

341 

342 def _unbox_scalar(self, value) -> np.timedelta64: 

343 if not isinstance(value, self._scalar_type) and value is not NaT: 

344 raise ValueError("'value' should be a Timedelta.") 

345 self._check_compatible_with(value) 

346 if value is NaT: 

347 return np.timedelta64(value._value, self.unit) 

348 else: 

349 # error: Incompatible return value type (got "timedelta64[timedelta | 

350 # int | None] | datetime64[date | int | None]", 

351 # expected "timedelta64[timedelta | int | None]") 

352 return value.as_unit(self.unit, round_ok=False).asm8 # type: ignore[return-value] 

353 

354 def _scalar_from_string(self, value) -> Timedelta | NaTType: 

355 return Timedelta(value) 

356 

357 def _check_compatible_with(self, other) -> None: 

358 # we don't have anything to validate. 

359 pass 

360 

361 # ---------------------------------------------------------------- 

362 # Array-Like / EA-Interface Methods 

363 

364 def astype(self, dtype, copy: bool = True): 

365 # We handle 

366 # --> timedelta64[ns] 

367 # --> timedelta64 

368 # DatetimeLikeArrayMixin super call handles other cases 

369 dtype = pandas_dtype(dtype) 

370 

371 if lib.is_np_dtype(dtype, "m"): 

372 if dtype == self.dtype: 

373 if copy: 

374 return self.copy() 

375 return self 

376 

377 if is_supported_dtype(dtype): 

378 # unit conversion e.g. timedelta64[s] 

379 res_values = astype_overflowsafe(self._ndarray, dtype, copy=False) 

380 return type(self)._simple_new(res_values, dtype=res_values.dtype) 

381 else: 

382 raise ValueError( 

383 f"Cannot convert from {self.dtype} to {dtype}. " 

384 "Supported resolutions are 's', 'ms', 'us', 'ns'" 

385 ) 

386 

387 return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy=copy) 

388 

389 def _iter_convert_chunk(self, data: np.ndarray) -> np.ndarray: 

390 return ints_to_pytimedelta(data, box=True) 

391 

392 # ---------------------------------------------------------------- 

393 # Reductions 

394 

395 def sum( 

396 self, 

397 *, 

398 axis: AxisInt | None = None, 

399 dtype: NpDtype | None = None, 

400 out=None, 

401 keepdims: bool = False, 

402 initial=None, 

403 skipna: bool = True, 

404 min_count: int = 0, 

405 ): 

406 nv.validate_sum( 

407 (), {"dtype": dtype, "out": out, "keepdims": keepdims, "initial": initial} 

408 ) 

409 

410 result = nanops.nansum( 

411 self._ndarray, axis=axis, skipna=skipna, min_count=min_count 

412 ) 

413 return self._wrap_reduction_result(axis, result) 

414 

415 def std( 

416 self, 

417 *, 

418 axis: AxisInt | None = None, 

419 dtype: NpDtype | None = None, 

420 out=None, 

421 ddof: int = 1, 

422 keepdims: bool = False, 

423 skipna: bool = True, 

424 ): 

425 nv.validate_stat_ddof_func( 

426 (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="std" 

427 ) 

428 

429 result = nanops.nanstd(self._ndarray, axis=axis, skipna=skipna, ddof=ddof) 

430 return self._wrap_reduction_result(axis, result) 

431 

432 # ---------------------------------------------------------------- 

433 # Accumulations 

434 

435 def _accumulate(self, name: str, *, skipna: bool = True, **kwargs): 

436 if name == "cumsum": 

437 op = getattr(datetimelike_accumulations, name) 

438 result = op(self._ndarray.copy(), skipna=skipna, **kwargs) 

439 

440 return type(self)._simple_new(result, dtype=self.dtype) 

441 elif name == "cumprod": 

442 raise TypeError("cumprod not supported for Timedelta.") 

443 

444 else: 

445 return super()._accumulate(name, skipna=skipna, **kwargs) 

446 

447 # ---------------------------------------------------------------- 

448 # Rendering Methods 

449 

450 def _formatter(self, boxed: bool = False): 

451 from pandas.io.formats.format import get_format_timedelta64 

452 

453 return get_format_timedelta64(self, box=True) 

454 

455 def _format_native_types( 

456 self, *, na_rep: str | float = "NaT", date_format=None, **kwargs 

457 ) -> npt.NDArray[np.object_]: 

458 from pandas.io.formats.format import get_format_timedelta64 

459 

460 # Relies on TimeDelta._repr_base 

461 formatter = get_format_timedelta64(self, na_rep) 

462 # equiv: np.array([formatter(x) for x in self._ndarray]) 

463 # but independent of dimension 

464 return np.frompyfunc(formatter, 1, 1)(self._ndarray) 

465 

466 # ---------------------------------------------------------------- 

467 # Arithmetic Methods 

468 

469 def _add_offset(self, other): 

470 assert not isinstance(other, (Tick, Day)) 

471 raise TypeError( 

472 f"cannot add the type {type(other).__name__} to a {type(self).__name__}" 

473 ) 

474 

475 def _mul_float_overflowsafe( 

476 self, other: float | np.floating | npt.NDArray[np.floating] 

477 ) -> Self: 

478 # GH#43178: detect float products that would silently saturate to 

479 # int64.max on the int64 cast below 

480 i8 = self.asi8 

481 self_mask = i8 == iNaT 

482 if self_mask.any(): 

483 # zero out NaT positions so they don't trigger the bounds check 

484 i8 = np.where(self_mask, 0, i8) 

485 f_result = i8 * other 

486 nan_mask = np.isnan(f_result) 

487 non_nan = f_result[~nan_mask] 

488 # Compare against 2**63, not i8max: i8max (2**63 - 1) rounds up to 

489 # 2**63 in float64, so a product landing exactly on 2**63 would slip 

490 # past a ``> i8max`` check and saturate on the cast. Also catches +/-inf. 

491 if non_nan.size and np.max(np.abs(non_nan), initial=0.0) >= 2.0**63: 

492 raise OutOfBoundsTimedelta("Overflow in timedelta multiplication") 

493 # NaN-to-int cast is platform-dependent; substitute 0 then re-mask as NaT 

494 if nan_mask.any(): 

495 f_result = np.where(nan_mask, 0.0, f_result) 

496 i8_result = f_result.astype("i8") 

497 nat_out = self_mask | nan_mask 

498 if nat_out.any(): 

499 i8_result[nat_out] = iNaT 

500 result = i8_result.view(self._ndarray.dtype) 

501 return type(self)._simple_new(result, dtype=result.dtype) 

502 

503 def _mul_int_overflowsafe(self, i8_other: npt.NDArray[np.int64]) -> Self: 

504 # GH#43178: mul_overflowsafe raises the low-level OverflowError; surface 

505 # it as OutOfBoundsTimedelta to match pandas' other td64 overflow paths. 

506 try: 

507 i8_result = mul_overflowsafe(self.asi8, i8_other) 

508 except OverflowError as err: 

509 raise OutOfBoundsTimedelta("Overflow in int64 multiplication") from err 

510 result = i8_result.view(self._ndarray.dtype) 

511 return type(self)._simple_new(result, dtype=result.dtype) 

512 

513 @unpack_zerodim_and_defer("__mul__") 

514 def __mul__(self, other) -> Self: 

515 if is_scalar(other): 

516 if lib.is_bool(other): 

517 raise TypeError( 

518 f"Cannot multiply '{self.dtype}' by bool, explicitly cast to " 

519 "integers instead" 

520 ) 

521 other = _exact_if_integral(other) 

522 if lib.is_integer(other): 

523 # GH#43178: detect int64 overflow rather than silently wrapping 

524 # in the i8 cast below (e.g. a multiplier outside int64 bounds). 

525 # TODO(numpy>=2.5): numpy detects this natively (numpy GH-31378) 

526 # but raises OverflowError; once the numpy floor is >= 2.5, drop 

527 # mul_overflowsafe and re-wrap numpy's error as 

528 # OutOfBoundsTimedelta. The non-integral float path isn't 

529 # covered and stays. 

530 other = int(other) 

531 if other > lib.i8max or other < -lib.i8max - 1: 

532 raise OutOfBoundsTimedelta("Overflow in int64 multiplication") 

533 i8_vals = self.asi8 

534 if other != 0 and i8_vals.size: 

535 # The extreme elements bound all products, so checking them 

536 # with exact Python-int arithmetic lets the common 

537 # no-overflow case use a vectorized multiply. NaT 

538 # (int64.min) always trips the bound, falling through to 

539 # the NaT-aware cython loop. 

540 low_prod = int(i8_vals.min()) * other 

541 high_prod = int(i8_vals.max()) * other 

542 if max(abs(low_prod), abs(high_prod)) <= lib.i8max: 

543 result = (i8_vals * other).view(self._ndarray.dtype) 

544 return type(self)._simple_new(result, dtype=result.dtype) 

545 return self._mul_int_overflowsafe(np.asarray(other, dtype="i8")) 

546 if lib.is_float(other): 

547 return self._mul_float_overflowsafe(other) 

548 # numpy will raise TypeError for non-numeric scalar 

549 result = self._ndarray * other 

550 if result.dtype.kind != "m": 

551 # numpy >= 2.1 may not raise a TypeError 

552 # and seems to dispatch to others.__rmul__? 

553 raise TypeError(f"Cannot multiply with {type(other).__name__}") 

554 return type(self)._simple_new(result, dtype=result.dtype) 

555 

556 if not hasattr(other, "dtype"): 

557 # list, tuple 

558 other = np.array(other) 

559 

560 if other.dtype.kind == "b": 

561 # GH#58054 

562 raise TypeError( 

563 f"Cannot multiply '{self.dtype}' by bool, explicitly cast to " 

564 "integers instead" 

565 ) 

566 if isinstance(other.dtype, (ArrowDtype, BaseMaskedDtype)): 

567 # GH#58054 

568 return NotImplemented 

569 

570 if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"): 

571 # Exclude timedelta64 here so we correctly raise TypeError 

572 # for that instead of ValueError 

573 raise ValueError("Cannot multiply with unequal lengths") 

574 

575 if is_object_dtype(other.dtype): 

576 # this multiplication will succeed only if all elements of other 

577 # are int or float scalars, so we will end up with 

578 # timedelta64[ns]-dtyped result 

579 arr = self._ndarray 

580 obj_result = np.array([arr[n] * other[n] for n in range(len(self))]) 

581 return type(self)._simple_new(obj_result, dtype=obj_result.dtype) 

582 

583 if other.dtype.kind in "iu": 

584 # GH#43178: detect int64 overflow rather than silently wrapping. 

585 # Cast to int64 first: an unsigned multiplier above int64.max wraps 

586 # to negative, which we detect by sign. We check the sign rather 

587 # than ``other > i8max`` because comparing a broadcast unsigned 

588 # array to a Python int segfaults on numpy < 2.2 (hit via the 

589 # DataFrame blockwise path). 

590 i8_other = other.astype("i8", copy=False) 

591 if other.dtype.kind == "u" and (i8_other < 0).any(): 

592 raise OutOfBoundsTimedelta("Overflow in int64 multiplication") 

593 return self._mul_int_overflowsafe(i8_other) 

594 

595 if other.dtype.kind == "f": 

596 return self._mul_float_overflowsafe(other) 

597 

598 # numpy will raise TypeError for non-numeric dtype 

599 result = self._ndarray * other 

600 if result.dtype.kind != "m": 

601 # numpy >= 2.1 may not raise a TypeError 

602 # and seems to dispatch to others.__rmul__? 

603 raise TypeError(f"Cannot multiply with {type(other).__name__}") 

604 return type(self)._simple_new(result, dtype=result.dtype) 

605 

606 __rmul__ = __mul__ 

607 

608 def _check_float_div_overflow( 

609 self, other: float | np.floating | npt.NDArray[np.floating] 

610 ) -> None: 

611 # GH#43178: numpy's timedelta64-by-float division silently saturates 

612 # when the quotient exceeds int64 bounds; detect that on the float64 

613 # quotient and raise instead. NaT dividends and zero/NaN divisors are 

614 # excluded: numpy returns NaT for those. A float64 quotient inside 

615 # (-2**63, 2**63) is at least an ulp (1024) away from the boundary, 

616 # so the floor in __floordiv__ cannot push it out of bounds. 

617 i8 = self.asi8 

618 other_arr = np.asarray(other) 

619 if other_arr.ndim == 0 and i8.size: 

620 divisor = other_arr.item() 

621 if divisor == 0 or np.isnan(divisor): 

622 # numpy returns all-NaT; nothing to check 

623 return 

624 # The extreme elements bound all quotients, so most cases resolve 

625 # without the full per-element check below. A NaT (int64.min) 

626 # dividend can only false-trip this bound, never pass an 

627 # overflowing quotient. 

628 low_quot = i8.min() / divisor 

629 high_quot = i8.max() / divisor 

630 if max(abs(low_quot), abs(high_quot)) < 2.0**63: 

631 return 

632 with np.errstate(divide="ignore", invalid="ignore"): 

633 f_quot = i8 / other_arr 

634 exclude_mask = (i8 == iNaT) | np.isnan(f_quot) | (other_arr == 0) 

635 if np.max(np.abs(f_quot), initial=0.0, where=~exclude_mask) >= 2.0**63: 

636 raise OutOfBoundsTimedelta("Overflow in timedelta division") 

637 

638 def _scalar_divlike_op(self, other, op): 

639 """ 

640 Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__ 

641 with scalar 'other'. 

642 """ 

643 if isinstance(other, self._recognized_scalars): 

644 other = Timedelta(other) 

645 # mypy assumes that __new__ returns an instance of the class 

646 # github.com/python/mypy/issues/1020 

647 if cast("Timedelta | NaTType", other) is NaT: 

648 # specifically timedelta64-NaT 

649 res = np.empty(self.shape, dtype=np.float64) 

650 res.fill(np.nan) 

651 return res 

652 

653 # otherwise, dispatch to Timedelta implementation 

654 return op(self._ndarray, other) 

655 

656 else: 

657 # caller is responsible for checking lib.is_scalar(other) 

658 # assume other is numeric, otherwise numpy will raise 

659 

660 if op in [roperator.rtruediv, roperator.rfloordiv]: 

661 raise TypeError( 

662 f"Cannot divide {type(other).__name__} by {type(self).__name__}" 

663 ) 

664 

665 other = _exact_if_integral(other) 

666 if lib.is_float(other): 

667 # GH#43178: raise instead of silently saturating on overflow 

668 self._check_float_div_overflow(other) 

669 result = op(self._ndarray, other) 

670 return type(self)._simple_new(result, dtype=result.dtype) 

671 

672 def _cast_divlike_op(self, other): 

673 if not hasattr(other, "dtype"): 

674 # e.g. list, tuple 

675 other = np.array(other) 

676 

677 if len(other) != len(self): 

678 raise ValueError("Cannot divide vectors with unequal lengths") 

679 return other 

680 

681 def _vector_divlike_op(self, other, op) -> np.ndarray | Self: 

682 """ 

683 Shared logic for __truediv__, __floordiv__, and their reversed versions 

684 with timedelta64-dtype ndarray other. 

685 """ 

686 other_arr = np.asarray(other) 

687 if other_arr.dtype.kind == "f" and op in [operator.truediv, operator.floordiv]: 

688 # GH#43178: raise instead of silently saturating on overflow 

689 self._check_float_div_overflow(other_arr) 

690 

691 # Let numpy handle it 

692 result = op(self._ndarray, other_arr) 

693 

694 if (is_integer_dtype(other.dtype) or is_float_dtype(other.dtype)) and op in [ 

695 operator.truediv, 

696 operator.floordiv, 

697 ]: 

698 return type(self)._simple_new(result, dtype=result.dtype) 

699 

700 if op in [operator.floordiv, roperator.rfloordiv]: 

701 mask = self.isna() | isna(other) 

702 if mask.any(): 

703 result = result.astype(np.float64) 

704 np.putmask(result, mask, np.nan) 

705 

706 return result 

707 

708 @unpack_zerodim_and_defer("__truediv__") 

709 def __truediv__(self, other): 

710 # timedelta / X is well-defined for timedelta-like or numeric X 

711 op = operator.truediv 

712 if is_scalar(other): 

713 return self._scalar_divlike_op(other, op) 

714 

715 other = self._cast_divlike_op(other) 

716 if ( 

717 lib.is_np_dtype(other.dtype, "m") 

718 or is_integer_dtype(other.dtype) 

719 or is_float_dtype(other.dtype) 

720 ): 

721 return self._vector_divlike_op(other, op) 

722 

723 if is_object_dtype(other.dtype): 

724 other = np.asarray(other) 

725 if self.ndim > 1: 

726 res_cols = [ 

727 left / right for left, right in zip(self, other, strict=True) 

728 ] 

729 res_cols2 = [x.reshape(1, -1) for x in res_cols] 

730 result = np.concatenate(res_cols2, axis=0) 

731 else: 

732 result = truediv_object_array(self._ndarray, other) 

733 

734 return result 

735 

736 else: 

737 return NotImplemented 

738 

739 @unpack_zerodim_and_defer("__rtruediv__") 

740 def __rtruediv__(self, other): 

741 # X / timedelta is defined only for timedelta-like X 

742 op = roperator.rtruediv 

743 if is_scalar(other): 

744 return self._scalar_divlike_op(other, op) 

745 

746 other = self._cast_divlike_op(other) 

747 if lib.is_np_dtype(other.dtype, "m"): 

748 return self._vector_divlike_op(other, op) 

749 

750 elif is_object_dtype(other.dtype): 

751 # Note: unlike in __truediv__, we do not _need_ to do type 

752 # inference on the result. It does not raise, a numeric array 

753 # is returned. GH#23829 

754 result_list = [other[n] / self[n] for n in range(len(self))] 

755 return np.array(result_list) 

756 

757 else: 

758 return NotImplemented 

759 

760 @unpack_zerodim_and_defer("__floordiv__") 

761 def __floordiv__(self, other): 

762 op = operator.floordiv 

763 if is_scalar(other): 

764 return self._scalar_divlike_op(other, op) 

765 

766 other = self._cast_divlike_op(other) 

767 if ( 

768 lib.is_np_dtype(other.dtype, "m") 

769 or is_integer_dtype(other.dtype) 

770 or is_float_dtype(other.dtype) 

771 ): 

772 return self._vector_divlike_op(other, op) 

773 

774 elif is_object_dtype(other.dtype): 

775 other = np.asarray(other) 

776 if self.ndim > 1: 

777 res_cols = [ 

778 left // right for left, right in zip(self, other, strict=True) 

779 ] 

780 res_cols2 = [x.reshape(1, -1) for x in res_cols] 

781 result = np.concatenate(res_cols2, axis=0) 

782 else: 

783 result = floordiv_object_array(self._ndarray, other) 

784 

785 assert result.dtype == object 

786 return result 

787 

788 else: 

789 return NotImplemented 

790 

791 @unpack_zerodim_and_defer("__rfloordiv__") 

792 def __rfloordiv__(self, other): 

793 op = roperator.rfloordiv 

794 if is_scalar(other): 

795 return self._scalar_divlike_op(other, op) 

796 

797 other = self._cast_divlike_op(other) 

798 if lib.is_np_dtype(other.dtype, "m"): 

799 return self._vector_divlike_op(other, op) 

800 

801 elif is_object_dtype(other.dtype): 

802 result_list = [other[n] // self[n] for n in range(len(self))] 

803 result = np.array(result_list) 

804 return result 

805 

806 else: 

807 return NotImplemented 

808 

809 @unpack_zerodim_and_defer("__mod__") 

810 def __mod__(self, other): 

811 # Note: This is a naive implementation, can likely be optimized 

812 if isinstance(other, self._recognized_scalars): 

813 other = Timedelta(other) 

814 return self - (self // other) * other 

815 

816 @unpack_zerodim_and_defer("__rmod__") 

817 def __rmod__(self, other): 

818 # Note: This is a naive implementation, can likely be optimized 

819 if isinstance(other, self._recognized_scalars): 

820 other = Timedelta(other) 

821 return other - (other // self) * self 

822 

823 @unpack_zerodim_and_defer("__divmod__") 

824 def __divmod__(self, other): 

825 # Note: This is a naive implementation, can likely be optimized 

826 if isinstance(other, self._recognized_scalars): 

827 other = Timedelta(other) 

828 

829 res1 = self // other 

830 res2 = self - res1 * other 

831 return res1, res2 

832 

833 @unpack_zerodim_and_defer("__rdivmod__") 

834 def __rdivmod__(self, other): 

835 # Note: This is a naive implementation, can likely be optimized 

836 if isinstance(other, self._recognized_scalars): 

837 other = Timedelta(other) 

838 

839 res1 = other // self 

840 res2 = other - res1 * self 

841 return res1, res2 

842 

843 def __neg__(self) -> TimedeltaArray: 

844 return type(self)._simple_new(-self._ndarray, dtype=self.dtype) 

845 

846 def __pos__(self) -> TimedeltaArray: 

847 return type(self)._simple_new(self._ndarray.copy(), dtype=self.dtype) 

848 

849 def __abs__(self) -> TimedeltaArray: 

850 # Note: freq is not preserved 

851 return type(self)._simple_new(np.abs(self._ndarray), dtype=self.dtype) 

852 

853 # ---------------------------------------------------------------- 

854 # Conversion Methods - Vectorized analogues of Timedelta methods 

855 

856 def total_seconds(self) -> npt.NDArray[np.float64]: 

857 """ 

858 Return total duration of each element expressed in seconds. 

859 

860 This method is available directly on TimedeltaArray, TimedeltaIndex 

861 and on Series containing timedelta values under the ``.dt`` namespace. 

862 

863 Returns 

864 ------- 

865 ndarray, Index or Series 

866 When the calling object is a TimedeltaArray, the return type 

867 is ndarray. When the calling object is a TimedeltaIndex, 

868 the return type is an Index with a float64 dtype. When the calling object 

869 is a Series, the return type is Series of type `float64` whose 

870 index is the same as the original. 

871 

872 See Also 

873 -------- 

874 datetime.timedelta.total_seconds : Standard library version 

875 of this method. 

876 TimedeltaIndex.components : Return a DataFrame with components of 

877 each Timedelta. 

878 

879 Examples 

880 -------- 

881 **Series** 

882 

883 >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D")) 

884 >>> s 

885 0 0 days 

886 1 1 days 

887 2 2 days 

888 3 3 days 

889 4 4 days 

890 dtype: timedelta64[us] 

891 

892 >>> s.dt.total_seconds() 

893 0 0.0 

894 1 86400.0 

895 2 172800.0 

896 3 259200.0 

897 4 345600.0 

898 dtype: float64 

899 

900 **TimedeltaIndex** 

901 

902 >>> idx = pd.to_timedelta(np.arange(5), unit="D") 

903 >>> idx 

904 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'], 

905 dtype='timedelta64[us]', freq=None) 

906 

907 >>> idx.total_seconds() 

908 Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64') 

909 """ 

910 pps = periods_per_second(self._creso) 

911 asi8 = self.asi8 

912 # Divide the integer-second part and the sub-second residual 

913 # separately, mirroring the same divmod split in the scalar 

914 # `_Timedelta.total_seconds`, so the result matches it bit-for-bit 

915 # (on IEEE-strict platforms; 32-bit x87 excess precision can differ 

916 # in the last ulp). A single asi8 / pps division rounds at the tick 

917 # magnitude and loses precision for values above 2**53. 

918 floor_s, residual = np.divmod(asi8, pps) 

919 result = floor_s + residual / pps 

920 if (asi8 > 2**53).any() or (asi8 < -(2**53)).any(): 

921 # Only above 2**53 ticks can the float result round onto an 

922 # integer-second boundary; the threshold is unit-independent 

923 # because a collapse needs ulp(floor_s) > 2 / pps, i.e. 

924 # |asi8| ~ floor_s * pps > 2**53. A sub-second residual puts the 

925 # true value strictly inside (floor, floor + 1), so nudge off 

926 # either boundary, otherwise bisect-style lookups (e.g. dateutil 

927 # DST, GH#31043) misclassify the timestamp as on a transition. 

928 # This mirrors the scalar guard in `_Timedelta.total_seconds`, 

929 # including its bound: beyond 2**52 seconds float64s are spaced 

930 # >= 1 second apart, so every representable value sits on a 

931 # boundary and nudging would only add error. 

932 fixable = (residual != 0) & (np.abs(floor_s) < 2**52) 

933 floor_f = floor_s.astype(np.float64) 

934 on_floor = fixable & (result == floor_f) 

935 on_ceil = fixable & (result == floor_f + 1.0) 

936 if on_floor.any(): 

937 result = np.where(on_floor, np.nextafter(result, result + 1.0), result) 

938 if on_ceil.any(): 

939 result = np.where(on_ceil, np.nextafter(result, result - 1.0), result) 

940 return self._maybe_mask_results(result, fill_value=None) 

941 

942 def to_pytimedelta(self) -> npt.NDArray[np.object_]: 

943 """ 

944 Return an ndarray of datetime.timedelta objects. 

945 

946 Each element of the :class:`TimedeltaIndex` is converted to the 

947 corresponding native Python :class:`datetime.timedelta` object. 

948 

949 Returns 

950 ------- 

951 numpy.ndarray 

952 A NumPy ``timedelta64`` object representing the same duration as the 

953 original pandas ``Timedelta`` object. The precision of the resulting 

954 object is in nanoseconds, which is the default 

955 time resolution used by pandas for ``Timedelta`` objects, ensuring 

956 high precision for time-based calculations. 

957 

958 See Also 

959 -------- 

960 to_timedelta : Convert argument to timedelta format. 

961 Timedelta : Represents a duration between two dates or times. 

962 DatetimeIndex: Index of datetime64 data. 

963 Timedelta.components : Return a components namedtuple-like 

964 of a single timedelta. 

965 

966 Examples 

967 -------- 

968 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D") 

969 >>> tdelta_idx 

970 TimedeltaIndex(['1 days', '2 days', '3 days'], 

971 dtype='timedelta64[us]', freq=None) 

972 >>> tdelta_idx.to_pytimedelta() 

973 array([datetime.timedelta(days=1), datetime.timedelta(days=2), 

974 datetime.timedelta(days=3)], dtype=object) 

975 

976 >>> tidx = pd.TimedeltaIndex(data=["1 days 02:30:45", "3 days 04:15:10"]) 

977 >>> tidx 

978 TimedeltaIndex(['1 days 02:30:45', '3 days 04:15:10'], 

979 dtype='timedelta64[us]', freq=None) 

980 >>> tidx.to_pytimedelta() 

981 array([datetime.timedelta(days=1, seconds=9045), 

982 datetime.timedelta(days=3, seconds=15310)], dtype=object) 

983 """ 

984 return ints_to_pytimedelta(self._ndarray) 

985 

986 days_docstring = textwrap.dedent("""Number of days for each element. 

987 

988 This attribute returns the number of whole days in each timedelta value. 

989 It represents the days component of the duration, not the total duration 

990 expressed in days. 

991 

992 See Also 

993 -------- 

994 Series.dt.seconds : Return number of seconds for each element. 

995 Series.dt.microseconds : Return number of microseconds for each element. 

996 Series.dt.nanoseconds : Return number of nanoseconds for each element. 

997 

998 Examples 

999 -------- 

1000 For Series: 

1001 

1002 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='D')) 

1003 >>> ser 

1004 0 1 days 

1005 1 2 days 

1006 2 3 days 

1007 dtype: timedelta64[us] 

1008 >>> ser.dt.days 

1009 0 1 

1010 1 2 

1011 2 3 

1012 dtype: int64 

1013 

1014 For TimedeltaIndex: 

1015 

1016 >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"]) 

1017 >>> tdelta_idx 

1018 TimedeltaIndex(['0 days', '10 days', '20 days'], 

1019 dtype='timedelta64[us]', freq=None) 

1020 >>> tdelta_idx.days 

1021 Index([0, 10, 20], dtype='int64')""") 

1022 days = _field_accessor("days", "days", days_docstring) 

1023 

1024 seconds_docstring = textwrap.dedent( 

1025 """Number of seconds (>= 0 and less than 1 day) for each element. 

1026 

1027 This attribute returns the seconds component of each timedelta value, 

1028 which is the number of seconds remaining after subtracting whole days. 

1029 Values range from 0 to 86399. 

1030 

1031 See Also 

1032 -------- 

1033 Series.dt.seconds : Return number of seconds for each element. 

1034 Series.dt.nanoseconds : Return number of nanoseconds for each element. 

1035 

1036 Examples 

1037 -------- 

1038 For Series: 

1039 

1040 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='s')) 

1041 >>> ser 

1042 0 0 days 00:00:01 

1043 1 0 days 00:00:02 

1044 2 0 days 00:00:03 

1045 dtype: timedelta64[us] 

1046 >>> ser.dt.seconds 

1047 0 1 

1048 1 2 

1049 2 3 

1050 dtype: int32 

1051 

1052 For TimedeltaIndex: 

1053 

1054 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='s') 

1055 >>> tdelta_idx 

1056 TimedeltaIndex(['0 days 00:00:01', '0 days 00:00:02', '0 days 00:00:03'], 

1057 dtype='timedelta64[us]', freq=None) 

1058 >>> tdelta_idx.seconds 

1059 Index([1, 2, 3], dtype='int32')""" 

1060 ) 

1061 seconds = _field_accessor( 

1062 "seconds", 

1063 "seconds", 

1064 seconds_docstring, 

1065 ) 

1066 

1067 microseconds_docstring = textwrap.dedent( 

1068 """Number of microseconds (>= 0 and less than 1 second) for each element. 

1069 

1070 This attribute returns the microseconds component of each timedelta value, 

1071 which is the number of microseconds remaining after subtracting whole 

1072 seconds. Values range from 0 to 999999. 

1073 

1074 See Also 

1075 -------- 

1076 pd.Timedelta.microseconds : Number of microseconds (>= 0 and less than 1 second). 

1077 pd.Timedelta.to_pytimedelta.microseconds : Number of microseconds (>= 0 and less 

1078 than 1 second) of a datetime.timedelta. 

1079 

1080 Examples 

1081 -------- 

1082 For Series: 

1083 

1084 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='us')) 

1085 >>> ser 

1086 0 0 days 00:00:00.000001 

1087 1 0 days 00:00:00.000002 

1088 2 0 days 00:00:00.000003 

1089 dtype: timedelta64[us] 

1090 >>> ser.dt.microseconds 

1091 0 1 

1092 1 2 

1093 2 3 

1094 dtype: int32 

1095 

1096 For TimedeltaIndex: 

1097 

1098 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='us') 

1099 >>> tdelta_idx 

1100 TimedeltaIndex(['0 days 00:00:00.000001', '0 days 00:00:00.000002', 

1101 '0 days 00:00:00.000003'], 

1102 dtype='timedelta64[us]', freq=None) 

1103 >>> tdelta_idx.microseconds 

1104 Index([1, 2, 3], dtype='int32')""" 

1105 ) 

1106 microseconds = _field_accessor( 

1107 "microseconds", 

1108 "microseconds", 

1109 microseconds_docstring, 

1110 ) 

1111 

1112 nanoseconds_docstring = textwrap.dedent( 

1113 """Number of nanoseconds (>= 0 and less than 1 microsecond) for each element. 

1114 

1115 This attribute returns the nanoseconds component of each timedelta value, 

1116 which is the number of nanoseconds remaining after subtracting whole 

1117 microseconds. Values range from 0 to 999. 

1118 

1119 See Also 

1120 -------- 

1121 Series.dt.seconds : Return number of seconds for each element. 

1122 Series.dt.microseconds : Return number of microseconds for each element. 

1123 

1124 Examples 

1125 -------- 

1126 For Series: 

1127 

1128 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='ns')) 

1129 >>> ser 

1130 0 0 days 00:00:00.000000001 

1131 1 0 days 00:00:00.000000002 

1132 2 0 days 00:00:00.000000003 

1133 dtype: timedelta64[ns] 

1134 >>> ser.dt.nanoseconds 

1135 0 1 

1136 1 2 

1137 2 3 

1138 dtype: int32 

1139 

1140 For TimedeltaIndex: 

1141 

1142 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='ns') 

1143 >>> tdelta_idx 

1144 TimedeltaIndex(['0 days 00:00:00.000000001', '0 days 00:00:00.000000002', 

1145 '0 days 00:00:00.000000003'], 

1146 dtype='timedelta64[ns]', freq=None) 

1147 >>> tdelta_idx.nanoseconds 

1148 Index([1, 2, 3], dtype='int32')""" 

1149 ) 

1150 nanoseconds = _field_accessor( 

1151 "nanoseconds", 

1152 "nanoseconds", 

1153 nanoseconds_docstring, 

1154 ) 

1155 

1156 @property 

1157 def components(self) -> DataFrame: 

1158 """ 

1159 Return a DataFrame of the individual resolution components of the Timedeltas. 

1160 

1161 The components (days, hours, minutes seconds, milliseconds, microseconds, 

1162 nanoseconds) are returned as columns in a DataFrame. 

1163 

1164 Returns 

1165 ------- 

1166 DataFrame 

1167 

1168 See Also 

1169 -------- 

1170 TimedeltaIndex.total_seconds : Return total duration expressed in seconds. 

1171 Timedelta.components : Return a components namedtuple-like of a single 

1172 timedelta. 

1173 

1174 Examples 

1175 -------- 

1176 >>> tdelta_idx = pd.to_timedelta(["1 day 3 min 2 us 42 ns"]) 

1177 >>> tdelta_idx 

1178 TimedeltaIndex(['1 days 00:03:00.000002042'], 

1179 dtype='timedelta64[ns]', freq=None) 

1180 >>> tdelta_idx.components 

1181 days hours minutes seconds milliseconds microseconds nanoseconds 

1182 0 1 0 3 0 0 2 42 

1183 """ 

1184 from pandas import DataFrame 

1185 

1186 columns = [ 

1187 "days", 

1188 "hours", 

1189 "minutes", 

1190 "seconds", 

1191 "milliseconds", 

1192 "microseconds", 

1193 "nanoseconds", 

1194 ] 

1195 hasnans = self._hasna 

1196 if hasnans: 

1197 

1198 def f(x): # pyright: ignore[reportRedeclaration] 

1199 if isna(x): 

1200 return [np.nan] * len(columns) 

1201 return x.components 

1202 

1203 else: 

1204 

1205 def f(x): 

1206 return x.components 

1207 

1208 result = DataFrame([f(x) for x in self], columns=columns) 

1209 if not hasnans: 

1210 result = result.astype("int64") 

1211 return result 

1212 

1213 

1214# --------------------------------------------------------------------- 

1215# Constructor Helpers 

1216 

1217 

1218def sequence_to_td64ns( 

1219 data, 

1220 copy: bool = False, 

1221 unit=None, 

1222 errors: DateTimeErrorChoices = "raise", 

1223) -> np.ndarray: 

1224 """ 

1225 Parameters 

1226 ---------- 

1227 data : list-like 

1228 copy : bool, default False 

1229 unit : str, optional 

1230 The timedelta unit to treat integers as multiples of. For numeric 

1231 data this defaults to ``'ns'``. 

1232 Must be un-specified if the data contains a str and ``errors=="raise"``. 

1233 errors : {"raise", "coerce", "ignore"}, default "raise" 

1234 How to handle elements that cannot be converted to timedelta64[ns]. 

1235 See ``pandas.to_timedelta`` for details. 

1236 

1237 Returns 

1238 ------- 

1239 converted : numpy.ndarray 

1240 The sequence converted to a numpy array with dtype ``timedelta64[ns]``. 

1241 

1242 Raises 

1243 ------ 

1244 ValueError : Data cannot be converted to timedelta64[ns]. 

1245 

1246 Notes 

1247 ----- 

1248 Unlike `pandas.to_timedelta`, if setting ``errors=ignore`` will not cause 

1249 errors to be ignored; they are caught and subsequently ignored at a 

1250 higher level. 

1251 """ 

1252 assert unit not in ["Y", "y", "M"] # caller is responsible for checking 

1253 

1254 if unit is not None: 

1255 unit = parse_timedelta_unit(unit) 

1256 

1257 data = extract_array(data, extract_numpy=True) 

1258 

1259 int_mask = None 

1260 if isinstance(data, IntegerArray): 

1261 # GH#66988 use the underlying int/uint ndarray + mask directly instead 

1262 # of going through to_numpy() to convert to int64, which could overflow 

1263 # for large uint64 data 

1264 int_mask = data._mask if data._hasna else None 

1265 data = data._data 

1266 if int_mask is not None: 

1267 # set to 0 to avoid OOB on masked values (setting iNaT only works for int64) 

1268 # will get converted to NaT later 

1269 data = data.copy() 

1270 data[int_mask] = 0 

1271 

1272 data, copy = dtl.ensure_arraylike_for_datetimelike( 

1273 data, copy, cls_name="TimedeltaArray" 

1274 ) 

1275 

1276 # Convert whatever we have into timedelta64[ns] dtype 

1277 if data.dtype == object or is_string_dtype(data.dtype): 

1278 # no need to make a copy, need to convert if string-dtyped 

1279 data = _objects_to_td64ns(data, unit=unit, errors=errors) 

1280 copy = False 

1281 

1282 elif is_integer_dtype(data.dtype): 

1283 # treat as multiples of the given unit 

1284 try: 

1285 data, copy_made = _ints_to_td64ns(data, unit=unit) 

1286 except OutOfBoundsTimedelta: 

1287 if errors == "raise": 

1288 raise 

1289 data = data.astype(object) 

1290 if int_mask is not None: 

1291 data[int_mask] = None 

1292 data = _objects_to_td64ns(data, unit=unit, errors=errors) 

1293 copy_made = True 

1294 else: 

1295 if int_mask is not None: 

1296 data[int_mask] = iNaT 

1297 # copy was already made before calling ensure_arraylike_for_datetimelike 

1298 copy_made = True 

1299 copy = copy and not copy_made 

1300 

1301 elif is_float_dtype(data.dtype): 

1302 # cast the unit, multiply base/frac separately 

1303 # to avoid precision issues from float -> int 

1304 if isinstance(data.dtype, ExtensionDtype): 

1305 mask = data._mask 

1306 data = data._data 

1307 else: 

1308 mask = np.isnan(data) 

1309 

1310 if unit is not None and unit != "ns": 

1311 # if all non-NaN entries are round, treat these like ints 

1312 # (which results in 'us' unit instead of 'ns') 

1313 with np.errstate(invalid="ignore"): 

1314 int_data = data.astype(np.int64) 

1315 # On ARM, float-to-int64 overflow saturates to INT64_MAX 

1316 # instead of wrapping, which makes the data == int_data 

1317 # check pass incorrectly for OOB values like float(2**63). 

1318 # Exclude values outside the int64 domain from the check. 

1319 i64 = np.iinfo(np.int64) 

1320 in_int64_range = (data >= np.float64(i64.min)) & ( 

1321 data < np.float64(i64.max) 

1322 ) 

1323 all_round = (mask | (in_int64_range & (data == int_data))).all() 

1324 if all_round: 

1325 result = sequence_to_td64ns( 

1326 int_data, copy=False, unit=unit, errors=errors 

1327 ) 

1328 result[mask] = iNaT 

1329 return result 

1330 

1331 data = data.astype(np.float64, copy=False) 

1332 try: 

1333 converted = cast_from_unit_vectorized(data, unit or "ns") 

1334 except OutOfBoundsDatetime as err: 

1335 if errors == "raise": 

1336 raise OutOfBoundsTimedelta(*err.args) from err 

1337 # GH#66823 fall back to the element-wise path, which honors 

1338 # errors="coerce", like the integer branch above does. 

1339 data = np.where(mask, np.nan, data) 

1340 data = _objects_to_td64ns(data.astype(object), unit=unit, errors=errors) 

1341 else: 

1342 converted[mask] = iNaT 

1343 data = converted.view("m8[ns]") 

1344 copy = False 

1345 

1346 elif lib.is_np_dtype(data.dtype, "m"): 

1347 if data.dtype.byteorder == ">": 

1348 # GH#68342 supported units are otherwise stored as-is; the swap also 

1349 # has to precede the cast, whose finer->coarser branch views i8 raw 

1350 data = data.astype(data.dtype.newbyteorder("<")) 

1351 copy = False 

1352 

1353 if not is_supported_dtype(data.dtype): 

1354 # cast to closest supported unit, i.e. s or ns 

1355 new_dtype = get_supported_dtype(data.dtype) 

1356 data = astype_overflowsafe(data, dtype=new_dtype, copy=False) 

1357 copy = False 

1358 

1359 else: 

1360 # This includes datetime64-dtype, see GH#23539, GH#29794 

1361 raise TypeError(f"dtype {data.dtype} cannot be converted to timedelta64[ns]") 

1362 

1363 if not copy: 

1364 data = np.asarray(data) 

1365 else: 

1366 data = np.array(data, copy=copy) 

1367 

1368 assert data.dtype.kind == "m" 

1369 assert data.dtype != "m8" # i.e. not unit-less 

1370 

1371 return data 

1372 

1373 

1374def _ints_to_td64ns(data, unit: str = "ns") -> tuple[np.ndarray, bool]: 

1375 """ 

1376 Convert an ndarray with integer-dtype to timedelta64[ns] dtype, treating 

1377 the integers as multiples of the given timedelta unit. 

1378 

1379 Parameters 

1380 ---------- 

1381 data : numpy.ndarray with integer-dtype 

1382 unit : str, default "ns" 

1383 The timedelta unit to treat integers as multiples of. 

1384 

1385 Returns 

1386 ------- 

1387 numpy.ndarray : timedelta64[ns] array converted from data 

1388 bool : whether a copy was made 

1389 """ 

1390 copy_made = False 

1391 unit = unit if unit is not None else "ns" 

1392 

1393 if data.dtype != np.int64: 

1394 # GH#60677 unsigned integers > int64 max overflow silently 

1395 # when cast to int64 (which timedelta64 is backed by) 

1396 if data.dtype == np.dtype("uint64") and (data > np.iinfo(np.int64).max).any(): 

1397 raise OutOfBoundsTimedelta(f"Cannot convert input with unit '{unit}'") 

1398 # converting to int64 makes a copy, so we can avoid 

1399 # re-copying later 

1400 data = data.astype(np.int64) 

1401 copy_made = True 

1402 

1403 if unit != "ns": 

1404 dtype_str = f"timedelta64[{unit}]" 

1405 data = data.view(dtype_str) 

1406 

1407 if unit != "us": 

1408 data = astype_overflowsafe(data, dtype=np.dtype("timedelta64[us]")) 

1409 

1410 # the astype conversion makes a copy, so we can avoid re-copying later 

1411 copy_made = True 

1412 

1413 else: 

1414 data = data.view("timedelta64[ns]") 

1415 

1416 return data, copy_made 

1417 

1418 

1419def _objects_to_td64ns( 

1420 data, unit=None, errors: DateTimeErrorChoices = "raise" 

1421) -> np.ndarray: 

1422 """ 

1423 Convert an object-dtyped or string-dtyped array into a 

1424 timedelta64[ns]-dtyped array. 

1425 

1426 Parameters 

1427 ---------- 

1428 data : ndarray or Index 

1429 unit : str, default "ns" 

1430 The timedelta unit to treat integers as multiples of. 

1431 Must not be specified if the data contains a str. 

1432 errors : {"raise", "coerce", "ignore"}, default "raise" 

1433 How to handle elements that cannot be converted to timedelta64[ns]. 

1434 See ``pandas.to_timedelta`` for details. 

1435 

1436 Returns 

1437 ------- 

1438 numpy.ndarray : timedelta64[ns] array converted from data 

1439 

1440 Raises 

1441 ------ 

1442 ValueError : Data cannot be converted to timedelta64[ns]. 

1443 

1444 Notes 

1445 ----- 

1446 Unlike `pandas.to_timedelta`, if setting `errors=ignore` will not cause 

1447 errors to be ignored; they are caught and subsequently ignored at a 

1448 higher level. 

1449 """ 

1450 # coerce Index to np.ndarray, converting string-dtype if necessary 

1451 values = np.asarray(data, dtype=np.object_) 

1452 

1453 result = array_to_timedelta64(values, unit=unit, errors=errors) 

1454 return result 

1455 

1456 

1457def _validate_td64_dtype(dtype) -> DtypeObj: 

1458 dtype = pandas_dtype(dtype) 

1459 if dtype == np.dtype("m8"): 

1460 # no precision disallowed GH#24806 

1461 msg = ( 

1462 "Passing in 'timedelta' dtype with no precision is not allowed. " 

1463 "Please pass in 'timedelta64[ns]' instead." 

1464 ) 

1465 raise ValueError(msg) 

1466 

1467 if not lib.is_np_dtype(dtype, "m"): 

1468 raise ValueError(f"dtype '{dtype}' is invalid, should be np.timedelta64 dtype") 

1469 elif not is_supported_dtype(dtype): 

1470 raise ValueError("Supported timedelta64 resolutions are 's', 'ms', 'us', 'ns'") 

1471 

1472 return dtype