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

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

919 statements  

1from __future__ import annotations 

2 

3from datetime import ( 

4 datetime, 

5 timedelta, 

6) 

7from functools import wraps 

8import operator 

9from typing import ( 

10 TYPE_CHECKING, 

11 Any, 

12 Literal, 

13 Self, 

14 TypeAlias, 

15 cast, 

16 final, 

17 overload, 

18) 

19import warnings 

20 

21import numpy as np 

22 

23from pandas._config import using_string_dtype 

24from pandas._config.config import _global_config as config 

25 

26from pandas._libs import ( 

27 algos, 

28 groupby as libgroupby, 

29 lib, 

30) 

31from pandas._libs.tslibs import ( 

32 BaseOffset, 

33 Day, 

34 IncompatibleFrequency, 

35 NaT, 

36 NaTType, 

37 Period, 

38 Tick, 

39 Timedelta, 

40 Timestamp, 

41 add_overflowsafe, 

42 astype_overflowsafe, 

43 get_unit_from_dtype, 

44 iNaT, 

45 ints_to_pydatetime, 

46 ints_to_pytimedelta, 

47 periods_per_day, 

48) 

49from pandas._libs.tslibs.fields import ( 

50 RoundTo, 

51 round_nsint64, 

52) 

53from pandas._libs.tslibs.np_datetime import compare_mismatched_resolutions 

54from pandas._libs.tslibs.timedeltas import get_unit_for_round 

55from pandas._libs.tslibs.timestamps import integer_op_not_supported 

56from pandas._typing import ( 

57 ArrayLike, 

58 AxisInt, 

59 DatetimeLikeScalar, 

60 Dtype, 

61 DtypeObj, 

62 F, 

63 InterpolateOptions, 

64 NpDtype, 

65 PositionalIndexer2D, 

66 PositionalIndexerTuple, 

67 ScalarIndexer, 

68 SequenceIndexer, 

69 TimeAmbiguous, 

70 TimeNonexistent, 

71 npt, 

72) 

73from pandas.compat.numpy import function as nv 

74from pandas.errors import ( 

75 AbstractMethodError, 

76 InvalidComparison, 

77 Pandas4Warning, 

78 PerformanceWarning, 

79) 

80from pandas.util._decorators import ( 

81 cache_readonly, 

82) 

83from pandas.util._exceptions import find_stack_level 

84 

85from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike 

86from pandas.core.dtypes.common import ( 

87 is_all_strings, 

88 is_integer_dtype, 

89 is_list_like, 

90 is_object_dtype, 

91 is_string_dtype, 

92 pandas_dtype, 

93) 

94from pandas.core.dtypes.dtypes import ( 

95 ArrowDtype, 

96 CategoricalDtype, 

97 DatetimeTZDtype, 

98 ExtensionDtype, 

99 PeriodDtype, 

100) 

101from pandas.core.dtypes.generic import ( 

102 ABCCategorical, 

103 ABCMultiIndex, 

104) 

105from pandas.core.dtypes.missing import ( 

106 is_valid_na_for_dtype, 

107 isna, 

108) 

109 

110from pandas.core import ( 

111 algorithms, 

112 missing, 

113 nanops, 

114 ops, 

115) 

116from pandas.core.algorithms import ( 

117 isin, 

118 map_array, 

119 unique1d, 

120) 

121from pandas.core.array_algos import datetimelike_accumulations 

122from pandas.core.arraylike import OpsMixin 

123from pandas.core.arrays._mixins import ( 

124 NDArrayBackedExtensionArray, 

125 ravel_compat, 

126) 

127from pandas.core.arrays.arrow.array import ArrowExtensionArray 

128from pandas.core.arrays.base import ExtensionArray 

129from pandas.core.arrays.integer import IntegerArray 

130from pandas.core.construction import ( 

131 array as pd_array, 

132 ensure_wrapped_if_datetimelike, 

133 extract_array, 

134) 

135from pandas.core.indexers import check_setitem_lengths 

136from pandas.core.ops.common import unpack_zerodim_and_defer 

137from pandas.core.ops.invalid import ( 

138 invalid_comparison, 

139 make_invalid_op, 

140) 

141 

142from pandas.tseries import frequencies 

143 

144if TYPE_CHECKING: 

145 from collections.abc import ( 

146 Callable, 

147 Iterator, 

148 Sequence, 

149 ) 

150 

151 from pandas._typing import TimeUnit 

152 

153 from pandas import Index 

154 from pandas.core.arrays import ( 

155 DatetimeArray, 

156 PeriodArray, 

157 TimedeltaArray, 

158 ) 

159 

160DTScalarOrNaT: TypeAlias = DatetimeLikeScalar | NaTType 

161 

162 

163def _make_unpacked_invalid_op(op_name: str): 

164 op = make_invalid_op(op_name) 

165 return unpack_zerodim_and_defer(op_name)(op) 

166 

167 

168def _period_dispatch(meth: F) -> F: 

169 """ 

170 For PeriodArray methods, dispatch to DatetimeArray and re-wrap the results 

171 in PeriodArray. We cannot use ._ndarray directly for the affected 

172 methods because the i8 data has different semantics on NaT values. 

173 """ 

174 

175 @wraps(meth) 

176 def new_meth(self, *args, **kwargs): 

177 if not isinstance(self.dtype, PeriodDtype): 

178 return meth(self, *args, **kwargs) 

179 

180 arr = self.view("M8[ns]") 

181 result = meth(arr, *args, **kwargs) 

182 if result is NaT: 

183 return NaT 

184 elif isinstance(result, Timestamp): 

185 return self._box_func(result._value) 

186 

187 res_i8 = result.view("i8") 

188 return self._from_backing_data(res_i8) 

189 

190 return cast("F", new_meth) 

191 

192 

193class DatetimeLikeArrayMixin(OpsMixin, NDArrayBackedExtensionArray): 

194 """ 

195 Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray 

196 

197 Assumes that __new__/__init__ defines: 

198 _ndarray 

199 """ 

200 

201 _is_recognized_dtype: Callable[[DtypeObj], bool] 

202 _recognized_scalars: tuple[type, ...] 

203 _ndarray: np.ndarray 

204 

205 @cache_readonly 

206 def _can_hold_na(self) -> bool: 

207 return True 

208 

209 def __init__( 

210 self, data, dtype: Dtype | None = None, freq=None, copy: bool = False 

211 ) -> None: 

212 raise AbstractMethodError(self) 

213 

214 @property 

215 def _scalar_type(self) -> type[DatetimeLikeScalar]: 

216 """ 

217 The scalar associated with this datelike 

218 

219 * PeriodArray : Period 

220 * DatetimeArray : Timestamp 

221 * TimedeltaArray : Timedelta 

222 """ 

223 raise AbstractMethodError(self) 

224 

225 def _scalar_from_string(self, value: str) -> DTScalarOrNaT: 

226 """ 

227 Construct a scalar type from a string. 

228 

229 Parameters 

230 ---------- 

231 value : str 

232 

233 Returns 

234 ------- 

235 Period, Timestamp, or Timedelta, or NaT 

236 Whatever the type of ``self._scalar_type`` is. 

237 

238 Notes 

239 ----- 

240 This should call ``self._check_compatible_with`` before 

241 unboxing the result. 

242 """ 

243 raise AbstractMethodError(self) 

244 

245 def _unbox_scalar( 

246 self, value: DTScalarOrNaT 

247 ) -> np.int64 | np.datetime64 | np.timedelta64: 

248 """ 

249 Unbox the integer value of a scalar `value`. 

250 

251 Parameters 

252 ---------- 

253 value : Period, Timestamp, Timedelta, or NaT 

254 Depending on subclass. 

255 

256 Returns 

257 ------- 

258 int 

259 

260 Examples 

261 -------- 

262 >>> arr = pd.array(np.array(["1970-01-01"], "datetime64[ns]")) 

263 >>> arr._unbox_scalar(arr[0]) 

264 np.datetime64('1970-01-01T00:00:00.000000000') 

265 """ 

266 raise AbstractMethodError(self) 

267 

268 def _check_compatible_with(self, other: DTScalarOrNaT) -> None: 

269 """ 

270 Verify that `self` and `other` are compatible. 

271 

272 * DatetimeArray verifies that the timezones (if any) match 

273 * PeriodArray verifies that the freq matches 

274 * Timedelta has no verification 

275 

276 In each case, NaT is considered compatible. 

277 

278 Parameters 

279 ---------- 

280 other 

281 

282 Raises 

283 ------ 

284 Exception 

285 """ 

286 raise AbstractMethodError(self) 

287 

288 # ------------------------------------------------------------------ 

289 

290 def _box_func(self, x): 

291 """ 

292 box function to get object from internal representation 

293 """ 

294 raise AbstractMethodError(self) 

295 

296 def _box_values(self, values) -> np.ndarray: 

297 """ 

298 apply box func to passed values 

299 """ 

300 return lib.map_infer(values, self._box_func, convert=False) 

301 

302 def __iter__(self) -> Iterator[Any]: 

303 if self.ndim > 1: 

304 return (self[n] for n in range(len(self))) 

305 else: 

306 return (self._box_func(v) for v in self.asi8) 

307 

308 @property 

309 def asi8(self) -> npt.NDArray[np.int64]: 

310 """ 

311 Integer representation of the values. 

312 

313 Returns 

314 ------- 

315 ndarray 

316 An ndarray with int64 dtype. 

317 """ 

318 # do not cache or you'll create a memory leak 

319 return self._ndarray.view("i8") 

320 

321 # ---------------------------------------------------------------- 

322 # Rendering Methods 

323 

324 def _format_native_types( 

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

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

327 """ 

328 Helper method for astype when converting to strings. 

329 

330 Returns 

331 ------- 

332 ndarray[str] 

333 """ 

334 raise AbstractMethodError(self) 

335 

336 def _formatter(self, boxed: bool = False) -> Callable[[object], str]: 

337 # TODO: Remove Datetime & DatetimeTZ formatters. 

338 return "'{}'".format 

339 

340 # ---------------------------------------------------------------- 

341 # Array-Like / EA-Interface Methods 

342 

343 def __array__( 

344 self, dtype: NpDtype | None = None, copy: bool | None = None 

345 ) -> np.ndarray: 

346 # used for Timedelta/DatetimeArray, overwritten by PeriodArray 

347 if is_object_dtype(dtype): 

348 if copy is False: 

349 raise ValueError( 

350 "Unable to avoid copy while creating an array as requested." 

351 ) 

352 return np.array(list(self), dtype=object) 

353 

354 if copy is True: 

355 return np.array(self._ndarray, dtype=dtype) 

356 

357 result = self._ndarray 

358 if self._readonly: 

359 result = result.view() 

360 result.flags.writeable = False 

361 return result 

362 

363 @overload 

364 def __getitem__(self, key: ScalarIndexer) -> DTScalarOrNaT: ... 

365 

366 @overload 

367 def __getitem__( 

368 self, 

369 key: SequenceIndexer | PositionalIndexerTuple, 

370 ) -> Self: ... 

371 

372 def __getitem__(self, key: PositionalIndexer2D) -> Self | DTScalarOrNaT: 

373 """ 

374 This getitem defers to the underlying array, which by-definition can 

375 only handle list-likes, slices, and integer scalars 

376 """ 

377 # Use cast as we know we will get back a DatetimeLikeArray or DTScalar, 

378 # but skip evaluating the Union at runtime for performance 

379 # (see https://github.com/pandas-dev/pandas/pull/44624) 

380 result = cast("Self | DTScalarOrNaT", super().__getitem__(key)) 

381 if lib.is_scalar(result): 

382 return result 

383 else: 

384 # At this point we know the result is an array. 

385 result = cast("Self", result) 

386 return result 

387 

388 # error: Argument 1 of "__setitem__" is incompatible with supertype 

389 # "ExtensionArray"; supertype defines the argument type as "Union[int, 

390 # ndarray]" 

391 def __setitem__( 

392 self, 

393 key: int | Sequence[int] | Sequence[bool] | slice, 

394 value: NaTType | Any | Sequence[Any], 

395 ) -> None: 

396 # I'm fudging the types a bit here. "Any" above really depends 

397 # on type(self). For PeriodArray, it's Period (or stuff coercible 

398 # to a period in from_sequence). For DatetimeArray, it's Timestamp... 

399 # I don't know if mypy can do that, possibly with Generics. 

400 # https://mypy.readthedocs.io/en/latest/generics.html 

401 

402 check_setitem_lengths(key, value, self) 

403 

404 # Calling super() before the no_op short-circuit means that we raise 

405 # on invalid 'value' even if this is a no-op, e.g. wrong-dtype empty array. 

406 super().__setitem__(key, value) 

407 

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

409 # Some notes on cases we don't have to handle here in the base class: 

410 # 1. PeriodArray.astype handles period -> period 

411 # 2. DatetimeArray.astype handles conversion between tz. 

412 # 3. DatetimeArray.astype handles datetime -> period 

413 dtype = pandas_dtype(dtype) 

414 

415 if dtype == object: 

416 if self.dtype.kind == "M": 

417 self = cast("DatetimeArray", self) 

418 # *much* faster than self._box_values 

419 # for e.g. test_get_loc_tuple_monotonic_above_size_cutoff 

420 i8data = self.asi8 

421 converted = ints_to_pydatetime( 

422 i8data, 

423 tz=self.tz, 

424 box="timestamp", 

425 reso=self._creso, 

426 ) 

427 return converted 

428 

429 elif self.dtype.kind == "m": 

430 return ints_to_pytimedelta(self._ndarray, box=True) 

431 

432 return self._box_values(self.asi8.ravel()).reshape(self.shape) 

433 

434 elif is_string_dtype(dtype): 

435 if isinstance(dtype, ExtensionDtype): 

436 arr_object = self._format_native_types(na_rep=dtype.na_value) # type: ignore[arg-type] 

437 cls = dtype.construct_array_type() 

438 return cls._from_sequence(arr_object, dtype=dtype, copy=False) 

439 else: 

440 return self._format_native_types() 

441 

442 elif isinstance(dtype, ExtensionDtype): 

443 return super().astype(dtype, copy=copy) 

444 elif dtype.kind in "iu": 

445 # we deliberately ignore int32 vs. int64 here. 

446 # See https://github.com/pandas-dev/pandas/issues/24381 for more. 

447 values = self.asi8 

448 if dtype != np.int64: 

449 raise TypeError( 

450 f"Converting from {self.dtype} to {dtype} is not supported. " 

451 "Do obj.astype('int64').astype(dtype) instead" 

452 ) 

453 

454 if copy: 

455 values = values.copy() 

456 return values 

457 elif (dtype.kind in "mM" and self.dtype != dtype) or dtype.kind == "f": 

458 # disallow conversion between datetime/timedelta, 

459 # and conversions for any datetimelike to float 

460 msg = f"Cannot cast {type(self).__name__} to dtype {dtype}" 

461 raise TypeError(msg) 

462 else: 

463 return np.asarray(self, dtype=dtype) 

464 

465 @overload # type: ignore[override] 

466 def view(self) -> Self: ... 

467 

468 @overload 

469 def view(self, dtype: Literal["M8[ns]"]) -> DatetimeArray: ... 

470 

471 @overload 

472 def view(self, dtype: Literal["m8[ns]"]) -> TimedeltaArray: ... 

473 

474 @overload 

475 def view(self, dtype: Dtype | None = ...) -> ArrayLike: ... 

476 

477 def view(self, dtype: Dtype | None = None) -> ArrayLike: 

478 # we need to explicitly call super() method as long as the `@overload`s 

479 # are present in this file. 

480 return super().view(dtype) 

481 

482 # ------------------------------------------------------------------ 

483 # Validation Methods 

484 # TODO: try to de-duplicate these, ensure identical behavior 

485 

486 def _validate_comparison_value(self, other): 

487 if isinstance(other, str): 

488 try: 

489 # GH#18435 strings get a pass from tzawareness compat 

490 other = self._scalar_from_string(other) 

491 except (ValueError, IncompatibleFrequency) as err: 

492 # failed to parse as Timestamp/Timedelta/Period 

493 raise InvalidComparison(other) from err 

494 

495 if isinstance(other, self._recognized_scalars) or other is NaT: 

496 # error: Argument 1 to "Timestamp" has incompatible type "object"; 

497 # expected "integer[Any] | float | str | date | datetime | 

498 # datetime64[date | int | None]" [arg-type] 

499 other = self._scalar_type(other) # type: ignore[arg-type] 

500 try: 

501 self._check_compatible_with(other) 

502 except TypeError as err: 

503 # e.g. tzawareness mismatch 

504 raise InvalidComparison(other) from err 

505 

506 elif not is_list_like(other): 

507 raise InvalidComparison(other) 

508 

509 elif len(other) != len(self): 

510 raise ValueError("Lengths must match") 

511 

512 else: 

513 try: 

514 other = self._validate_listlike(other, allow_object=True) 

515 self._check_compatible_with(other) 

516 except TypeError as err: 

517 if is_object_dtype(getattr(other, "dtype", None)): 

518 # We will have to operate element-wise 

519 pass 

520 else: 

521 raise InvalidComparison(other) from err 

522 

523 return other 

524 

525 def _validate_scalar( 

526 self, 

527 value, 

528 *, 

529 allow_listlike: bool = False, 

530 unbox: bool = True, 

531 ): 

532 """ 

533 Validate that the input value can be cast to our scalar_type. 

534 

535 Parameters 

536 ---------- 

537 value : object 

538 allow_listlike: bool, default False 

539 When raising an exception, whether the message should say 

540 listlike inputs are allowed. 

541 unbox : bool, default True 

542 Whether to unbox the result before returning. Note: unbox=False 

543 skips the setitem compatibility check. 

544 

545 Returns 

546 ------- 

547 self._scalar_type or NaT 

548 """ 

549 if isinstance(value, self._scalar_type): 

550 pass 

551 

552 elif isinstance(value, str): 

553 # NB: Careful about tzawareness 

554 try: 

555 value = self._scalar_from_string(value) 

556 except ValueError as err: 

557 msg = self._validation_error_message(value, allow_listlike) 

558 raise TypeError(msg) from err 

559 

560 elif is_valid_na_for_dtype(value, self.dtype): 

561 # GH#18295 

562 value = NaT 

563 

564 elif isna(value): 

565 # if we are dt64tz and value is dt64("NaT"), dont cast to NaT, 

566 # or else we'll fail to raise in _unbox_scalar 

567 msg = self._validation_error_message(value, allow_listlike) 

568 raise TypeError(msg) 

569 

570 elif isinstance(value, self._recognized_scalars): 

571 # error: Argument 1 to "Timestamp" has incompatible type "object"; expected 

572 # "integer[Any] | float | str | date | datetime | datetime64" 

573 value = self._scalar_type(value) # type: ignore[arg-type] 

574 

575 else: 

576 msg = self._validation_error_message(value, allow_listlike) 

577 raise TypeError(msg) 

578 

579 if not unbox: 

580 # NB: In general NDArrayBackedExtensionArray will unbox here; 

581 # this option exists to prevent a performance hit in 

582 # TimedeltaIndex.get_loc 

583 return value 

584 return self._unbox_scalar(value) 

585 

586 def _validation_error_message(self, value, allow_listlike: bool = False) -> str: 

587 """ 

588 Construct an exception message on validation error. 

589 

590 Some methods allow only scalar inputs, while others allow either scalar 

591 or listlike. 

592 

593 Parameters 

594 ---------- 

595 allow_listlike: bool, default False 

596 

597 Returns 

598 ------- 

599 str 

600 """ 

601 if hasattr(value, "dtype") and getattr(value, "ndim", 0) > 0: 

602 msg_got = f"{value.dtype} array" 

603 else: 

604 msg_got = f"'{type(value).__name__}'" 

605 if allow_listlike: 

606 msg = ( 

607 f"value should be a '{self._scalar_type.__name__}', 'NaT', " 

608 f"or array of those. Got {msg_got} instead." 

609 ) 

610 else: 

611 msg = ( 

612 f"value should be a '{self._scalar_type.__name__}' or 'NaT'. " 

613 f"Got {msg_got} instead." 

614 ) 

615 return msg 

616 

617 def _validate_listlike(self, value, allow_object: bool = False): 

618 if isinstance(value, type(self)): 

619 if self.dtype.kind in "mM" and not allow_object and self.unit != value.unit: # type: ignore[attr-defined] 

620 # error: "DatetimeLikeArrayMixin" has no attribute "as_unit" 

621 value = value.as_unit(self.unit, round_ok=False) # type: ignore[attr-defined] 

622 return value 

623 

624 if isinstance(value, list) and len(value) == 0: 

625 # We treat empty list as our own dtype. 

626 return type(self)._from_sequence([], dtype=self.dtype) 

627 

628 if hasattr(value, "dtype") and value.dtype == object: 

629 # `array` below won't do inference if value is an Index or Series. 

630 # so do so here. in the Index case, inferred_type may be cached. 

631 arr = np.asarray(value) 

632 flat = arr.ravel() if arr.ndim != 1 else arr 

633 converted = lib.maybe_convert_objects( 

634 flat, convert_non_numeric=True, dtype_if_all_nat=self.dtype 

635 ) 

636 converted = ensure_wrapped_if_datetimelike(converted) 

637 if isinstance(converted, type(self)): 

638 if ( 

639 self.dtype.kind in "mM" 

640 and not allow_object 

641 and self.unit != converted.unit # type: ignore[attr-defined] 

642 ): 

643 converted = converted.as_unit(self.unit, round_ok=False) # type: ignore[attr-defined] 

644 if arr.ndim != 1: 

645 converted = converted.reshape(arr.shape) 

646 return converted 

647 elif self.dtype.kind == "M" and lib.infer_dtype(value) == "date": 

648 # GH#65056 

649 warnings.warn( 

650 "Inferring datetime64 from data containing datetime.date " 

651 "objects is deprecated. In a future version, these will " 

652 "be left as object dtype. Use " 

653 "pd.to_datetime to explicitly convert " 

654 "to datetime64.", 

655 Pandas4Warning, 

656 stacklevel=find_stack_level(), 

657 ) 

658 value = type(self)._from_sequence(value) 

659 

660 if isinstance(value, list): 

661 value = construct_1d_object_array_from_listlike(value) 

662 if isinstance(value, np.ndarray) and value.dtype == object: 

663 # We need to call maybe_convert_objects here instead of in pd_array 

664 # so we can specify dtype_if_all_nat. 

665 value = lib.maybe_convert_objects( 

666 value, convert_non_numeric=True, dtype_if_all_nat=self.dtype 

667 ) 

668 # Do type inference if necessary up front (after unpacking 

669 # NumpyExtensionArray) 

670 # e.g. we passed PeriodIndex.values and got an ndarray of Periods 

671 value = extract_array(value, extract_numpy=True) 

672 value = pd_array(value) 

673 value = extract_array(value, extract_numpy=True) 

674 

675 if is_all_strings(value): 

676 # We got a StringArray 

677 try: 

678 # TODO: Could use from_sequence_of_strings if implemented 

679 # Note: passing dtype is necessary for PeriodArray tests 

680 value = type(self)._from_sequence(value, dtype=self.dtype) 

681 except ValueError: 

682 pass 

683 

684 if isinstance(value.dtype, CategoricalDtype): 

685 # e.g. we have a Categorical holding self.dtype 

686 if value.categories.dtype == self.dtype: 

687 # TODO: do we need equal dtype or just comparable? 

688 value = value._internal_get_values() 

689 value = extract_array(value, extract_numpy=True) 

690 

691 if allow_object and is_object_dtype(value.dtype): 

692 pass 

693 

694 elif not type(self)._is_recognized_dtype(value.dtype): 

695 msg = self._validation_error_message(value, True) 

696 raise TypeError(msg) 

697 

698 if self.dtype.kind in "mM" and not allow_object: 

699 # error: "DatetimeLikeArrayMixin" has no attribute "as_unit" 

700 value = value.as_unit(self.unit, round_ok=False) # type: ignore[attr-defined] 

701 return value 

702 

703 def _validate_setitem_value(self, value): 

704 if is_list_like(value): 

705 value = self._validate_listlike(value) 

706 else: 

707 return self._validate_scalar(value, allow_listlike=True) 

708 

709 return self._unbox(value) 

710 

711 @final 

712 def _unbox(self, other) -> np.int64 | np.datetime64 | np.timedelta64 | np.ndarray: 

713 """ 

714 Unbox either a scalar with _unbox_scalar or an instance of our own type. 

715 """ 

716 if lib.is_scalar(other): 

717 other = self._unbox_scalar(other) 

718 else: 

719 # same type as self 

720 self._check_compatible_with(other) 

721 other = other._ndarray 

722 return other 

723 

724 def fillna(self, value, limit: int | None = None, copy: bool = True) -> Self: 

725 # Fast path: single-pass Cython using iNaT sentinel. GH#42147 

726 if lib.is_scalar(value): 

727 if not self._hasna: 

728 return self.copy() if copy else self[:] 

729 try: 

730 validated = self._validate_setitem_value(value) 

731 except (ValueError, TypeError): 

732 pass 

733 else: 

734 if copy: 

735 new_ndarray = self._ndarray.copy() 

736 else: 

737 if self._readonly: 

738 raise ValueError("Cannot modify read-only array") 

739 new_ndarray = self._ndarray 

740 

741 arr_i8 = new_ndarray.view("i8") 

742 fill_i8 = np.array(validated, dtype=new_ndarray.dtype).view("i8")[()] 

743 algos.scalar_fillna_inplace( 

744 arr_i8, fill_i8, is_datetimelike=True, limit=limit 

745 ) 

746 

747 return type(self)._simple_new(new_ndarray, dtype=self.dtype) 

748 

749 return super().fillna(value, limit=limit, copy=copy) 

750 

751 # ------------------------------------------------------------------ 

752 # Additional array methods 

753 # These are not part of the EA API, but we implement them because 

754 # pandas assumes they're there. 

755 

756 @ravel_compat 

757 def map(self, mapper, na_action: Literal["ignore"] | None = None): 

758 from pandas import Index 

759 

760 result = map_array(self, mapper, na_action=na_action) 

761 result = Index(result, copy=False) 

762 

763 if isinstance(result, ABCMultiIndex): 

764 return result.to_numpy() 

765 else: 

766 return result._data 

767 

768 def isin(self, values: ArrayLike) -> npt.NDArray[np.bool_]: 

769 """ 

770 Compute boolean array of whether each value is found in the 

771 passed set of values. 

772 

773 Parameters 

774 ---------- 

775 values : np.ndarray or ExtensionArray 

776 

777 Returns 

778 ------- 

779 ndarray[bool] 

780 """ 

781 if values.dtype.kind in "fiuc": 

782 # TODO: de-duplicate with equals, validate_comparison_value 

783 return np.zeros(self.shape, dtype=bool) 

784 

785 values = ensure_wrapped_if_datetimelike(values) 

786 

787 if not isinstance(values, type(self)): 

788 if values.dtype == object: 

789 values = lib.maybe_convert_objects( 

790 values, # type: ignore[arg-type] 

791 convert_non_numeric=True, 

792 dtype_if_all_nat=self.dtype, 

793 ) 

794 if values.dtype != object: 

795 return self.isin(values) 

796 else: 

797 # TODO: Deprecate this case 

798 # https://github.com/pandas-dev/pandas/pull/58645/files#r1604055791 

799 return isin(self.astype(object), values) 

800 return np.zeros(self.shape, dtype=bool) 

801 

802 if self.dtype.kind in "mM": 

803 self = cast("DatetimeArray | TimedeltaArray", self) 

804 # Cast to the higher resolution to avoid silently truncating 

805 # finer-resolution values, which could lead to false matches. 

806 self, values = self._ensure_matching_resos(values) 

807 

808 try: 

809 # error: Argument 1 to "_check_compatible_with" of "DatetimeLikeArrayMixin" 

810 # has incompatible type "ExtensionArray | ndarray[Any, Any]"; expected 

811 # "Period | Timestamp | Timedelta | NaTType" 

812 self._check_compatible_with(values) # type: ignore[arg-type] 

813 except (TypeError, ValueError): 

814 # Includes tzawareness mismatch and IncompatibleFrequencyError 

815 return np.zeros(self.shape, dtype=bool) 

816 

817 # error: Item "ExtensionArray" of "ExtensionArray | ndarray[Any, Any]" 

818 # has no attribute "asi8" 

819 return isin(self.asi8, values.asi8) # type: ignore[union-attr] 

820 

821 # ------------------------------------------------------------------ 

822 # Null Handling 

823 

824 def isna(self) -> npt.NDArray[np.bool_]: 

825 return self._isnan 

826 

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

828 def _isnan(self) -> npt.NDArray[np.bool_]: 

829 """ 

830 return if each value is nan 

831 """ 

832 return self.asi8 == iNaT 

833 

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

835 def _hasna(self) -> bool: 

836 """ 

837 return if I have any nans; enables various perf speedups 

838 """ 

839 return bool(self._isnan.any()) 

840 

841 def _maybe_mask_results( 

842 self, result: np.ndarray, fill_value=iNaT, convert=None 

843 ) -> np.ndarray: 

844 """ 

845 Parameters 

846 ---------- 

847 result : np.ndarray 

848 fill_value : object, default iNaT 

849 convert : str, dtype or None 

850 

851 Returns 

852 ------- 

853 result : ndarray with values replace by the fill_value 

854 

855 mask the result if needed, convert to the provided dtype if its not 

856 None 

857 

858 This is an internal routine. 

859 """ 

860 if self._hasna: 

861 if convert: 

862 result = result.astype(convert) 

863 if fill_value is None: 

864 fill_value = np.nan 

865 np.putmask(result, self._isnan, fill_value) 

866 return result 

867 

868 # ------------------------------------------------------------------ 

869 # Frequency Properties/Methods 

870 

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

872 def _inferred_freq_str(self) -> str | None: 

873 """ 

874 Internal version of inferred_freq without deprecation warning. 

875 """ 

876 if self.ndim != 1: 

877 return None 

878 try: 

879 return frequencies.infer_freq_str(self) 

880 except ValueError: 

881 return None 

882 

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

884 def inferred_freq(self) -> str | BaseOffset | None: 

885 """ 

886 Tries to return a string representing a frequency generated by infer_freq. 

887 

888 .. deprecated:: 3.1.0 

889 A future version of pandas will return a :class:`BaseOffset` instead of 

890 a string. Use 

891 ``pd.set_option('future.infer_freq_returns_offset', True)`` to opt 

892 in to the future behavior. 

893 

894 Returns None if it can't autodetect the frequency. 

895 

896 See Also 

897 -------- 

898 DatetimeIndex.freqstr : Return the frequency object as a string if it's set, 

899 otherwise None. 

900 

901 Examples 

902 -------- 

903 For DatetimeIndex: 

904 

905 >>> idx = pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"]) 

906 >>> idx.inferred_freq # doctest: +SKIP 

907 '2D' 

908 

909 For TimedeltaIndex: 

910 

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

912 >>> tdelta_idx 

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

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

915 >>> tdelta_idx.inferred_freq # doctest: +SKIP 

916 '10D' 

917 """ 

918 return frequencies.maybe_convert_inferred_freq( 

919 self._inferred_freq_str, "inferred_freq" 

920 ) 

921 

922 # monotonicity/uniqueness properties are called via frequencies.infer_freq, 

923 # see GH#23789 

924 

925 @property 

926 def _is_monotonic_increasing(self) -> bool: 

927 return algos.is_monotonic(self.asi8, timelike=True)[0] 

928 

929 @property 

930 def _is_monotonic_decreasing(self) -> bool: 

931 return algos.is_monotonic(self.asi8, timelike=True)[1] 

932 

933 @property 

934 def _is_unique(self) -> bool: 

935 return len(unique1d(self.asi8.ravel("K"))) == self.size 

936 

937 # ------------------------------------------------------------------ 

938 # Arithmetic Methods 

939 

940 def _cmp_method(self, other, op): 

941 if self.ndim > 1 and getattr(other, "shape", None) == self.shape: 

942 # TODO: handle 2D-like listlikes 

943 return op(self.ravel(), other.ravel()).reshape(self.shape) 

944 

945 if is_list_like(other): 

946 if not isinstance( 

947 other, (list, np.ndarray, ExtensionArray) 

948 ) and not ops.has_castable_attr(other): 

949 warnings.warn( 

950 f"Operation with {type(other).__name__} is deprecated. " 

951 "In a future version these will be treated as scalar-like. " 

952 "To retain the old behavior, explicitly wrap in a Series " 

953 "instead.", 

954 Pandas4Warning, 

955 stacklevel=find_stack_level(), 

956 ) 

957 

958 try: 

959 other = self._validate_comparison_value(other) 

960 except InvalidComparison: 

961 if hasattr(other, "dtype") and isinstance(other.dtype, ArrowDtype): 

962 return NotImplemented 

963 return invalid_comparison(self, other, op) 

964 

965 dtype = getattr(other, "dtype", None) 

966 if is_object_dtype(dtype): 

967 # We have to use comp_method_OBJECT_ARRAY instead of numpy 

968 # comparison otherwise it would raise when comparing to None 

969 result = ops.comp_method_OBJECT_ARRAY( 

970 op, np.asarray(self.astype(object)), other 

971 ) 

972 return result 

973 if other is NaT: 

974 if op is operator.ne: 

975 result = np.ones(self.shape, dtype=bool) 

976 else: 

977 result = np.zeros(self.shape, dtype=bool) 

978 return result 

979 

980 if not isinstance(self.dtype, PeriodDtype): 

981 self = cast("TimelikeOps", self) 

982 if self._creso != other._creso: 

983 if not isinstance(other, type(self)): 

984 # i.e. Timedelta/Timestamp, cast to ndarray and let 

985 # compare_mismatched_resolutions handle broadcasting 

986 try: 

987 # GH#52080 see if we can losslessly cast to shared unit 

988 other = other.as_unit(self.unit, round_ok=False) 

989 except ValueError: 

990 other_arr = np.array(other.asm8) 

991 return compare_mismatched_resolutions( 

992 self._ndarray, other_arr, op 

993 ) 

994 else: 

995 other_arr = other._ndarray 

996 return compare_mismatched_resolutions(self._ndarray, other_arr, op) 

997 

998 other_vals = self._unbox(other) 

999 # GH#37462 comparison on i8 values is almost 2x faster than M8/m8 

1000 result = op(self._ndarray.view("i8"), other_vals.view("i8")) 

1001 

1002 o_mask = isna(other) 

1003 mask = self._isnan | o_mask 

1004 if mask.any(): 

1005 nat_result = op is operator.ne 

1006 np.putmask(result, mask, nat_result) 

1007 

1008 return result 

1009 

1010 # pow is invalid for all three subclasses; TimedeltaArray will override 

1011 # the multiplication and division ops 

1012 __pow__ = _make_unpacked_invalid_op("__pow__") 

1013 __rpow__ = _make_unpacked_invalid_op("__rpow__") 

1014 __mul__ = _make_unpacked_invalid_op("__mul__") 

1015 __rmul__ = _make_unpacked_invalid_op("__rmul__") 

1016 __truediv__ = _make_unpacked_invalid_op("__truediv__") 

1017 __rtruediv__ = _make_unpacked_invalid_op("__rtruediv__") 

1018 __floordiv__ = _make_unpacked_invalid_op("__floordiv__") 

1019 __rfloordiv__ = _make_unpacked_invalid_op("__rfloordiv__") 

1020 __mod__ = _make_unpacked_invalid_op("__mod__") 

1021 __rmod__ = _make_unpacked_invalid_op("__rmod__") 

1022 __divmod__ = _make_unpacked_invalid_op("__divmod__") 

1023 __rdivmod__ = _make_unpacked_invalid_op("__rdivmod__") 

1024 

1025 @final 

1026 def _get_i8_values_and_mask( 

1027 self, other 

1028 ) -> tuple[int | npt.NDArray[np.int64], npt.NDArray[np.bool_] | None]: 

1029 """ 

1030 Get the int64 values and b_mask to pass to add_overflowsafe. 

1031 """ 

1032 if isinstance(other, Period): 

1033 i8values = other.ordinal 

1034 mask = None 

1035 elif isinstance(other, (Timestamp, Timedelta)): 

1036 i8values = other._value 

1037 mask = None 

1038 else: 

1039 # PeriodArray, DatetimeArray, TimedeltaArray 

1040 mask = other._isnan 

1041 i8values = other.asi8 

1042 return i8values, mask 

1043 

1044 @final 

1045 def _add_datetimelike_scalar(self, other) -> DatetimeArray: 

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

1047 raise TypeError( 

1048 f"cannot add {type(self).__name__} and {type(other).__name__}" 

1049 ) 

1050 

1051 self = cast("TimedeltaArray", self) 

1052 

1053 from pandas.core.arrays import DatetimeArray 

1054 from pandas.core.arrays.datetimes import tz_to_dtype 

1055 

1056 assert other is not NaT 

1057 if isna(other): 

1058 # i.e. np.datetime64("NaT") 

1059 # In this case we specifically interpret NaT as a datetime, not 

1060 # the timedelta interpretation we would get by returning self + NaT 

1061 result = self._ndarray + NaT.to_datetime64().astype(f"M8[{self.unit}]") 

1062 # Preserve our resolution 

1063 return DatetimeArray._simple_new(result, dtype=result.dtype) 

1064 

1065 other = Timestamp(other) 

1066 self, other = self._ensure_matching_resos(other) 

1067 self = cast("TimedeltaArray", self) 

1068 

1069 other_i8, o_mask = self._get_i8_values_and_mask(other) 

1070 result = add_overflowsafe(self.asi8, np.asarray(other_i8, dtype="i8")) 

1071 res_values = result.view(f"M8[{self.unit}]") 

1072 

1073 dtype = tz_to_dtype(tz=other.tz, unit=self.unit) 

1074 res_values = result.view(f"M8[{self.unit}]") 

1075 return DatetimeArray._simple_new(res_values, dtype=dtype) 

1076 

1077 @final 

1078 def _add_datetime_arraylike(self, other: DatetimeArray) -> DatetimeArray: 

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

1080 raise TypeError( 

1081 f"cannot add {type(self).__name__} and {type(other).__name__}" 

1082 ) 

1083 

1084 # defer to DatetimeArray.__add__ 

1085 return other + self 

1086 

1087 @final 

1088 def _sub_datetimelike_scalar( 

1089 self, other: datetime | np.datetime64 

1090 ) -> TimedeltaArray: 

1091 if self.dtype.kind != "M": 

1092 raise TypeError(f"cannot subtract a datelike from a {type(self).__name__}") 

1093 

1094 self = cast("DatetimeArray", self) 

1095 # subtract a datetime from myself, yielding an ndarray[timedelta64[ns]] 

1096 

1097 if isna(other): 

1098 # i.e. np.datetime64("NaT") 

1099 return self - NaT 

1100 

1101 ts = Timestamp(other) 

1102 

1103 self, ts = self._ensure_matching_resos(ts) 

1104 return self._sub_datetimelike(ts) 

1105 

1106 @final 

1107 def _sub_datetime_arraylike(self, other: DatetimeArray) -> TimedeltaArray: 

1108 if self.dtype.kind != "M": 

1109 raise TypeError(f"cannot subtract a datelike from a {type(self).__name__}") 

1110 

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

1112 raise ValueError("cannot add indices of unequal length") 

1113 

1114 self = cast("DatetimeArray", self) 

1115 

1116 self, other = self._ensure_matching_resos(other) 

1117 return self._sub_datetimelike(other) 

1118 

1119 @final 

1120 def _sub_datetimelike(self, other: Timestamp | DatetimeArray) -> TimedeltaArray: 

1121 self = cast("DatetimeArray", self) 

1122 

1123 from pandas.core.arrays import TimedeltaArray 

1124 

1125 try: 

1126 self._assert_tzawareness_compat(other) 

1127 except TypeError as err: 

1128 new_message = str(err).replace("compare", "subtract") 

1129 raise type(err)(new_message) from err 

1130 

1131 other_i8, o_mask = self._get_i8_values_and_mask(other) 

1132 res_values = add_overflowsafe(self.asi8, np.asarray(-other_i8, dtype="i8")) 

1133 res_m8 = res_values.view(f"timedelta64[{self.unit}]") 

1134 

1135 return TimedeltaArray._simple_new(res_m8, dtype=res_m8.dtype) 

1136 

1137 @final 

1138 def _add_period(self, other: Period) -> PeriodArray: 

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

1140 raise TypeError(f"cannot add Period to a {type(self).__name__}") 

1141 

1142 # We will wrap in a PeriodArray and defer to the reversed operation 

1143 from pandas.core.arrays.period import PeriodArray 

1144 

1145 i8vals = np.broadcast_to(other.ordinal, self.shape) 

1146 dtype = PeriodDtype(other.freq) 

1147 parr = PeriodArray(i8vals, dtype=dtype) 

1148 return parr + self 

1149 

1150 def _add_offset(self, offset): 

1151 raise AbstractMethodError(self) 

1152 

1153 def _add_timedeltalike_scalar(self, other): 

1154 """ 

1155 Add a delta of a timedeltalike 

1156 

1157 Returns 

1158 ------- 

1159 Same type as self 

1160 """ 

1161 if isna(other): 

1162 # i.e np.timedelta64("NaT") 

1163 new_values = np.empty(self.shape, dtype="i8").view(self._ndarray.dtype) 

1164 new_values.fill(iNaT) 

1165 return type(self)._simple_new(new_values, dtype=self.dtype) 

1166 

1167 # PeriodArray overrides, so we only get here with DTA/TDA 

1168 self = cast("DatetimeArray | TimedeltaArray", self) 

1169 other = Timedelta(other) 

1170 self, other = self._ensure_matching_resos(other) 

1171 return self._add_timedeltalike(other) 

1172 

1173 def _add_timedelta_arraylike(self, other: TimedeltaArray) -> Self: 

1174 """ 

1175 Add a delta of a TimedeltaIndex 

1176 

1177 Returns 

1178 ------- 

1179 Same type as self 

1180 """ 

1181 # overridden by PeriodArray 

1182 

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

1184 raise ValueError("cannot add indices of unequal length") 

1185 

1186 self, other = cast( 

1187 "DatetimeArray | TimedeltaArray", self 

1188 )._ensure_matching_resos(other) 

1189 return self._add_timedeltalike(other) 

1190 

1191 @final 

1192 def _add_timedeltalike(self, other: Timedelta | TimedeltaArray) -> Self: 

1193 other_i8, o_mask = self._get_i8_values_and_mask(other) 

1194 new_values = add_overflowsafe(self.asi8, np.asarray(other_i8, dtype="i8")) 

1195 res_values = new_values.view(self._ndarray.dtype) 

1196 

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

1198 

1199 @final 

1200 def _add_nat(self) -> Self: 

1201 """ 

1202 Add pd.NaT to self 

1203 """ 

1204 if isinstance(self.dtype, PeriodDtype): 

1205 raise TypeError( 

1206 f"Cannot add {type(self).__name__} and {type(NaT).__name__}" 

1207 ) 

1208 

1209 # GH#19124 pd.NaT is treated like a timedelta for both timedelta 

1210 # and datetime dtypes 

1211 result = np.empty(self.shape, dtype=np.int64) 

1212 result.fill(iNaT) 

1213 result = result.view(self._ndarray.dtype) # preserve reso 

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

1215 

1216 @final 

1217 def _sub_nat(self) -> np.ndarray: 

1218 """ 

1219 Subtract pd.NaT from self 

1220 """ 

1221 # GH#19124 Timedelta - datetime is not in general well-defined. 

1222 # We make an exception for pd.NaT, which in this case quacks 

1223 # like a timedelta. 

1224 # For datetime64 dtypes by convention we treat NaT as a datetime, so 

1225 # this subtraction returns a timedelta64 dtype. 

1226 # For period dtype, timedelta64 is a close-enough return dtype. 

1227 result = np.empty(self.shape, dtype=np.int64) 

1228 result.fill(iNaT) 

1229 if self.dtype.kind in "mM": 

1230 # We can retain unit in dtype 

1231 self = cast("DatetimeArray| TimedeltaArray", self) 

1232 return result.view(f"timedelta64[{self.unit}]") 

1233 else: 

1234 return result.view("timedelta64[ns]") 

1235 

1236 @final 

1237 def _sub_periodlike(self, other: Period | PeriodArray) -> npt.NDArray[np.object_]: 

1238 # If the operation is well-defined, we return an object-dtype ndarray 

1239 # of DateOffsets. Null entries are filled with pd.NaT 

1240 if not isinstance(self.dtype, PeriodDtype): 

1241 raise TypeError( 

1242 f"cannot subtract {type(other).__name__} from {type(self).__name__}" 

1243 ) 

1244 

1245 self = cast("PeriodArray", self) 

1246 self._check_compatible_with(other) 

1247 

1248 other_i8, o_mask = self._get_i8_values_and_mask(other) 

1249 # GH#66552 the difference is a count of periods, not an ordinal, so 

1250 # INT64_MIN is a legitimate answer here and not the NaT sentinel. 

1251 new_i8_data = add_overflowsafe( 

1252 self.asi8, np.asarray(-other_i8, dtype="i8"), sentinel_ok=True 

1253 ) 

1254 # multiply by python ints: numpy's scalar multiply spuriously reports 

1255 # overflow for a np.int64 count of INT64_MIN on Windows 

1256 counts = new_i8_data.ravel().tolist() 

1257 # dtype=object is necessary for the length-zero case, where numpy 

1258 # would otherwise infer float64 (GH#40624) 

1259 new_data = np.array( 

1260 [self.freq.base * count for count in counts], dtype=object 

1261 ).reshape(new_i8_data.shape) 

1262 

1263 if o_mask is None: 

1264 # i.e. Period scalar 

1265 mask = self._isnan 

1266 else: 

1267 # i.e. PeriodArray 

1268 mask = self._isnan | o_mask 

1269 new_data[mask] = NaT 

1270 return new_data 

1271 

1272 @final 

1273 def _addsub_object_array(self, other: npt.NDArray[np.object_], op) -> np.ndarray: 

1274 """ 

1275 Add or subtract array-like of DateOffset objects 

1276 

1277 Parameters 

1278 ---------- 

1279 other : np.ndarray[object] 

1280 op : {operator.add, operator.sub} 

1281 

1282 Returns 

1283 ------- 

1284 np.ndarray[object] 

1285 Except in fastpath case with length 1 where we operate on the 

1286 contained scalar. 

1287 """ 

1288 assert op in [operator.add, operator.sub] 

1289 if len(other) == 1 and self.ndim == 1: 

1290 # Note: without this special case, we could annotate return type 

1291 # as ndarray[object] 

1292 # If both 1D then broadcasting is unambiguous 

1293 return op(self, other[0]) 

1294 

1295 if config["mode"]["performance_warnings"]: 

1296 warnings.warn( 

1297 "Adding/subtracting object-dtype array to " 

1298 f"{type(self).__name__} not vectorized.", 

1299 PerformanceWarning, 

1300 stacklevel=find_stack_level(), 

1301 ) 

1302 

1303 # Caller is responsible for broadcasting if necessary 

1304 assert self.shape == other.shape, (self.shape, other.shape) 

1305 

1306 res_values = op(self.astype("O"), np.asarray(other)) 

1307 return res_values 

1308 

1309 def _accumulate(self, name: str, *, skipna: bool = True, **kwargs) -> Self: 

1310 if name not in {"cummin", "cummax"}: 

1311 raise TypeError(f"Accumulation {name} not supported for {type(self)}") 

1312 

1313 op = getattr(datetimelike_accumulations, name) 

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

1315 

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

1317 

1318 @unpack_zerodim_and_defer("__add__") 

1319 def __add__(self, other): 

1320 other_dtype = getattr(other, "dtype", None) 

1321 other = ensure_wrapped_if_datetimelike(other) 

1322 

1323 # scalar others 

1324 if other is NaT: 

1325 result: np.ndarray | DatetimeLikeArrayMixin = self._add_nat() 

1326 elif isinstance(other, (Tick, timedelta, np.timedelta64)): 

1327 result = self._add_timedeltalike_scalar(other) 

1328 elif isinstance(other, Day) and lib.is_np_dtype(self.dtype, "Mm"): 

1329 # We treat this as Tick-like 

1330 td = Timedelta(days=other.n).as_unit("s") 

1331 result = self._add_timedeltalike_scalar(td) 

1332 elif isinstance(other, BaseOffset): 

1333 # specifically _not_ a Tick 

1334 result = self._add_offset(other) 

1335 elif isinstance(other, (datetime, np.datetime64)): 

1336 result = self._add_datetimelike_scalar(other) 

1337 elif isinstance(other, Period) and lib.is_np_dtype(self.dtype, "m"): 

1338 result = self._add_period(other) 

1339 elif lib.is_integer(other): 

1340 # This check must come after the check for np.timedelta64 

1341 # as is_integer returns True for these 

1342 if not isinstance(self.dtype, PeriodDtype): 

1343 raise integer_op_not_supported(self) 

1344 obj = cast("PeriodArray", self) 

1345 result = obj._addsub_int_array_or_scalar(other * obj.dtype._n, operator.add) 

1346 

1347 # array-like others 

1348 elif lib.is_np_dtype(other_dtype, "m"): 

1349 # TimedeltaIndex, ndarray[timedelta64] 

1350 result = self._add_timedelta_arraylike(other) 

1351 elif is_object_dtype(other_dtype): 

1352 # e.g. Array/Index of DateOffset objects 

1353 result = self._addsub_object_array(other, operator.add) 

1354 elif lib.is_np_dtype(other_dtype, "M") or isinstance( 

1355 other_dtype, DatetimeTZDtype 

1356 ): 

1357 # DatetimeIndex, ndarray[datetime64] 

1358 return self._add_datetime_arraylike(other) 

1359 elif is_integer_dtype(other_dtype): 

1360 if not isinstance(self.dtype, PeriodDtype): 

1361 raise integer_op_not_supported(self) 

1362 obj = cast("PeriodArray", self) 

1363 result = obj._addsub_int_array_or_scalar(other * obj.dtype._n, operator.add) 

1364 elif other_dtype is not None and other_dtype.kind == "b": 

1365 # GH#68452 is_integer_dtype is False for bool, so without this bool 

1366 # operands fall through to numpy, which reads True as a one-unit 

1367 # timedelta. Period, where ints are legal, keeps its object-path raise 

1368 if isinstance(self.dtype, PeriodDtype): 

1369 return NotImplemented 

1370 raise integer_op_not_supported(self) 

1371 else: 

1372 # Includes Categorical, other ExtensionArrays 

1373 # For PeriodDtype, if self is a TimedeltaArray and other is a 

1374 # PeriodArray with a timedelta-like (i.e. Tick) freq, this 

1375 # operation is valid. Defer to the PeriodArray implementation. 

1376 # In remaining cases, this will end up raising TypeError. 

1377 return NotImplemented 

1378 

1379 if isinstance(result, np.ndarray) and lib.is_np_dtype(result.dtype, "m"): 

1380 from pandas.core.arrays import TimedeltaArray 

1381 

1382 return TimedeltaArray._from_sequence(result, dtype=result.dtype) 

1383 return result 

1384 

1385 def __radd__(self, other): 

1386 # alias for __add__ 

1387 return self.__add__(other) 

1388 

1389 @unpack_zerodim_and_defer("__sub__") 

1390 def __sub__(self, other): 

1391 other_dtype = getattr(other, "dtype", None) 

1392 other = ensure_wrapped_if_datetimelike(other) 

1393 

1394 # scalar others 

1395 if other is NaT: 

1396 result: np.ndarray | DatetimeLikeArrayMixin = self._sub_nat() 

1397 elif isinstance(other, (Tick, timedelta, np.timedelta64)): 

1398 result = self._add_timedeltalike_scalar(-other) 

1399 elif isinstance(other, Day) and lib.is_np_dtype(self.dtype, "Mm"): 

1400 # We treat this as Tick-like 

1401 td = Timedelta(days=other.n).as_unit("s") 

1402 result = self._add_timedeltalike_scalar(-td) 

1403 elif isinstance(other, BaseOffset): 

1404 # specifically _not_ a Tick 

1405 result = self._add_offset(-other) 

1406 elif isinstance(other, (datetime, np.datetime64)): 

1407 result = self._sub_datetimelike_scalar(other) 

1408 elif lib.is_integer(other): 

1409 # This check must come after the check for np.timedelta64 

1410 # as is_integer returns True for these 

1411 if not isinstance(self.dtype, PeriodDtype): 

1412 raise integer_op_not_supported(self) 

1413 obj = cast("PeriodArray", self) 

1414 result = obj._addsub_int_array_or_scalar(other * obj.dtype._n, operator.sub) 

1415 

1416 elif isinstance(other, Period): 

1417 result = self._sub_periodlike(other) 

1418 

1419 # array-like others 

1420 elif lib.is_np_dtype(other_dtype, "m"): 

1421 # TimedeltaIndex, ndarray[timedelta64] 

1422 result = self._add_timedelta_arraylike(-other) 

1423 elif is_object_dtype(other_dtype): 

1424 # e.g. Array/Index of DateOffset objects 

1425 result = self._addsub_object_array(other, operator.sub) 

1426 elif lib.is_np_dtype(other_dtype, "M") or isinstance( 

1427 other_dtype, DatetimeTZDtype 

1428 ): 

1429 # DatetimeIndex, ndarray[datetime64] 

1430 result = self._sub_datetime_arraylike(other) 

1431 elif isinstance(other_dtype, PeriodDtype): 

1432 # PeriodIndex 

1433 result = self._sub_periodlike(other) 

1434 elif is_integer_dtype(other_dtype): 

1435 if not isinstance(self.dtype, PeriodDtype): 

1436 raise integer_op_not_supported(self) 

1437 obj = cast("PeriodArray", self) 

1438 result = obj._addsub_int_array_or_scalar(other * obj.dtype._n, operator.sub) 

1439 elif other_dtype is not None and other_dtype.kind == "b": 

1440 # GH#68452, see __add__ 

1441 if isinstance(self.dtype, PeriodDtype): 

1442 return NotImplemented 

1443 raise integer_op_not_supported(self) 

1444 else: 

1445 # Includes ExtensionArrays, float_dtype 

1446 return NotImplemented 

1447 

1448 if isinstance(result, np.ndarray) and lib.is_np_dtype(result.dtype, "m"): 

1449 from pandas.core.arrays import TimedeltaArray 

1450 

1451 return TimedeltaArray._from_sequence(result, dtype=result.dtype) 

1452 return result 

1453 

1454 def __rsub__(self, other): 

1455 other_dtype = getattr(other, "dtype", None) 

1456 other_is_dt64 = lib.is_np_dtype(other_dtype, "M") or isinstance( 

1457 other_dtype, DatetimeTZDtype 

1458 ) 

1459 

1460 if other_is_dt64 and lib.is_np_dtype(self.dtype, "m"): 

1461 # ndarray[datetime64] cannot be subtracted from self, so 

1462 # we need to wrap in DatetimeArray/Index and flip the operation 

1463 if lib.is_scalar(other): 

1464 # i.e. np.datetime64 object 

1465 return Timestamp(other) - self 

1466 if not isinstance(other, DatetimeLikeArrayMixin): 

1467 # Avoid down-casting DatetimeIndex 

1468 from pandas.core.arrays import DatetimeArray 

1469 

1470 other = DatetimeArray._from_sequence(other, dtype=other.dtype) 

1471 return other - self 

1472 elif self.dtype.kind == "M" and hasattr(other, "dtype") and not other_is_dt64: 

1473 # GH#19959 datetime - datetime is well-defined as timedelta, 

1474 # but any other type - datetime is not well-defined. 

1475 raise TypeError( 

1476 f"cannot subtract {type(self).__name__} from " 

1477 f"{type(other).__name__}[{other.dtype}]" 

1478 ) 

1479 elif isinstance(self.dtype, PeriodDtype) and lib.is_np_dtype(other_dtype, "m"): 

1480 # TODO: Can we simplify/generalize these cases at all? 

1481 raise TypeError(f"cannot subtract {type(self).__name__} from {other.dtype}") 

1482 elif lib.is_np_dtype(self.dtype, "m"): 

1483 self = cast("TimedeltaArray", self) 

1484 return (-self) + other 

1485 

1486 flipped = self - other 

1487 if flipped.dtype.kind == "M": 

1488 # GH#59571 give a more helpful exception message 

1489 raise TypeError( 

1490 f"cannot subtract {type(self).__name__} from {type(other).__name__}" 

1491 ) 

1492 # We get here with e.g. datetime objects 

1493 return -flipped 

1494 

1495 def __iadd__(self, other) -> Self: 

1496 result = self + other 

1497 self[:] = result[:] 

1498 return self 

1499 

1500 def __isub__(self, other) -> Self: 

1501 result = self - other 

1502 self[:] = result[:] 

1503 return self 

1504 

1505 # -------------------------------------------------------------- 

1506 # Reductions 

1507 

1508 @_period_dispatch 

1509 def _quantile( 

1510 self, 

1511 qs: npt.NDArray[np.float64], 

1512 interpolation: str, 

1513 ) -> Self: 

1514 return super()._quantile(qs=qs, interpolation=interpolation) 

1515 

1516 @_period_dispatch 

1517 def min(self, *, axis: AxisInt | None = None, skipna: bool = True, **kwargs): 

1518 """ 

1519 Return the minimum value of the Array or minimum along 

1520 an axis. 

1521 

1522 See Also 

1523 -------- 

1524 numpy.ndarray.min 

1525 Index.min : Return the minimum value in an Index. 

1526 Series.min : Return the minimum value in a Series. 

1527 """ 

1528 nv.validate_min((), kwargs) 

1529 nv.validate_minmax_axis(axis, self.ndim) 

1530 

1531 result = nanops.nanmin(self._ndarray, axis=axis, skipna=skipna) 

1532 return self._wrap_reduction_result(axis, result) 

1533 

1534 @_period_dispatch 

1535 def max(self, *, axis: AxisInt | None = None, skipna: bool = True, **kwargs): 

1536 """ 

1537 Return the maximum value of the Array or maximum along 

1538 an axis. 

1539 

1540 See Also 

1541 -------- 

1542 numpy.ndarray.max 

1543 Index.max : Return the maximum value in an Index. 

1544 Series.max : Return the maximum value in a Series. 

1545 """ 

1546 nv.validate_max((), kwargs) 

1547 nv.validate_minmax_axis(axis, self.ndim) 

1548 

1549 result = nanops.nanmax(self._ndarray, axis=axis, skipna=skipna) 

1550 return self._wrap_reduction_result(axis, result) 

1551 

1552 def mean(self, *, skipna: bool = True, axis: AxisInt | None = 0): 

1553 """ 

1554 Return the mean value of the Array. 

1555 

1556 Parameters 

1557 ---------- 

1558 skipna : bool, default True 

1559 Whether to ignore any NaT elements. 

1560 axis : int, optional, default 0 

1561 Axis for the function to be applied on. 

1562 

1563 Returns 

1564 ------- 

1565 scalar 

1566 Timestamp or Timedelta. 

1567 

1568 See Also 

1569 -------- 

1570 numpy.ndarray.mean : Returns the average of array elements along a given axis. 

1571 Series.mean : Return the mean value in a Series. 

1572 

1573 Notes 

1574 ----- 

1575 mean is only defined for Datetime and Timedelta dtypes, not for Period. 

1576 

1577 Examples 

1578 -------- 

1579 For :class:`pandas.DatetimeIndex`: 

1580 

1581 >>> idx = pd.date_range("2001-01-01 00:00", periods=3) 

1582 >>> idx 

1583 DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'], 

1584 dtype='datetime64[us]', freq='D') 

1585 >>> idx.mean() 

1586 Timestamp('2001-01-02 00:00:00') 

1587 

1588 For :class:`pandas.TimedeltaIndex`: 

1589 

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

1591 >>> tdelta_idx 

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

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

1594 >>> tdelta_idx.mean() 

1595 Timedelta('2 days 00:00:00') 

1596 """ 

1597 if isinstance(self.dtype, PeriodDtype): 

1598 # See discussion in GH#24757 

1599 raise TypeError( 

1600 f"mean is not implemented for {type(self).__name__} since the " 

1601 "meaning is ambiguous. An alternative is " 

1602 "obj.to_timestamp(how='start').mean()" 

1603 ) 

1604 

1605 result = nanops.nanmean( 

1606 self._ndarray, axis=axis, skipna=skipna, mask=self.isna() 

1607 ) 

1608 return self._wrap_reduction_result(axis, result) 

1609 

1610 @_period_dispatch 

1611 def median(self, *, axis: AxisInt | None = None, skipna: bool = True, **kwargs): 

1612 nv.validate_median((), kwargs) 

1613 

1614 if axis is not None and abs(axis) >= self.ndim: 

1615 raise ValueError("abs(axis) must be less than ndim") 

1616 

1617 result = nanops.nanmedian(self._ndarray, axis=axis, skipna=skipna) 

1618 return self._wrap_reduction_result(axis, result) 

1619 

1620 def _mode(self, dropna: bool = True): 

1621 mask = None 

1622 if dropna: 

1623 mask = self.isna() 

1624 

1625 i8modes, _ = algorithms.mode(self.view("i8"), mask=mask) 

1626 npmodes = i8modes.view(self._ndarray.dtype) 

1627 npmodes = cast("np.ndarray", npmodes) 

1628 return self._from_backing_data(npmodes) 

1629 

1630 # ------------------------------------------------------------------ 

1631 # GroupBy Methods 

1632 

1633 def _groupby_op( 

1634 self, 

1635 *, 

1636 how: str, 

1637 has_dropped_na: bool, 

1638 min_count: int, 

1639 ngroups: int, 

1640 ids: npt.NDArray[np.intp], 

1641 **kwargs, 

1642 ): 

1643 dtype = self.dtype 

1644 if dtype.kind == "M": 

1645 # Adding/multiplying datetimes is not valid 

1646 if how in ["sum", "prod", "cumsum", "cumprod", "var", "skew", "kurt"]: 

1647 raise TypeError(f"datetime64 type does not support operation '{how}'") 

1648 if how in ["any", "all"]: 

1649 # GH#34479 

1650 raise TypeError(nanops.dt64_any_all_msg(how)) 

1651 

1652 elif isinstance(dtype, PeriodDtype): 

1653 # Adding/multiplying Periods is not valid 

1654 if how in ["sum", "prod", "cumsum", "cumprod", "var", "skew", "kurt"]: 

1655 raise TypeError(f"Period type does not support {how} operations") 

1656 if how in ["any", "all"]: 

1657 # GH#34479 

1658 raise TypeError( 

1659 f"'{how}' with PeriodDtype is no longer supported. " 

1660 f"Use (obj != pd.Period(ordinal=0, freq=freq)).{how}() instead." 

1661 ) 

1662 # timedeltas we can add but not multiply 

1663 elif how in ["prod", "cumprod", "skew", "kurt", "var"]: 

1664 raise TypeError(f"timedelta64 type does not support {how} operations") 

1665 

1666 # All of the functions implemented here are ordinal, so we can 

1667 # operate on the tz-naive equivalents 

1668 npvalues = self._ndarray.view("M8[ns]") 

1669 

1670 from pandas.core.groupby.ops import WrappedCythonOp 

1671 

1672 op = WrappedCythonOp(how=how, has_dropped_na=has_dropped_na) 

1673 

1674 res_values = op._cython_op_ndim_compat( 

1675 npvalues, 

1676 min_count=min_count, 

1677 ngroups=ngroups, 

1678 comp_ids=ids, 

1679 mask=None, 

1680 **kwargs, 

1681 ) 

1682 

1683 if op.how in op.cast_blocklist: 

1684 # i.e. how in ["rank"], since other cast_blocklist methods don't go 

1685 # through cython_operation 

1686 return res_values 

1687 

1688 # We did a view to M8[ns] above, now we go the other direction 

1689 assert res_values.dtype == "M8[ns]" 

1690 if how in ["std", "sem"]: 

1691 from pandas.core.arrays import TimedeltaArray 

1692 

1693 if isinstance(self.dtype, PeriodDtype): 

1694 raise TypeError("'std' and 'sem' are not valid for PeriodDtype") 

1695 self = cast("DatetimeArray | TimedeltaArray", self) 

1696 new_dtype = f"m8[{self.unit}]" 

1697 res_values = res_values.view(new_dtype) 

1698 return TimedeltaArray._simple_new(res_values, dtype=res_values.dtype) 

1699 

1700 res_values = res_values.view(self._ndarray.dtype) 

1701 return self._from_backing_data(res_values) 

1702 

1703 def _groupby_quantile( 

1704 self, 

1705 *, 

1706 qs: npt.NDArray[np.float64], 

1707 interpolation: Literal["linear", "lower", "higher", "nearest", "midpoint"], 

1708 ids: npt.NDArray[np.intp], 

1709 ngroups: int, 

1710 starts: npt.NDArray[np.int64], 

1711 ends: npt.NDArray[np.int64], 

1712 ) -> ArrayLike: 

1713 nqs = len(qs) 

1714 mask = self.isna() 

1715 i8vals = self.asi8 

1716 

1717 # Block manager may store DatetimeArray/TimedeltaArray as 2D (1, N) 

1718 ncols = 1 

1719 if self.ndim == 2: 

1720 ncols = self._ndarray.shape[0] 

1721 

1722 out = np.empty((ncols, ngroups, nqs), dtype=np.float64) 

1723 for col in range(ncols): 

1724 if self.ndim == 2: 

1725 col_mask = mask[col] 

1726 col_vals = i8vals[col] 

1727 else: 

1728 col_mask = mask 

1729 col_vals = i8vals 

1730 

1731 libgroupby.group_quantile( 

1732 out[col], 

1733 values=col_vals, 

1734 mask=col_mask, 

1735 labels=ids, 

1736 qs=qs, 

1737 interpolation=interpolation, 

1738 starts=starts, 

1739 ends=ends, 

1740 result_mask=None, 

1741 is_datetimelike=True, 

1742 ) 

1743 

1744 if self.ndim == 1: 

1745 out = out.ravel("K") # type: ignore[assignment] 

1746 else: 

1747 out = out.reshape(ncols, ngroups * nqs) # type: ignore[assignment] 

1748 

1749 # All-NA groups come back as NaN; casting NaN to i8 is platform 

1750 # dependent (0 on some architectures), so map them to iNaT explicitly. 

1751 na_mask = np.isnan(out) 

1752 out[na_mask] = 0 

1753 out = out.astype("i8") 

1754 out[na_mask] = iNaT 

1755 out = out.view(self._ndarray.dtype) 

1756 return self._from_backing_data(out) 

1757 

1758 

1759class DatelikeOps(DatetimeLikeArrayMixin): 

1760 """ 

1761 Common ops for DatetimeIndex/PeriodIndex, but not TimedeltaIndex. 

1762 """ 

1763 

1764 def strftime(self, date_format: str) -> npt.NDArray[np.object_]: 

1765 """ 

1766 Convert to Index using specified date_format. 

1767 

1768 Return an Index of formatted strings specified by date_format, which 

1769 supports the same string format as the python standard library. Details 

1770 of the string format can be found in `python string format 

1771 doc <https://docs.python.org/3/library/datetime.html 

1772 #strftime-and-strptime-behavior>`__. 

1773 

1774 Formats supported by the C `strftime` API but not by the python string format 

1775 doc (such as `"%R"`, `"%r"`) are not officially supported and should be 

1776 preferably replaced with their supported equivalents (such as `"%H:%M"`, 

1777 `"%I:%M:%S %p"`). 

1778 

1779 Note that `PeriodIndex` support additional directives, detailed in 

1780 `Period.strftime`. 

1781 

1782 Parameters 

1783 ---------- 

1784 date_format : str 

1785 Date format string (e.g. "%%Y-%%m-%%d"). 

1786 

1787 Returns 

1788 ------- 

1789 ndarray[object] 

1790 NumPy ndarray of formatted strings. 

1791 

1792 See Also 

1793 -------- 

1794 to_datetime : Convert the given argument to datetime. 

1795 DatetimeIndex.normalize : Return DatetimeIndex with times to midnight. 

1796 DatetimeIndex.round : Round the DatetimeIndex to the specified freq. 

1797 DatetimeIndex.floor : Floor the DatetimeIndex to the specified freq. 

1798 Timestamp.strftime : Format a single Timestamp. 

1799 Period.strftime : Format a single Period. 

1800 

1801 Examples 

1802 -------- 

1803 >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), periods=3, freq="s") 

1804 >>> rng.strftime("%B %d, %Y, %r") 

1805 Index(['March 10, 2018, 09:00:00 AM', 'March 10, 2018, 09:00:01 AM', 

1806 'March 10, 2018, 09:00:02 AM'], 

1807 dtype='str') 

1808 """ 

1809 result = self._format_native_types(date_format=date_format, na_rep=np.nan) 

1810 if using_string_dtype(): 

1811 from pandas import StringDtype 

1812 

1813 return pd_array(result, dtype=StringDtype(na_value=np.nan)) # type: ignore[return-value] 

1814 return result.astype(object, copy=False) 

1815 

1816 

1817_ITER_CHUNKSIZE = 10_000 

1818 

1819 

1820class TimelikeOps(DatetimeLikeArrayMixin): 

1821 """ 

1822 Common ops for TimedeltaIndex/DatetimeIndex, but not PeriodIndex. 

1823 """ 

1824 

1825 @classmethod 

1826 def _validate_dtype(cls, values, dtype): 

1827 raise AbstractMethodError(cls) 

1828 

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

1830 raise AbstractMethodError(self) 

1831 

1832 def __iter__(self) -> Iterator[Any]: 

1833 if self.ndim > 1: 

1834 for i in range(len(self)): 

1835 yield self[i] 

1836 else: 

1837 # convert in chunks of 10k for efficiency 

1838 data = self._ndarray 

1839 length = len(self) 

1840 chunksize = _ITER_CHUNKSIZE 

1841 chunks = (length // chunksize) + 1 

1842 for i in range(chunks): 

1843 start_i = i * chunksize 

1844 end_i = min((i + 1) * chunksize, length) 

1845 yield from self._iter_convert_chunk(data[start_i:end_i]) 

1846 

1847 @final 

1848 @classmethod 

1849 def _validate_frequency(cls, index, freq: BaseOffset) -> None: 

1850 """ 

1851 Validate that a frequency is compatible with the values of a given 

1852 Datetime Array/Index or Timedelta Array/Index 

1853 

1854 Parameters 

1855 ---------- 

1856 index : DatetimeIndex or TimedeltaIndex 

1857 The index on which to determine if the given frequency is valid 

1858 freq : DateOffset 

1859 The frequency to validate 

1860 """ 

1861 inferred = index._inferred_freq_str 

1862 if index.size == 0 or inferred == freq.freqstr: 

1863 return None 

1864 

1865 if getattr(index.dtype, "tz", None) is not None and not isinstance(freq, Tick): 

1866 # GH#55499 non-tick offsets do wall-time arithmetic, so validate 

1867 # against wall times; avoids raising on ambiguous times. 

1868 index = index.tz_localize(None) 

1869 

1870 try: 

1871 on_freq = cls._generate_range( 

1872 start=index[0], 

1873 end=None, 

1874 periods=len(index), 

1875 freq=freq, 

1876 unit=index.unit, 

1877 ) 

1878 if not lib.array_equivalent_bytes(index.asi8, on_freq.asi8): 

1879 raise ValueError 

1880 except ValueError as err: 

1881 if "non-fixed" in str(err): 

1882 # non-fixed frequencies are not meaningful for timedelta64; 

1883 # we retain that error message 

1884 raise err 

1885 # GH#11587 the main way this is reached is if the `np.array_equal` 

1886 # check above is False. This can also be reached if index[0] 

1887 # is `NaT`, in which case the call to `cls._generate_range` will 

1888 # raise a ValueError, which we re-raise with a more targeted 

1889 # message. 

1890 raise ValueError( 

1891 f"Inferred frequency {inferred} from passed values " 

1892 f"does not conform to passed frequency {freq.freqstr}" 

1893 ) from err 

1894 

1895 @classmethod 

1896 def _generate_range( 

1897 cls, start, end, periods: int | None, freq, *args, **kwargs 

1898 ) -> Self: 

1899 raise AbstractMethodError(cls) 

1900 

1901 # -------------------------------------------------------------- 

1902 

1903 @cache_readonly 

1904 def _creso(self) -> int: 

1905 return get_unit_from_dtype(self._ndarray.dtype) 

1906 

1907 @cache_readonly 

1908 def unit(self) -> TimeUnit: 

1909 """ 

1910 The precision unit of the datetime data. 

1911 

1912 Returns the precision unit for the dtype. 

1913 It means the smallest time frame that can be stored within this dtype. 

1914 

1915 Returns 

1916 ------- 

1917 str 

1918 Unit string representation (e.g. "ns"). 

1919 

1920 See Also 

1921 -------- 

1922 TimelikeOps.as_unit : Converts to a specific unit. 

1923 

1924 Examples 

1925 -------- 

1926 >>> idx = pd.DatetimeIndex(["2020-01-02 01:02:03.004005006"]) 

1927 >>> idx.unit 

1928 'ns' 

1929 >>> idx.as_unit("s").unit 

1930 's' 

1931 """ 

1932 # error: Incompatible return value type (got "str", expected 

1933 # "Literal['s', 'ms', 'us', 'ns']") [return-value] 

1934 return dtype_to_unit(self.dtype) # type: ignore[return-value,arg-type] 

1935 

1936 def as_unit(self, unit: TimeUnit, round_ok: bool = True) -> Self: 

1937 """ 

1938 Convert to a dtype with the given unit resolution. 

1939 

1940 The limits of timestamp representation depend on the chosen resolution. 

1941 Different resolutions can be converted to each other through as_unit. 

1942 

1943 Parameters 

1944 ---------- 

1945 unit : {'s', 'ms', 'us', 'ns'} 

1946 round_ok : bool, default True 

1947 If False and the conversion requires rounding, raise ValueError. 

1948 

1949 Returns 

1950 ------- 

1951 same type as self 

1952 Converted to the specified unit. 

1953 

1954 See Also 

1955 -------- 

1956 Timestamp.as_unit : Convert to the given unit. 

1957 

1958 Examples 

1959 -------- 

1960 For :class:`pandas.DatetimeIndex`: 

1961 

1962 >>> idx = pd.DatetimeIndex(["2020-01-02 01:02:03.004005006"]) 

1963 >>> idx 

1964 DatetimeIndex(['2020-01-02 01:02:03.004005006'], 

1965 dtype='datetime64[ns]', freq=None) 

1966 >>> idx.as_unit("s") 

1967 DatetimeIndex(['2020-01-02 01:02:03'], dtype='datetime64[s]', freq=None) 

1968 

1969 For :class:`pandas.TimedeltaIndex`: 

1970 

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

1972 >>> tdelta_idx 

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

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

1975 >>> tdelta_idx.as_unit("s") 

1976 TimedeltaIndex(['1 days 00:03:00'], dtype='timedelta64[s]', freq=None) 

1977 """ 

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

1979 raise ValueError("Supported units are 's', 'ms', 'us', 'ns'") 

1980 

1981 dtype = np.dtype(f"{self.dtype.kind}8[{unit}]") 

1982 new_values = astype_overflowsafe(self._ndarray, dtype, round_ok=round_ok) 

1983 

1984 if isinstance(self.dtype, np.dtype): 

1985 new_dtype = new_values.dtype 

1986 else: 

1987 tz = cast("DatetimeArray", self).tz 

1988 new_dtype = DatetimeTZDtype(tz=tz, unit=unit) 

1989 

1990 return type(self)._simple_new( 

1991 new_values, 

1992 dtype=new_dtype, 

1993 ) 

1994 

1995 # TODO: annotate other as DatetimeArray | TimedeltaArray | Timestamp | Timedelta 

1996 # with the return type matching input type. TypeVar? 

1997 def _ensure_matching_resos(self, other): 

1998 if self._creso != other._creso: 

1999 # Just as with Timestamp/Timedelta, we cast to the higher resolution 

2000 if self._creso < other._creso: 

2001 self = self.as_unit(other.unit) 

2002 else: 

2003 other = other.as_unit(self.unit) 

2004 return self, other 

2005 

2006 # -------------------------------------------------------------- 

2007 

2008 def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs): 

2009 if ( 

2010 ufunc in [np.isnan, np.isinf, np.isfinite] 

2011 and len(inputs) == 1 

2012 and inputs[0] is self 

2013 ): 

2014 # numpy 1.18 changed isinf and isnan to not raise on dt64/td64 

2015 return getattr(ufunc, method)(self._ndarray, **kwargs) 

2016 

2017 return super().__array_ufunc__(ufunc, method, *inputs, **kwargs) 

2018 

2019 def _round(self, freq, mode, ambiguous, nonexistent): 

2020 # round the local times 

2021 if isinstance(self.dtype, DatetimeTZDtype): 

2022 # operate on naive timestamps, then convert back to aware 

2023 self = cast("DatetimeArray", self) 

2024 naive = self.tz_localize(None) 

2025 result = naive._round(freq, mode, ambiguous, nonexistent) 

2026 return result.tz_localize( 

2027 self.tz, ambiguous=ambiguous, nonexistent=nonexistent 

2028 ) 

2029 

2030 values = self.view("i8") 

2031 values = cast("np.ndarray", values) 

2032 nanos = get_unit_for_round(freq, self._creso) 

2033 if nanos == 0: 

2034 # GH 52761 

2035 return self.copy() 

2036 result_i8 = round_nsint64(values, mode, nanos) 

2037 result = self._maybe_mask_results(result_i8, fill_value=iNaT) 

2038 result = result.view(self._ndarray.dtype) 

2039 return self._simple_new(result, dtype=self.dtype) 

2040 

2041 def round( # type: ignore[override] 

2042 self, 

2043 freq, 

2044 ambiguous: TimeAmbiguous = "raise", 

2045 nonexistent: TimeNonexistent = "raise", 

2046 ) -> Self: 

2047 """ 

2048 Perform round operation on the data to the specified `freq`. 

2049 

2050 This method rounds each datetime value in the Series/Index to the 

2051 nearest specified frequency using standard rounding rules (round half 

2052 to even). 

2053 

2054 Parameters 

2055 ---------- 

2056 freq : str or Offset 

2057 The frequency level to round the index to. Must be a fixed 

2058 frequency like 's' (second) not 'ME' (month end). See 

2059 :ref:`frequency aliases <timeseries.offset_aliases>` for 

2060 a list of possible `freq` values. 

2061 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise' 

2062 Only relevant for DatetimeIndex: 

2063 

2064 - 'infer' will attempt to infer fall dst-transition hours based on 

2065 order. Requires that the timestamps are monotonically increasing. 

2066 - bool-ndarray where True signifies a DST time, False designates 

2067 a non-DST time (note that this flag is only applicable for 

2068 ambiguous times) 

2069 - 'NaT' will return NaT where there are ambiguous times 

2070 - 'raise' will raise a ValueError if there are ambiguous 

2071 times. 

2072 

2073 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \ 

2074 default 'raise' 

2075 A nonexistent time does not exist in a particular timezone 

2076 where clocks moved forward due to DST. 

2077 

2078 - 'shift_forward' will shift the nonexistent time forward to the 

2079 closest existing time 

2080 - 'shift_backward' will shift the nonexistent time backward to the 

2081 closest existing time 

2082 - 'NaT' will return NaT where there are nonexistent times 

2083 - timedelta objects will shift nonexistent times by the timedelta 

2084 - 'raise' will raise a ValueError if there are 

2085 nonexistent times. 

2086 

2087 Returns 

2088 ------- 

2089 DatetimeIndex, TimedeltaIndex, or Series 

2090 Index of the same type for a DatetimeIndex or TimedeltaIndex, 

2091 or a Series with the same index for a Series. 

2092 

2093 Raises 

2094 ------ 

2095 ValueError if the `freq` cannot be converted. 

2096 

2097 See Also 

2098 -------- 

2099 DatetimeIndex.floor : 

2100 Perform floor operation on the data to the specified `freq`. 

2101 DatetimeIndex.snap : 

2102 Snap time stamps to nearest occurring frequency. 

2103 

2104 Notes 

2105 ----- 

2106 If the timestamps have a timezone, rounding will take place relative to the 

2107 local ("wall") time and re-localized to the same timezone. When rounding 

2108 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to 

2109 control the re-localization behavior. 

2110 

2111 Examples 

2112 -------- 

2113 **DatetimeIndex** 

2114 

2115 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min") 

2116 >>> rng 

2117 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00', 

2118 '2018-01-01 12:01:00'], 

2119 dtype='datetime64[us]', freq='min') 

2120 

2121 >>> rng.round('h') 

2122 DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00', 

2123 '2018-01-01 12:00:00'], 

2124 dtype='datetime64[us]', freq=None) 

2125 

2126 **Series** 

2127 

2128 >>> pd.Series(rng).dt.round("h") 

2129 0 2018-01-01 12:00:00 

2130 1 2018-01-01 12:00:00 

2131 2 2018-01-01 12:00:00 

2132 dtype: datetime64[us] 

2133 

2134 When rounding near a daylight savings time transition, use ``ambiguous`` or 

2135 ``nonexistent`` to control how the timestamp should be re-localized. 

2136 

2137 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 03:30:00"], tz="Europe/Amsterdam") 

2138 

2139 >>> rng_tz.floor("2h", ambiguous=False) 

2140 DatetimeIndex(['2021-10-31 02:00:00+01:00'], 

2141 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2142 

2143 >>> rng_tz.floor("2h", ambiguous=True) 

2144 DatetimeIndex(['2021-10-31 02:00:00+02:00'], 

2145 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2146 """ 

2147 return self._round(freq, RoundTo.NEAREST_HALF_EVEN, ambiguous, nonexistent) 

2148 

2149 def floor( 

2150 self, 

2151 freq, 

2152 ambiguous: TimeAmbiguous = "raise", 

2153 nonexistent: TimeNonexistent = "raise", 

2154 ) -> Self: 

2155 """ 

2156 Perform floor operation on the data to the specified `freq`. 

2157 

2158 This method rounds each datetime value in the Series/Index down to 

2159 the specified frequency (i.e., towards negative infinity). 

2160 

2161 Parameters 

2162 ---------- 

2163 freq : str or Offset 

2164 The frequency level to floor the index to. Must be a fixed 

2165 frequency like 's' (second) not 'ME' (month end). See 

2166 :ref:`frequency aliases <timeseries.offset_aliases>` for 

2167 a list of possible `freq` values. 

2168 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise' 

2169 Only relevant for DatetimeIndex: 

2170 

2171 - 'infer' will attempt to infer fall dst-transition hours based on 

2172 order. Requires that the timestamps are monotonically increasing. 

2173 - bool-ndarray where True signifies a DST time, False designates 

2174 a non-DST time (note that this flag is only applicable for 

2175 ambiguous times) 

2176 - 'NaT' will return NaT where there are ambiguous times 

2177 - 'raise' will raise a ValueError if there are ambiguous 

2178 times. 

2179 

2180 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \ 

2181 default 'raise' 

2182 A nonexistent time does not exist in a particular timezone 

2183 where clocks moved forward due to DST. 

2184 

2185 - 'shift_forward' will shift the nonexistent time forward to the 

2186 closest existing time 

2187 - 'shift_backward' will shift the nonexistent time backward to the 

2188 closest existing time 

2189 - 'NaT' will return NaT where there are nonexistent times 

2190 - timedelta objects will shift nonexistent times by the timedelta 

2191 - 'raise' will raise a ValueError if there are 

2192 nonexistent times. 

2193 

2194 Returns 

2195 ------- 

2196 DatetimeIndex, TimedeltaIndex, or Series 

2197 Index of the same type for a DatetimeIndex or TimedeltaIndex, 

2198 or a Series with the same index for a Series. 

2199 

2200 Raises 

2201 ------ 

2202 ValueError if the `freq` cannot be converted. 

2203 

2204 See Also 

2205 -------- 

2206 DatetimeIndex.floor : 

2207 Perform floor operation on the data to the specified `freq`. 

2208 DatetimeIndex.snap : 

2209 Snap time stamps to nearest occurring frequency. 

2210 

2211 Notes 

2212 ----- 

2213 If the timestamps have a timezone, flooring will take place relative to the 

2214 local ("wall") time and re-localized to the same timezone. When flooring 

2215 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to 

2216 control the re-localization behavior. 

2217 

2218 Examples 

2219 -------- 

2220 **DatetimeIndex** 

2221 

2222 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min") 

2223 >>> rng 

2224 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00', 

2225 '2018-01-01 12:01:00'], 

2226 dtype='datetime64[us]', freq='min') 

2227 

2228 >>> rng.floor('h') 

2229 DatetimeIndex(['2018-01-01 11:00:00', '2018-01-01 12:00:00', 

2230 '2018-01-01 12:00:00'], 

2231 dtype='datetime64[us]', freq=None) 

2232 

2233 **Series** 

2234 

2235 >>> pd.Series(rng).dt.floor("h") 

2236 0 2018-01-01 11:00:00 

2237 1 2018-01-01 12:00:00 

2238 2 2018-01-01 12:00:00 

2239 dtype: datetime64[us] 

2240 

2241 When rounding near a daylight savings time transition, use ``ambiguous`` or 

2242 ``nonexistent`` to control how the timestamp should be re-localized. 

2243 

2244 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 03:30:00"], tz="Europe/Amsterdam") 

2245 

2246 >>> rng_tz.floor("2h", ambiguous=False) 

2247 DatetimeIndex(['2021-10-31 02:00:00+01:00'], 

2248 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2249 

2250 >>> rng_tz.floor("2h", ambiguous=True) 

2251 DatetimeIndex(['2021-10-31 02:00:00+02:00'], 

2252 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2253 """ 

2254 return self._round(freq, RoundTo.MINUS_INFTY, ambiguous, nonexistent) 

2255 

2256 def ceil( 

2257 self, 

2258 freq, 

2259 ambiguous: TimeAmbiguous = "raise", 

2260 nonexistent: TimeNonexistent = "raise", 

2261 ) -> Self: 

2262 """ 

2263 Perform ceil operation on the data to the specified `freq`. 

2264 

2265 This method rounds each datetime value in the Series/Index up to 

2266 the specified frequency (i.e., towards positive infinity). 

2267 

2268 Parameters 

2269 ---------- 

2270 freq : str or Offset 

2271 The frequency level to ceil the index to. Must be a fixed 

2272 frequency like 's' (second) not 'ME' (month end). See 

2273 :ref:`frequency aliases <timeseries.offset_aliases>` for 

2274 a list of possible `freq` values. 

2275 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise' 

2276 Only relevant for DatetimeIndex: 

2277 

2278 - 'infer' will attempt to infer fall dst-transition hours based on 

2279 order. Requires that the timestamps are monotonically increasing. 

2280 - bool-ndarray where True signifies a DST time, False designates 

2281 a non-DST time (note that this flag is only applicable for 

2282 ambiguous times) 

2283 - 'NaT' will return NaT where there are ambiguous times 

2284 - 'raise' will raise a ValueError if there are ambiguous 

2285 times. 

2286 

2287 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \ 

2288 default 'raise' 

2289 A nonexistent time does not exist in a particular timezone 

2290 where clocks moved forward due to DST. 

2291 

2292 - 'shift_forward' will shift the nonexistent time forward to the 

2293 closest existing time 

2294 - 'shift_backward' will shift the nonexistent time backward to the 

2295 closest existing time 

2296 - 'NaT' will return NaT where there are nonexistent times 

2297 - timedelta objects will shift nonexistent times by the timedelta 

2298 - 'raise' will raise a ValueError if there are 

2299 nonexistent times. 

2300 

2301 Returns 

2302 ------- 

2303 DatetimeIndex, TimedeltaIndex, or Series 

2304 Index of the same type for a DatetimeIndex or TimedeltaIndex, 

2305 or a Series with the same index for a Series. 

2306 

2307 Raises 

2308 ------ 

2309 ValueError if the `freq` cannot be converted. 

2310 

2311 See Also 

2312 -------- 

2313 DatetimeIndex.floor : 

2314 Perform floor operation on the data to the specified `freq`. 

2315 DatetimeIndex.snap : 

2316 Snap time stamps to nearest occurring frequency. 

2317 

2318 Notes 

2319 ----- 

2320 If the timestamps have a timezone, ceiling will take place relative to the 

2321 local ("wall") time and re-localized to the same timezone. When ceiling 

2322 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to 

2323 control the re-localization behavior. 

2324 

2325 Examples 

2326 -------- 

2327 **DatetimeIndex** 

2328 

2329 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min") 

2330 >>> rng 

2331 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00', 

2332 '2018-01-01 12:01:00'], 

2333 dtype='datetime64[us]', freq='min') 

2334 

2335 >>> rng.ceil('h') 

2336 DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00', 

2337 '2018-01-01 13:00:00'], 

2338 dtype='datetime64[us]', freq=None) 

2339 

2340 **Series** 

2341 

2342 >>> pd.Series(rng).dt.ceil("h") 

2343 0 2018-01-01 12:00:00 

2344 1 2018-01-01 12:00:00 

2345 2 2018-01-01 13:00:00 

2346 dtype: datetime64[us] 

2347 

2348 When rounding near a daylight savings time transition, use ``ambiguous`` or 

2349 ``nonexistent`` to control how the timestamp should be re-localized. 

2350 

2351 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 01:30:00"], tz="Europe/Amsterdam") 

2352 

2353 >>> rng_tz.ceil("h", ambiguous=False) 

2354 DatetimeIndex(['2021-10-31 02:00:00+01:00'], 

2355 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2356 

2357 >>> rng_tz.ceil("h", ambiguous=True) 

2358 DatetimeIndex(['2021-10-31 02:00:00+02:00'], 

2359 dtype='datetime64[us, Europe/Amsterdam]', freq=None) 

2360 """ 

2361 return self._round(freq, RoundTo.PLUS_INFTY, ambiguous, nonexistent) 

2362 

2363 # -------------------------------------------------------------- 

2364 # Reductions 

2365 

2366 def any(self, *, axis: AxisInt | None = None, skipna: bool = True) -> bool: 

2367 # GH#34479 the nanops call will raise a TypeError for non-td64 dtype 

2368 return nanops.nanany(self._ndarray, axis=axis, skipna=skipna, mask=self.isna()) 

2369 

2370 def all(self, *, axis: AxisInt | None = None, skipna: bool = True) -> bool: 

2371 # GH#34479 the nanops call will raise a TypeError for non-td64 dtype 

2372 

2373 return nanops.nanall(self._ndarray, axis=axis, skipna=skipna, mask=self.isna()) 

2374 

2375 # -------------------------------------------------------------- 

2376 # ExtensionArray Interface 

2377 

2378 def _values_for_json(self) -> np.ndarray: 

2379 # Small performance bump vs the base class which calls np.asarray(self) 

2380 if isinstance(self.dtype, np.dtype): 

2381 return self._ndarray 

2382 return super()._values_for_json() 

2383 

2384 def factorize( 

2385 self, 

2386 use_na_sentinel: bool = True, 

2387 sort: bool = False, 

2388 ): 

2389 if sort: 

2390 raise NotImplementedError( 

2391 f"The 'sort' keyword in {type(self).__name__}.factorize is not " 

2392 "supported. To factorize with sort, call pd.factorize(obj, sort=True) " 

2393 "instead." 

2394 ) 

2395 return super().factorize(use_na_sentinel=use_na_sentinel) 

2396 

2397 def interpolate( 

2398 self, 

2399 *, 

2400 method: InterpolateOptions, 

2401 axis: int, 

2402 index: Index, 

2403 limit, 

2404 limit_direction, 

2405 limit_area, 

2406 copy: bool, 

2407 **kwargs, 

2408 ) -> Self: 

2409 """ 

2410 See NDFrame.interpolate.__doc__. 

2411 """ 

2412 # NB: we return type(self) even if copy=False 

2413 if method != "linear": 

2414 raise NotImplementedError 

2415 

2416 if not copy: 

2417 out_data = self._ndarray 

2418 else: 

2419 out_data = self._ndarray.copy() 

2420 

2421 missing.interpolate_2d_inplace( 

2422 out_data, 

2423 method=method, 

2424 axis=axis, 

2425 index=index, 

2426 limit=limit, 

2427 limit_direction=limit_direction, 

2428 limit_area=limit_area, 

2429 **kwargs, 

2430 ) 

2431 if not copy: 

2432 return self 

2433 return type(self)._simple_new(out_data, dtype=self.dtype) 

2434 

2435 # -------------------------------------------------------------- 

2436 # Unsorted 

2437 

2438 @property 

2439 def _is_dates_only(self) -> bool: 

2440 """ 

2441 Check if we are round times at midnight (and no timezone), which will 

2442 be given a more compact __repr__ than other cases. For TimedeltaArray 

2443 we are checking for multiples of 24H. 

2444 """ 

2445 if not lib.is_np_dtype(self.dtype): 

2446 # i.e. we have a timezone 

2447 return False 

2448 

2449 values_int = self.asi8 

2450 consider_values = values_int != iNaT 

2451 reso = get_unit_from_dtype(self.dtype) 

2452 ppd = periods_per_day(reso) 

2453 

2454 # TODO: can we reuse is_date_array_normalized? would need a skipna kwd 

2455 # (first attempt at this was less performant than this implementation) 

2456 even_days = np.logical_and(consider_values, values_int % ppd != 0).sum() == 0 

2457 return even_days 

2458 

2459 

2460# ------------------------------------------------------------------- 

2461# Shared Constructor Helpers 

2462 

2463 

2464def ensure_arraylike_for_datetimelike( 

2465 data, copy: bool, cls_name: str 

2466) -> tuple[ArrayLike, bool]: 

2467 if not hasattr(data, "dtype"): 

2468 # e.g. list, tuple 

2469 if not isinstance(data, (list, tuple)) and np.ndim(data) == 0: 

2470 # i.e. generator 

2471 data = list(data) 

2472 

2473 data = construct_1d_object_array_from_listlike(data) 

2474 copy = False 

2475 elif isinstance(data, ABCMultiIndex): 

2476 raise TypeError(f"Cannot create a {cls_name} from a MultiIndex.") 

2477 else: 

2478 data = extract_array(data, extract_numpy=True) 

2479 

2480 if isinstance(data, IntegerArray) or ( 

2481 isinstance(data, ArrowExtensionArray) and data.dtype.kind in "iu" 

2482 ): 

2483 data = data.to_numpy("int64", na_value=iNaT) 

2484 copy = False 

2485 elif isinstance(data, ArrowExtensionArray): 

2486 data = data._maybe_convert_datelike_array() 

2487 data = data.to_numpy() 

2488 copy = False 

2489 elif not isinstance(data, (np.ndarray, ExtensionArray)): 

2490 # GH#24539 e.g. xarray, dask object 

2491 data = np.asarray(data) 

2492 

2493 elif isinstance(data, ABCCategorical): 

2494 # GH#18664 preserve tz in going DTI->Categorical->DTI 

2495 # TODO: cases where we need to do another pass through maybe_convert_dtype, 

2496 # e.g. the categories are timedelta64s 

2497 data = data.categories.take(data.codes, allow_fill=True, fill_value=NaT)._values 

2498 copy = False 

2499 

2500 if data.ndim == 0: 

2501 raise ValueError(f"Cannot construct {cls_name} from 0-dim input") 

2502 

2503 return data, copy 

2504 

2505 

2506@overload 

2507def validate_periods(periods: None) -> None: ... 

2508 

2509 

2510@overload 

2511def validate_periods(periods: int) -> int: ... 

2512 

2513 

2514def validate_periods(periods: int | None) -> int | None: 

2515 """ 

2516 If a `periods` argument is passed to the Datetime/Timedelta Array/Index 

2517 constructor, cast it to an integer. 

2518 

2519 Parameters 

2520 ---------- 

2521 periods : None, int 

2522 

2523 Returns 

2524 ------- 

2525 periods : None or int 

2526 

2527 Raises 

2528 ------ 

2529 TypeError 

2530 if periods is not None or int 

2531 """ 

2532 if periods is not None and not lib.is_integer(periods): 

2533 raise TypeError(f"periods must be an integer, got {periods}") 

2534 # error: Incompatible return value type (got "int | integer[Any] | None", 

2535 # expected "int | None") 

2536 return periods # type: ignore[return-value] 

2537 

2538 

2539def dtype_to_unit(dtype: DatetimeTZDtype | np.dtype | ArrowDtype) -> str: 

2540 """ 

2541 Return the unit str corresponding to the dtype's resolution. 

2542 

2543 Parameters 

2544 ---------- 

2545 dtype : DatetimeTZDtype or np.dtype 

2546 If np.dtype, we assume it is a datetime64 dtype. 

2547 

2548 Returns 

2549 ------- 

2550 str 

2551 """ 

2552 if isinstance(dtype, DatetimeTZDtype): 

2553 return dtype.unit 

2554 elif isinstance(dtype, ArrowDtype): 

2555 if dtype.kind not in "mM": 

2556 raise ValueError(f"{dtype=} does not have a resolution.") 

2557 return dtype.pyarrow_dtype.unit 

2558 return np.datetime_data(dtype)[0]