Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from datetime import datetime, timedelta 

2import operator 

3from typing import Any, Sequence, Type, Union, cast 

4import warnings 

5 

6import numpy as np 

7 

8from pandas._libs import NaT, NaTType, Timestamp, algos, iNaT, lib 

9from pandas._libs.tslibs.c_timestamp import integer_op_not_supported 

10from pandas._libs.tslibs.period import DIFFERENT_FREQ, IncompatibleFrequency, Period 

11from pandas._libs.tslibs.timedeltas import Timedelta, delta_to_nanoseconds 

12from pandas._libs.tslibs.timestamps import RoundTo, round_nsint64 

13from pandas._typing import DatetimeLikeScalar 

14from pandas.compat import set_function_name 

15from pandas.compat.numpy import function as nv 

16from pandas.errors import AbstractMethodError, NullFrequencyError, PerformanceWarning 

17from pandas.util._decorators import Appender, Substitution 

18from pandas.util._validators import validate_fillna_kwargs 

19 

20from pandas.core.dtypes.common import ( 

21 is_categorical_dtype, 

22 is_datetime64_any_dtype, 

23 is_datetime64_dtype, 

24 is_datetime64tz_dtype, 

25 is_datetime_or_timedelta_dtype, 

26 is_dtype_equal, 

27 is_float_dtype, 

28 is_integer_dtype, 

29 is_list_like, 

30 is_object_dtype, 

31 is_period_dtype, 

32 is_string_dtype, 

33 is_timedelta64_dtype, 

34 is_unsigned_integer_dtype, 

35 pandas_dtype, 

36) 

37from pandas.core.dtypes.generic import ABCSeries 

38from pandas.core.dtypes.inference import is_array_like 

39from pandas.core.dtypes.missing import is_valid_nat_for_dtype, isna 

40 

41from pandas.core import missing, nanops, ops 

42from pandas.core.algorithms import checked_add_with_arr, take, unique1d, value_counts 

43from pandas.core.arrays.base import ExtensionArray, ExtensionOpsMixin 

44import pandas.core.common as com 

45from pandas.core.indexers import check_array_indexer 

46from pandas.core.ops.common import unpack_zerodim_and_defer 

47from pandas.core.ops.invalid import invalid_comparison, make_invalid_op 

48 

49from pandas.tseries import frequencies 

50from pandas.tseries.offsets import DateOffset, Tick 

51 

52 

53def _datetimelike_array_cmp(cls, op): 

54 """ 

55 Wrap comparison operations to convert Timestamp/Timedelta/Period-like to 

56 boxed scalars/arrays. 

57 """ 

58 opname = f"__{op.__name__}__" 

59 nat_result = opname == "__ne__" 

60 

61 @unpack_zerodim_and_defer(opname) 

62 def wrapper(self, other): 

63 

64 if isinstance(other, str): 

65 try: 

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

67 other = self._scalar_from_string(other) 

68 except ValueError: 

69 # failed to parse as Timestamp/Timedelta/Period 

70 return invalid_comparison(self, other, op) 

71 

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

73 other = self._scalar_type(other) 

74 self._check_compatible_with(other) 

75 

76 other_i8 = self._unbox_scalar(other) 

77 

78 result = op(self.view("i8"), other_i8) 

79 if isna(other): 

80 result.fill(nat_result) 

81 

82 elif not is_list_like(other): 

83 return invalid_comparison(self, other, op) 

84 

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

86 raise ValueError("Lengths must match") 

87 

88 else: 

89 if isinstance(other, list): 

90 # TODO: could use pd.Index to do inference? 

91 other = np.array(other) 

92 

93 if not isinstance(other, (np.ndarray, type(self))): 

94 return invalid_comparison(self, other, op) 

95 

96 if is_object_dtype(other): 

97 # We have to use comp_method_OBJECT_ARRAY instead of numpy 

98 # comparison otherwise it would fail to raise when 

99 # comparing tz-aware and tz-naive 

100 with np.errstate(all="ignore"): 

101 result = ops.comp_method_OBJECT_ARRAY( 

102 op, self.astype(object), other 

103 ) 

104 o_mask = isna(other) 

105 

106 elif not type(self)._is_recognized_dtype(other.dtype): 

107 return invalid_comparison(self, other, op) 

108 

109 else: 

110 # For PeriodDType this casting is unnecessary 

111 other = type(self)._from_sequence(other) 

112 self._check_compatible_with(other) 

113 

114 result = op(self.view("i8"), other.view("i8")) 

115 o_mask = other._isnan 

116 

117 if o_mask.any(): 

118 result[o_mask] = nat_result 

119 

120 if self._hasnans: 

121 result[self._isnan] = nat_result 

122 

123 return result 

124 

125 return set_function_name(wrapper, opname, cls) 

126 

127 

128class AttributesMixin: 

129 _data: np.ndarray 

130 

131 @classmethod 

132 def _simple_new(cls, values, **kwargs): 

133 raise AbstractMethodError(cls) 

134 

135 @property 

136 def _scalar_type(self) -> Type[DatetimeLikeScalar]: 

137 """The scalar associated with this datelike 

138 

139 * PeriodArray : Period 

140 * DatetimeArray : Timestamp 

141 * TimedeltaArray : Timedelta 

142 """ 

143 raise AbstractMethodError(self) 

144 

145 def _scalar_from_string( 

146 self, value: str 

147 ) -> Union[Period, Timestamp, Timedelta, NaTType]: 

148 """ 

149 Construct a scalar type from a string. 

150 

151 Parameters 

152 ---------- 

153 value : str 

154 

155 Returns 

156 ------- 

157 Period, Timestamp, or Timedelta, or NaT 

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

159 

160 Notes 

161 ----- 

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

163 unboxing the result. 

164 """ 

165 raise AbstractMethodError(self) 

166 

167 def _unbox_scalar(self, value: Union[Period, Timestamp, Timedelta, NaTType]) -> int: 

168 """ 

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

170 

171 Parameters 

172 ---------- 

173 value : Union[Period, Timestamp, Timedelta] 

174 

175 Returns 

176 ------- 

177 int 

178 

179 Examples 

180 -------- 

181 >>> self._unbox_scalar(Timedelta('10s')) # DOCTEST: +SKIP 

182 10000000000 

183 """ 

184 raise AbstractMethodError(self) 

185 

186 def _check_compatible_with( 

187 self, other: Union[Period, Timestamp, Timedelta, NaTType], setitem: bool = False 

188 ) -> None: 

189 """ 

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

191 

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

193 * PeriodArray verifies that the freq matches 

194 * Timedelta has no verification 

195 

196 In each case, NaT is considered compatible. 

197 

198 Parameters 

199 ---------- 

200 other 

201 setitem : bool, default False 

202 For __setitem__ we may have stricter compatiblity resrictions than 

203 for comparisons. 

204 

205 Raises 

206 ------ 

207 Exception 

208 """ 

209 raise AbstractMethodError(self) 

210 

211 

212class DatelikeOps: 

213 """ 

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

215 """ 

216 

217 @Substitution( 

218 URL="https://docs.python.org/3/library/datetime.html" 

219 "#strftime-and-strptime-behavior" 

220 ) 

221 def strftime(self, date_format): 

222 """ 

223 Convert to Index using specified date_format. 

224 

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

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

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

228 doc <%(URL)s>`__. 

229 

230 Parameters 

231 ---------- 

232 date_format : str 

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

234 

235 Returns 

236 ------- 

237 ndarray 

238 NumPy ndarray of formatted strings. 

239 

240 See Also 

241 -------- 

242 to_datetime : Convert the given argument to datetime. 

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

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

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

246 

247 Examples 

248 -------- 

249 >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), 

250 ... periods=3, freq='s') 

251 >>> rng.strftime('%%B %%d, %%Y, %%r') 

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

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

254 dtype='object') 

255 """ 

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

257 return result.astype(object) 

258 

259 

260class TimelikeOps: 

261 """ 

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

263 """ 

264 

265 _round_doc = """ 

266 Perform {op} operation on the data to the specified `freq`. 

267 

268 Parameters 

269 ---------- 

270 freq : str or Offset 

271 The frequency level to {op} the index to. Must be a fixed 

272 frequency like 'S' (second) not 'ME' (month end). See 

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

274 a list of possible `freq` values. 

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

276 Only relevant for DatetimeIndex: 

277 

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

279 order 

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

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

282 ambiguous times) 

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

284 - 'raise' will raise an AmbiguousTimeError if there are ambiguous 

285 times. 

286 

287 .. versionadded:: 0.24.0 

288 

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

290default 'raise' 

291 A nonexistent time does not exist in a particular timezone 

292 where clocks moved forward due to DST. 

293 

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

295 closest existing time 

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

297 closest existing time 

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

299 - timedelta objects will shift nonexistent times by the timedelta 

300 - 'raise' will raise an NonExistentTimeError if there are 

301 nonexistent times. 

302 

303 .. versionadded:: 0.24.0 

304 

305 Returns 

306 ------- 

307 DatetimeIndex, TimedeltaIndex, or Series 

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

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

310 

311 Raises 

312 ------ 

313 ValueError if the `freq` cannot be converted. 

314 

315 Examples 

316 -------- 

317 **DatetimeIndex** 

318 

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

320 >>> rng 

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

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

323 dtype='datetime64[ns]', freq='T') 

324 """ 

325 

326 _round_example = """>>> rng.round('H') 

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

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

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

330 

331 **Series** 

332 

333 >>> pd.Series(rng).dt.round("H") 

334 0 2018-01-01 12:00:00 

335 1 2018-01-01 12:00:00 

336 2 2018-01-01 12:00:00 

337 dtype: datetime64[ns] 

338 """ 

339 

340 _floor_example = """>>> rng.floor('H') 

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

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

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

344 

345 **Series** 

346 

347 >>> pd.Series(rng).dt.floor("H") 

348 0 2018-01-01 11:00:00 

349 1 2018-01-01 12:00:00 

350 2 2018-01-01 12:00:00 

351 dtype: datetime64[ns] 

352 """ 

353 

354 _ceil_example = """>>> rng.ceil('H') 

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

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

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

358 

359 **Series** 

360 

361 >>> pd.Series(rng).dt.ceil("H") 

362 0 2018-01-01 12:00:00 

363 1 2018-01-01 12:00:00 

364 2 2018-01-01 13:00:00 

365 dtype: datetime64[ns] 

366 """ 

367 

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

369 # round the local times 

370 if is_datetime64tz_dtype(self): 

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

372 naive = self.tz_localize(None) 

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

374 aware = result.tz_localize( 

375 self.tz, ambiguous=ambiguous, nonexistent=nonexistent 

376 ) 

377 return aware 

378 

379 values = self.view("i8") 

380 result = round_nsint64(values, mode, freq) 

381 result = self._maybe_mask_results(result, fill_value=NaT) 

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

383 

384 @Appender((_round_doc + _round_example).format(op="round")) 

385 def round(self, freq, ambiguous="raise", nonexistent="raise"): 

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

387 

388 @Appender((_round_doc + _floor_example).format(op="floor")) 

389 def floor(self, freq, ambiguous="raise", nonexistent="raise"): 

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

391 

392 @Appender((_round_doc + _ceil_example).format(op="ceil")) 

393 def ceil(self, freq, ambiguous="raise", nonexistent="raise"): 

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

395 

396 

397class DatetimeLikeArrayMixin(ExtensionOpsMixin, AttributesMixin, ExtensionArray): 

398 """ 

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

400 

401 Assumes that __new__/__init__ defines: 

402 _data 

403 _freq 

404 

405 and that the inheriting class has methods: 

406 _generate_range 

407 """ 

408 

409 @property 

410 def ndim(self) -> int: 

411 return self._data.ndim 

412 

413 @property 

414 def shape(self): 

415 return self._data.shape 

416 

417 def reshape(self, *args, **kwargs): 

418 # Note: we drop any freq 

419 data = self._data.reshape(*args, **kwargs) 

420 return type(self)(data, dtype=self.dtype) 

421 

422 def ravel(self, *args, **kwargs): 

423 # Note: we drop any freq 

424 data = self._data.ravel(*args, **kwargs) 

425 return type(self)(data, dtype=self.dtype) 

426 

427 @property 

428 def _box_func(self): 

429 """ 

430 box function to get object from internal representation 

431 """ 

432 raise AbstractMethodError(self) 

433 

434 def _box_values(self, values): 

435 """ 

436 apply box func to passed values 

437 """ 

438 return lib.map_infer(values, self._box_func) 

439 

440 def __iter__(self): 

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

442 

443 @property 

444 def asi8(self) -> np.ndarray: 

445 """ 

446 Integer representation of the values. 

447 

448 Returns 

449 ------- 

450 ndarray 

451 An ndarray with int64 dtype. 

452 """ 

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

454 return self._data.view("i8") 

455 

456 @property 

457 def _ndarray_values(self): 

458 return self._data 

459 

460 # ---------------------------------------------------------------- 

461 # Rendering Methods 

462 

463 def _format_native_types(self, na_rep="NaT", date_format=None): 

464 """ 

465 Helper method for astype when converting to strings. 

466 

467 Returns 

468 ------- 

469 ndarray[str] 

470 """ 

471 raise AbstractMethodError(self) 

472 

473 def _formatter(self, boxed=False): 

474 # TODO: Remove Datetime & DatetimeTZ formatters. 

475 return "'{}'".format 

476 

477 # ---------------------------------------------------------------- 

478 # Array-Like / EA-Interface Methods 

479 

480 @property 

481 def nbytes(self): 

482 return self._data.nbytes 

483 

484 def __array__(self, dtype=None) -> np.ndarray: 

485 # used for Timedelta/DatetimeArray, overwritten by PeriodArray 

486 if is_object_dtype(dtype): 

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

488 return self._data 

489 

490 @property 

491 def size(self) -> int: 

492 """The number of elements in this array.""" 

493 return np.prod(self.shape) 

494 

495 def __len__(self) -> int: 

496 return len(self._data) 

497 

498 def __getitem__(self, key): 

499 """ 

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

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

502 """ 

503 

504 is_int = lib.is_integer(key) 

505 if lib.is_scalar(key) and not is_int: 

506 raise IndexError( 

507 "only integers, slices (`:`), ellipsis (`...`), " 

508 "numpy.newaxis (`None`) and integer or boolean " 

509 "arrays are valid indices" 

510 ) 

511 

512 getitem = self._data.__getitem__ 

513 if is_int: 

514 val = getitem(key) 

515 if lib.is_scalar(val): 

516 # i.e. self.ndim == 1 

517 return self._box_func(val) 

518 return type(self)(val, dtype=self.dtype) 

519 

520 if com.is_bool_indexer(key): 

521 # first convert to boolean, because check_array_indexer doesn't 

522 # allow object dtype 

523 if is_object_dtype(key): 

524 key = np.asarray(key, dtype=bool) 

525 

526 key = check_array_indexer(self, key) 

527 if key.all(): 

528 key = slice(0, None, None) 

529 else: 

530 key = lib.maybe_booleans_to_slice(key.view(np.uint8)) 

531 elif isinstance(key, list) and len(key) == 1 and isinstance(key[0], slice): 

532 # see https://github.com/pandas-dev/pandas/issues/31299, need to allow 

533 # this for now (would otherwise raise in check_array_indexer) 

534 pass 

535 else: 

536 key = check_array_indexer(self, key) 

537 

538 is_period = is_period_dtype(self) 

539 if is_period: 

540 freq = self.freq 

541 else: 

542 freq = None 

543 if isinstance(key, slice): 

544 if self.freq is not None and key.step is not None: 

545 freq = key.step * self.freq 

546 else: 

547 freq = self.freq 

548 elif key is Ellipsis: 

549 # GH#21282 indexing with Ellipsis is similar to a full slice, 

550 # should preserve `freq` attribute 

551 freq = self.freq 

552 

553 result = getitem(key) 

554 if result.ndim > 1: 

555 # To support MPL which performs slicing with 2 dim 

556 # even though it only has 1 dim by definition 

557 return result 

558 

559 return self._simple_new(result, dtype=self.dtype, freq=freq) 

560 

561 def __setitem__( 

562 self, 

563 key: Union[int, Sequence[int], Sequence[bool], slice], 

564 value: Union[NaTType, Any, Sequence[Any]], 

565 ) -> None: 

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

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

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

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

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

571 if lib.is_scalar(value) and not isna(value): 

572 value = com.maybe_box_datetimelike(value) 

573 

574 if is_list_like(value): 

575 is_slice = isinstance(key, slice) 

576 

577 if lib.is_scalar(key): 

578 raise ValueError("setting an array element with a sequence.") 

579 

580 if not is_slice: 

581 key = cast(Sequence, key) 

582 if len(key) != len(value) and not com.is_bool_indexer(key): 

583 msg = ( 

584 f"shape mismatch: value array of length '{len(key)}' " 

585 "does not match indexing result of length " 

586 f"'{len(value)}'." 

587 ) 

588 raise ValueError(msg) 

589 elif not len(key): 

590 return 

591 

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

593 self._check_compatible_with(value, setitem=True) 

594 value = value.asi8 

595 elif isinstance(value, self._scalar_type): 

596 self._check_compatible_with(value, setitem=True) 

597 value = self._unbox_scalar(value) 

598 elif is_valid_nat_for_dtype(value, self.dtype): 

599 value = iNaT 

600 else: 

601 msg = ( 

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

603 f"or array of those. Got '{type(value).__name__}' instead." 

604 ) 

605 raise TypeError(msg) 

606 

607 key = check_array_indexer(self, key) 

608 self._data[key] = value 

609 self._maybe_clear_freq() 

610 

611 def _maybe_clear_freq(self): 

612 # inplace operations like __setitem__ may invalidate the freq of 

613 # DatetimeArray and TimedeltaArray 

614 pass 

615 

616 def astype(self, dtype, copy=True): 

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

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

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

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

621 from pandas import Categorical 

622 

623 dtype = pandas_dtype(dtype) 

624 

625 if is_object_dtype(dtype): 

626 return self._box_values(self.asi8) 

627 elif is_string_dtype(dtype) and not is_categorical_dtype(dtype): 

628 return self._format_native_types() 

629 elif is_integer_dtype(dtype): 

630 # we deliberately ignore int32 vs. int64 here. 

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

632 values = self.asi8 

633 

634 if is_unsigned_integer_dtype(dtype): 

635 # Again, we ignore int32 vs. int64 

636 values = values.view("uint64") 

637 

638 if copy: 

639 values = values.copy() 

640 return values 

641 elif ( 

642 is_datetime_or_timedelta_dtype(dtype) 

643 and not is_dtype_equal(self.dtype, dtype) 

644 ) or is_float_dtype(dtype): 

645 # disallow conversion between datetime/timedelta, 

646 # and conversions for any datetimelike to float 

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

648 raise TypeError(msg) 

649 elif is_categorical_dtype(dtype): 

650 return Categorical(self, dtype=dtype) 

651 else: 

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

653 

654 def view(self, dtype=None): 

655 if dtype is None or dtype is self.dtype: 

656 return type(self)(self._data, dtype=self.dtype) 

657 return self._data.view(dtype=dtype) 

658 

659 # ------------------------------------------------------------------ 

660 # ExtensionArray Interface 

661 

662 def unique(self): 

663 result = unique1d(self.asi8) 

664 return type(self)(result, dtype=self.dtype) 

665 

666 def _validate_fill_value(self, fill_value): 

667 """ 

668 If a fill_value is passed to `take` convert it to an i8 representation, 

669 raising ValueError if this is not possible. 

670 

671 Parameters 

672 ---------- 

673 fill_value : object 

674 

675 Returns 

676 ------- 

677 fill_value : np.int64 

678 

679 Raises 

680 ------ 

681 ValueError 

682 """ 

683 if isna(fill_value): 

684 fill_value = iNaT 

685 elif isinstance(fill_value, self._recognized_scalars): 

686 self._check_compatible_with(fill_value) 

687 fill_value = self._scalar_type(fill_value) 

688 fill_value = self._unbox_scalar(fill_value) 

689 else: 

690 raise ValueError( 

691 f"'fill_value' should be a {self._scalar_type}. Got '{fill_value}'." 

692 ) 

693 return fill_value 

694 

695 def take(self, indices, allow_fill=False, fill_value=None): 

696 if allow_fill: 

697 fill_value = self._validate_fill_value(fill_value) 

698 

699 new_values = take( 

700 self.asi8, indices, allow_fill=allow_fill, fill_value=fill_value 

701 ) 

702 

703 return type(self)(new_values, dtype=self.dtype) 

704 

705 @classmethod 

706 def _concat_same_type(cls, to_concat): 

707 dtypes = {x.dtype for x in to_concat} 

708 assert len(dtypes) == 1 

709 dtype = list(dtypes)[0] 

710 

711 values = np.concatenate([x.asi8 for x in to_concat]) 

712 return cls(values, dtype=dtype) 

713 

714 def copy(self): 

715 values = self.asi8.copy() 

716 return type(self)._simple_new(values, dtype=self.dtype, freq=self.freq) 

717 

718 def _values_for_factorize(self): 

719 return self.asi8, iNaT 

720 

721 @classmethod 

722 def _from_factorized(cls, values, original): 

723 return cls(values, dtype=original.dtype) 

724 

725 def _values_for_argsort(self): 

726 return self._data 

727 

728 @Appender(ExtensionArray.shift.__doc__) 

729 def shift(self, periods=1, fill_value=None, axis=0): 

730 if not self.size or periods == 0: 

731 return self.copy() 

732 

733 if is_valid_nat_for_dtype(fill_value, self.dtype): 

734 fill_value = NaT 

735 elif not isinstance(fill_value, self._recognized_scalars): 

736 # only warn if we're not going to raise 

737 if self._scalar_type is Period and lib.is_integer(fill_value): 

738 # kludge for #31971 since Period(integer) tries to cast to str 

739 new_fill = Period._from_ordinal(fill_value, freq=self.freq) 

740 else: 

741 new_fill = self._scalar_type(fill_value) 

742 

743 # stacklevel here is chosen to be correct when called from 

744 # DataFrame.shift or Series.shift 

745 warnings.warn( 

746 f"Passing {type(fill_value)} to shift is deprecated and " 

747 "will raise in a future version, pass " 

748 f"{self._scalar_type.__name__} instead.", 

749 FutureWarning, 

750 stacklevel=7, 

751 ) 

752 fill_value = new_fill 

753 

754 fill_value = self._unbox_scalar(fill_value) 

755 

756 new_values = self._data 

757 

758 # make sure array sent to np.roll is c_contiguous 

759 f_ordered = new_values.flags.f_contiguous 

760 if f_ordered: 

761 new_values = new_values.T 

762 axis = new_values.ndim - axis - 1 

763 

764 new_values = np.roll(new_values, periods, axis=axis) 

765 

766 axis_indexer = [slice(None)] * self.ndim 

767 if periods > 0: 

768 axis_indexer[axis] = slice(None, periods) 

769 else: 

770 axis_indexer[axis] = slice(periods, None) 

771 new_values[tuple(axis_indexer)] = fill_value 

772 

773 # restore original order 

774 if f_ordered: 

775 new_values = new_values.T 

776 

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

778 

779 # ------------------------------------------------------------------ 

780 # Additional array methods 

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

782 # pandas assumes they're there. 

783 

784 def searchsorted(self, value, side="left", sorter=None): 

785 """ 

786 Find indices where elements should be inserted to maintain order. 

787 

788 Find the indices into a sorted array `self` such that, if the 

789 corresponding elements in `value` were inserted before the indices, 

790 the order of `self` would be preserved. 

791 

792 Parameters 

793 ---------- 

794 value : array_like 

795 Values to insert into `self`. 

796 side : {'left', 'right'}, optional 

797 If 'left', the index of the first suitable location found is given. 

798 If 'right', return the last such index. If there is no suitable 

799 index, return either 0 or N (where N is the length of `self`). 

800 sorter : 1-D array_like, optional 

801 Optional array of integer indices that sort `self` into ascending 

802 order. They are typically the result of ``np.argsort``. 

803 

804 Returns 

805 ------- 

806 indices : array of ints 

807 Array of insertion points with the same shape as `value`. 

808 """ 

809 if isinstance(value, str): 

810 value = self._scalar_from_string(value) 

811 

812 if not (isinstance(value, (self._scalar_type, type(self))) or isna(value)): 

813 raise ValueError(f"Unexpected type for 'value': {type(value)}") 

814 

815 self._check_compatible_with(value) 

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

817 value = value.asi8 

818 else: 

819 value = self._unbox_scalar(value) 

820 

821 return self.asi8.searchsorted(value, side=side, sorter=sorter) 

822 

823 def repeat(self, repeats, *args, **kwargs): 

824 """ 

825 Repeat elements of an array. 

826 

827 See Also 

828 -------- 

829 numpy.ndarray.repeat 

830 """ 

831 nv.validate_repeat(args, kwargs) 

832 values = self._data.repeat(repeats) 

833 return type(self)(values.view("i8"), dtype=self.dtype) 

834 

835 def value_counts(self, dropna=False): 

836 """ 

837 Return a Series containing counts of unique values. 

838 

839 Parameters 

840 ---------- 

841 dropna : bool, default True 

842 Don't include counts of NaT values. 

843 

844 Returns 

845 ------- 

846 Series 

847 """ 

848 from pandas import Series, Index 

849 

850 if dropna: 

851 values = self[~self.isna()]._data 

852 else: 

853 values = self._data 

854 

855 cls = type(self) 

856 

857 result = value_counts(values, sort=False, dropna=dropna) 

858 index = Index( 

859 cls(result.index.view("i8"), dtype=self.dtype), name=result.index.name 

860 ) 

861 return Series(result.values, index=index, name=result.name) 

862 

863 def map(self, mapper): 

864 # TODO(GH-23179): Add ExtensionArray.map 

865 # Need to figure out if we want ExtensionArray.map first. 

866 # If so, then we can refactor IndexOpsMixin._map_values to 

867 # a standalone function and call from here.. 

868 # Else, just rewrite _map_infer_values to do the right thing. 

869 from pandas import Index 

870 

871 return Index(self).map(mapper).array 

872 

873 # ------------------------------------------------------------------ 

874 # Null Handling 

875 

876 def isna(self): 

877 return self._isnan 

878 

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

880 def _isnan(self): 

881 """ 

882 return if each value is nan 

883 """ 

884 return self.asi8 == iNaT 

885 

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

887 def _hasnans(self): 

888 """ 

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

890 """ 

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

892 

893 def _maybe_mask_results(self, result, fill_value=iNaT, convert=None): 

894 """ 

895 Parameters 

896 ---------- 

897 result : a ndarray 

898 fill_value : object, default iNaT 

899 convert : str, dtype or None 

900 

901 Returns 

902 ------- 

903 result : ndarray with values replace by the fill_value 

904 

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

906 None 

907 

908 This is an internal routine. 

909 """ 

910 

911 if self._hasnans: 

912 if convert: 

913 result = result.astype(convert) 

914 if fill_value is None: 

915 fill_value = np.nan 

916 result[self._isnan] = fill_value 

917 return result 

918 

919 def fillna(self, value=None, method=None, limit=None): 

920 # TODO(GH-20300): remove this 

921 # Just overriding to ensure that we avoid an astype(object). 

922 # Either 20300 or a `_values_for_fillna` would avoid this duplication. 

923 if isinstance(value, ABCSeries): 

924 value = value.array 

925 

926 value, method = validate_fillna_kwargs(value, method) 

927 

928 mask = self.isna() 

929 

930 if is_array_like(value): 

931 if len(value) != len(self): 

932 raise ValueError( 

933 f"Length of 'value' does not match. Got ({len(value)}) " 

934 f" expected {len(self)}" 

935 ) 

936 value = value[mask] 

937 

938 if mask.any(): 

939 if method is not None: 

940 if method == "pad": 

941 func = missing.pad_1d 

942 else: 

943 func = missing.backfill_1d 

944 

945 values = self._data 

946 if not is_period_dtype(self): 

947 # For PeriodArray self._data is i8, which gets copied 

948 # by `func`. Otherwise we need to make a copy manually 

949 # to avoid modifying `self` in-place. 

950 values = values.copy() 

951 

952 new_values = func(values, limit=limit, mask=mask) 

953 if is_datetime64tz_dtype(self): 

954 # we need to pass int64 values to the constructor to avoid 

955 # re-localizing incorrectly 

956 new_values = new_values.view("i8") 

957 new_values = type(self)(new_values, dtype=self.dtype) 

958 else: 

959 # fill with value 

960 new_values = self.copy() 

961 new_values[mask] = value 

962 else: 

963 new_values = self.copy() 

964 return new_values 

965 

966 # ------------------------------------------------------------------ 

967 # Frequency Properties/Methods 

968 

969 @property 

970 def freq(self): 

971 """ 

972 Return the frequency object if it is set, otherwise None. 

973 """ 

974 return self._freq 

975 

976 @freq.setter 

977 def freq(self, value): 

978 if value is not None: 

979 value = frequencies.to_offset(value) 

980 self._validate_frequency(self, value) 

981 

982 self._freq = value 

983 

984 @property 

985 def freqstr(self): 

986 """ 

987 Return the frequency object as a string if its set, otherwise None 

988 """ 

989 if self.freq is None: 

990 return None 

991 return self.freq.freqstr 

992 

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

994 def inferred_freq(self): 

995 """ 

996 Tryies to return a string representing a frequency guess, 

997 generated by infer_freq. Returns None if it can't autodetect the 

998 frequency. 

999 """ 

1000 if self.ndim != 1: 

1001 return None 

1002 try: 

1003 return frequencies.infer_freq(self) 

1004 except ValueError: 

1005 return None 

1006 

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

1008 def _resolution(self): 

1009 return frequencies.Resolution.get_reso_from_freq(self.freqstr) 

1010 

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

1012 def resolution(self): 

1013 """ 

1014 Returns day, hour, minute, second, millisecond or microsecond 

1015 """ 

1016 return frequencies.Resolution.get_str(self._resolution) 

1017 

1018 @classmethod 

1019 def _validate_frequency(cls, index, freq, **kwargs): 

1020 """ 

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

1022 Datetime Array/Index or Timedelta Array/Index 

1023 

1024 Parameters 

1025 ---------- 

1026 index : DatetimeIndex or TimedeltaIndex 

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

1028 freq : DateOffset 

1029 The frequency to validate 

1030 """ 

1031 if is_period_dtype(cls): 

1032 # Frequency validation is not meaningful for Period Array/Index 

1033 return None 

1034 

1035 inferred = index.inferred_freq 

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

1037 return None 

1038 

1039 try: 

1040 on_freq = cls._generate_range( 

1041 start=index[0], end=None, periods=len(index), freq=freq, **kwargs 

1042 ) 

1043 if not np.array_equal(index.asi8, on_freq.asi8): 

1044 raise ValueError 

1045 except ValueError as e: 

1046 if "non-fixed" in str(e): 

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

1048 # we retain that error message 

1049 raise e 

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

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

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

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

1054 # message. 

1055 raise ValueError( 

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

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

1058 ) 

1059 

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

1061 # see GH#23789 

1062 

1063 @property 

1064 def _is_monotonic_increasing(self): 

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

1066 

1067 @property 

1068 def _is_monotonic_decreasing(self): 

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

1070 

1071 @property 

1072 def _is_unique(self): 

1073 return len(unique1d(self.asi8)) == len(self) 

1074 

1075 # ------------------------------------------------------------------ 

1076 # Arithmetic Methods 

1077 _create_comparison_method = classmethod(_datetimelike_array_cmp) 

1078 

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

1080 # the multiplication and division ops 

1081 __pow__ = make_invalid_op("__pow__") 

1082 __rpow__ = make_invalid_op("__rpow__") 

1083 __mul__ = make_invalid_op("__mul__") 

1084 __rmul__ = make_invalid_op("__rmul__") 

1085 __truediv__ = make_invalid_op("__truediv__") 

1086 __rtruediv__ = make_invalid_op("__rtruediv__") 

1087 __floordiv__ = make_invalid_op("__floordiv__") 

1088 __rfloordiv__ = make_invalid_op("__rfloordiv__") 

1089 __mod__ = make_invalid_op("__mod__") 

1090 __rmod__ = make_invalid_op("__rmod__") 

1091 __divmod__ = make_invalid_op("__divmod__") 

1092 __rdivmod__ = make_invalid_op("__rdivmod__") 

1093 

1094 def _add_datetimelike_scalar(self, other): 

1095 # Overridden by TimedeltaArray 

1096 raise TypeError(f"cannot add {type(self).__name__} and {type(other).__name__}") 

1097 

1098 _add_datetime_arraylike = _add_datetimelike_scalar 

1099 

1100 def _sub_datetimelike_scalar(self, other): 

1101 # Overridden by DatetimeArray 

1102 assert other is not NaT 

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

1104 

1105 _sub_datetime_arraylike = _sub_datetimelike_scalar 

1106 

1107 def _sub_period(self, other): 

1108 # Overridden by PeriodArray 

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

1110 

1111 def _add_offset(self, offset): 

1112 raise AbstractMethodError(self) 

1113 

1114 def _add_delta(self, other): 

1115 """ 

1116 Add a timedelta-like, Tick or TimedeltaIndex-like object 

1117 to self, yielding an int64 numpy array 

1118 

1119 Parameters 

1120 ---------- 

1121 delta : {timedelta, np.timedelta64, Tick, 

1122 TimedeltaIndex, ndarray[timedelta64]} 

1123 

1124 Returns 

1125 ------- 

1126 result : ndarray[int64] 

1127 

1128 Notes 

1129 ----- 

1130 The result's name is set outside of _add_delta by the calling 

1131 method (__add__ or __sub__), if necessary (i.e. for Indexes). 

1132 """ 

1133 if isinstance(other, (Tick, timedelta, np.timedelta64)): 

1134 new_values = self._add_timedeltalike_scalar(other) 

1135 elif is_timedelta64_dtype(other): 

1136 # ndarray[timedelta64] or TimedeltaArray/index 

1137 new_values = self._add_delta_tdi(other) 

1138 

1139 return new_values 

1140 

1141 def _add_timedeltalike_scalar(self, other): 

1142 """ 

1143 Add a delta of a timedeltalike 

1144 return the i8 result view 

1145 """ 

1146 if isna(other): 

1147 # i.e np.timedelta64("NaT"), not recognized by delta_to_nanoseconds 

1148 new_values = np.empty(self.shape, dtype="i8") 

1149 new_values[:] = iNaT 

1150 return new_values 

1151 

1152 inc = delta_to_nanoseconds(other) 

1153 new_values = checked_add_with_arr(self.asi8, inc, arr_mask=self._isnan).view( 

1154 "i8" 

1155 ) 

1156 new_values = self._maybe_mask_results(new_values) 

1157 return new_values.view("i8") 

1158 

1159 def _add_delta_tdi(self, other): 

1160 """ 

1161 Add a delta of a TimedeltaIndex 

1162 return the i8 result view 

1163 """ 

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

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

1166 

1167 if isinstance(other, np.ndarray): 

1168 # ndarray[timedelta64]; wrap in TimedeltaIndex for op 

1169 from pandas.core.arrays import TimedeltaArray 

1170 

1171 other = TimedeltaArray._from_sequence(other) 

1172 

1173 self_i8 = self.asi8 

1174 other_i8 = other.asi8 

1175 new_values = checked_add_with_arr( 

1176 self_i8, other_i8, arr_mask=self._isnan, b_mask=other._isnan 

1177 ) 

1178 if self._hasnans or other._hasnans: 

1179 mask = (self._isnan) | (other._isnan) 

1180 new_values[mask] = iNaT 

1181 return new_values.view("i8") 

1182 

1183 def _add_nat(self): 

1184 """ 

1185 Add pd.NaT to self 

1186 """ 

1187 if is_period_dtype(self): 

1188 raise TypeError( 

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

1190 ) 

1191 

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

1193 # and datetime dtypes 

1194 result = np.zeros(self.shape, dtype=np.int64) 

1195 result.fill(iNaT) 

1196 return type(self)(result, dtype=self.dtype, freq=None) 

1197 

1198 def _sub_nat(self): 

1199 """ 

1200 Subtract pd.NaT from self 

1201 """ 

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

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

1204 # like a timedelta. 

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

1206 # this subtraction returns a timedelta64 dtype. 

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

1208 result = np.zeros(self.shape, dtype=np.int64) 

1209 result.fill(iNaT) 

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

1211 

1212 def _sub_period_array(self, other): 

1213 """ 

1214 Subtract a Period Array/Index from self. This is only valid if self 

1215 is itself a Period Array/Index, raises otherwise. Both objects must 

1216 have the same frequency. 

1217 

1218 Parameters 

1219 ---------- 

1220 other : PeriodIndex or PeriodArray 

1221 

1222 Returns 

1223 ------- 

1224 result : np.ndarray[object] 

1225 Array of DateOffset objects; nulls represented by NaT. 

1226 """ 

1227 if not is_period_dtype(self): 

1228 raise TypeError( 

1229 f"cannot subtract {other.dtype}-dtype from {type(self).__name__}" 

1230 ) 

1231 

1232 if self.freq != other.freq: 

1233 msg = DIFFERENT_FREQ.format( 

1234 cls=type(self).__name__, own_freq=self.freqstr, other_freq=other.freqstr 

1235 ) 

1236 raise IncompatibleFrequency(msg) 

1237 

1238 new_values = checked_add_with_arr( 

1239 self.asi8, -other.asi8, arr_mask=self._isnan, b_mask=other._isnan 

1240 ) 

1241 

1242 new_values = np.array([self.freq.base * x for x in new_values]) 

1243 if self._hasnans or other._hasnans: 

1244 mask = (self._isnan) | (other._isnan) 

1245 new_values[mask] = NaT 

1246 return new_values 

1247 

1248 def _addsub_object_array(self, other: np.ndarray, op): 

1249 """ 

1250 Add or subtract array-like of DateOffset objects 

1251 

1252 Parameters 

1253 ---------- 

1254 other : np.ndarray[object] 

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

1256 

1257 Returns 

1258 ------- 

1259 result : same class as self 

1260 """ 

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

1262 if len(other) == 1: 

1263 return op(self, other[0]) 

1264 

1265 warnings.warn( 

1266 "Adding/subtracting array of DateOffsets to " 

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

1268 PerformanceWarning, 

1269 ) 

1270 

1271 # For EA self.astype('O') returns a numpy array, not an Index 

1272 left = self.astype("O") 

1273 

1274 res_values = op(left, np.array(other)) 

1275 kwargs = {} 

1276 if not is_period_dtype(self): 

1277 kwargs["freq"] = "infer" 

1278 try: 

1279 res = type(self)._from_sequence(res_values, **kwargs) 

1280 except ValueError: 

1281 # e.g. we've passed a Timestamp to TimedeltaArray 

1282 res = res_values 

1283 return res 

1284 

1285 def _time_shift(self, periods, freq=None): 

1286 """ 

1287 Shift each value by `periods`. 

1288 

1289 Note this is different from ExtensionArray.shift, which 

1290 shifts the *position* of each element, padding the end with 

1291 missing values. 

1292 

1293 Parameters 

1294 ---------- 

1295 periods : int 

1296 Number of periods to shift by. 

1297 freq : pandas.DateOffset, pandas.Timedelta, or str 

1298 Frequency increment to shift by. 

1299 """ 

1300 if freq is not None and freq != self.freq: 

1301 if isinstance(freq, str): 

1302 freq = frequencies.to_offset(freq) 

1303 offset = periods * freq 

1304 result = self + offset 

1305 return result 

1306 

1307 if periods == 0: 

1308 # immutable so OK 

1309 return self.copy() 

1310 

1311 if self.freq is None: 

1312 raise NullFrequencyError("Cannot shift with no freq") 

1313 

1314 start = self[0] + periods * self.freq 

1315 end = self[-1] + periods * self.freq 

1316 

1317 # Note: in the DatetimeTZ case, _generate_range will infer the 

1318 # appropriate timezone from `start` and `end`, so tz does not need 

1319 # to be passed explicitly. 

1320 return self._generate_range(start=start, end=end, periods=None, freq=self.freq) 

1321 

1322 @unpack_zerodim_and_defer("__add__") 

1323 def __add__(self, other): 

1324 

1325 # scalar others 

1326 if other is NaT: 

1327 result = self._add_nat() 

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

1329 result = self._add_delta(other) 

1330 elif isinstance(other, DateOffset): 

1331 # specifically _not_ a Tick 

1332 result = self._add_offset(other) 

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

1334 result = self._add_datetimelike_scalar(other) 

1335 elif lib.is_integer(other): 

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

1337 # as is_integer returns True for these 

1338 if not is_period_dtype(self): 

1339 raise integer_op_not_supported(self) 

1340 result = self._time_shift(other) 

1341 

1342 # array-like others 

1343 elif is_timedelta64_dtype(other): 

1344 # TimedeltaIndex, ndarray[timedelta64] 

1345 result = self._add_delta(other) 

1346 elif is_object_dtype(other): 

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

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

1349 elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): 

1350 # DatetimeIndex, ndarray[datetime64] 

1351 return self._add_datetime_arraylike(other) 

1352 elif is_integer_dtype(other): 

1353 if not is_period_dtype(self): 

1354 raise integer_op_not_supported(self) 

1355 result = self._addsub_int_array(other, operator.add) 

1356 else: 

1357 # Includes Categorical, other ExtensionArrays 

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

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

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

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

1362 return NotImplemented 

1363 

1364 if is_timedelta64_dtype(result) and isinstance(result, np.ndarray): 

1365 from pandas.core.arrays import TimedeltaArray 

1366 

1367 return TimedeltaArray(result) 

1368 return result 

1369 

1370 def __radd__(self, other): 

1371 # alias for __add__ 

1372 return self.__add__(other) 

1373 

1374 @unpack_zerodim_and_defer("__sub__") 

1375 def __sub__(self, other): 

1376 

1377 # scalar others 

1378 if other is NaT: 

1379 result = self._sub_nat() 

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

1381 result = self._add_delta(-other) 

1382 elif isinstance(other, DateOffset): 

1383 # specifically _not_ a Tick 

1384 result = self._add_offset(-other) 

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

1386 result = self._sub_datetimelike_scalar(other) 

1387 elif lib.is_integer(other): 

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

1389 # as is_integer returns True for these 

1390 if not is_period_dtype(self): 

1391 raise integer_op_not_supported(self) 

1392 result = self._time_shift(-other) 

1393 

1394 elif isinstance(other, Period): 

1395 result = self._sub_period(other) 

1396 

1397 # array-like others 

1398 elif is_timedelta64_dtype(other): 

1399 # TimedeltaIndex, ndarray[timedelta64] 

1400 result = self._add_delta(-other) 

1401 elif is_object_dtype(other): 

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

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

1404 elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): 

1405 # DatetimeIndex, ndarray[datetime64] 

1406 result = self._sub_datetime_arraylike(other) 

1407 elif is_period_dtype(other): 

1408 # PeriodIndex 

1409 result = self._sub_period_array(other) 

1410 elif is_integer_dtype(other): 

1411 if not is_period_dtype(self): 

1412 raise integer_op_not_supported(self) 

1413 result = self._addsub_int_array(other, operator.sub) 

1414 else: 

1415 # Includes ExtensionArrays, float_dtype 

1416 return NotImplemented 

1417 

1418 if is_timedelta64_dtype(result) and isinstance(result, np.ndarray): 

1419 from pandas.core.arrays import TimedeltaArray 

1420 

1421 return TimedeltaArray(result) 

1422 return result 

1423 

1424 def __rsub__(self, other): 

1425 if is_datetime64_any_dtype(other) and is_timedelta64_dtype(self.dtype): 

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

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

1428 if lib.is_scalar(other): 

1429 # i.e. np.datetime64 object 

1430 return Timestamp(other) - self 

1431 if not isinstance(other, DatetimeLikeArrayMixin): 

1432 # Avoid down-casting DatetimeIndex 

1433 from pandas.core.arrays import DatetimeArray 

1434 

1435 other = DatetimeArray(other) 

1436 return other - self 

1437 elif ( 

1438 is_datetime64_any_dtype(self.dtype) 

1439 and hasattr(other, "dtype") 

1440 and not is_datetime64_any_dtype(other.dtype) 

1441 ): 

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

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

1444 raise TypeError( 

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

1446 ) 

1447 elif is_period_dtype(self.dtype) and is_timedelta64_dtype(other): 

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

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

1450 elif is_timedelta64_dtype(self.dtype): 

1451 if lib.is_integer(other) or is_integer_dtype(other): 

1452 # need to subtract before negating, since that flips freq 

1453 # -self flips self.freq, messing up results 

1454 return -(self - other) 

1455 

1456 return (-self) + other 

1457 

1458 return -(self - other) 

1459 

1460 def __iadd__(self, other): # type: ignore 

1461 result = self + other 

1462 self[:] = result[:] 

1463 

1464 if not is_period_dtype(self): 

1465 # restore freq, which is invalidated by setitem 

1466 self._freq = result._freq 

1467 return self 

1468 

1469 def __isub__(self, other): # type: ignore 

1470 result = self - other 

1471 self[:] = result[:] 

1472 

1473 if not is_period_dtype(self): 

1474 # restore freq, which is invalidated by setitem 

1475 self._freq = result._freq 

1476 return self 

1477 

1478 # -------------------------------------------------------------- 

1479 # Reductions 

1480 

1481 def _reduce(self, name, axis=0, skipna=True, **kwargs): 

1482 op = getattr(self, name, None) 

1483 if op: 

1484 return op(skipna=skipna, **kwargs) 

1485 else: 

1486 return super()._reduce(name, skipna, **kwargs) 

1487 

1488 def min(self, axis=None, skipna=True, *args, **kwargs): 

1489 """ 

1490 Return the minimum value of the Array or minimum along 

1491 an axis. 

1492 

1493 See Also 

1494 -------- 

1495 numpy.ndarray.min 

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

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

1498 """ 

1499 nv.validate_min(args, kwargs) 

1500 nv.validate_minmax_axis(axis) 

1501 

1502 result = nanops.nanmin(self.asi8, skipna=skipna, mask=self.isna()) 

1503 if isna(result): 

1504 # Period._from_ordinal does not handle np.nan gracefully 

1505 return NaT 

1506 return self._box_func(result) 

1507 

1508 def max(self, axis=None, skipna=True, *args, **kwargs): 

1509 """ 

1510 Return the maximum value of the Array or maximum along 

1511 an axis. 

1512 

1513 See Also 

1514 -------- 

1515 numpy.ndarray.max 

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

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

1518 """ 

1519 # TODO: skipna is broken with max. 

1520 # See https://github.com/pandas-dev/pandas/issues/24265 

1521 nv.validate_max(args, kwargs) 

1522 nv.validate_minmax_axis(axis) 

1523 

1524 mask = self.isna() 

1525 if skipna: 

1526 values = self[~mask].asi8 

1527 elif mask.any(): 

1528 return NaT 

1529 else: 

1530 values = self.asi8 

1531 

1532 if not len(values): 

1533 # short-circuit for empty max / min 

1534 return NaT 

1535 

1536 result = nanops.nanmax(values, skipna=skipna) 

1537 # Don't have to worry about NA `result`, since no NA went in. 

1538 return self._box_func(result) 

1539 

1540 def mean(self, skipna=True): 

1541 """ 

1542 Return the mean value of the Array. 

1543 

1544 .. versionadded:: 0.25.0 

1545 

1546 Parameters 

1547 ---------- 

1548 skipna : bool, default True 

1549 Whether to ignore any NaT elements. 

1550 

1551 Returns 

1552 ------- 

1553 scalar 

1554 Timestamp or Timedelta. 

1555 

1556 See Also 

1557 -------- 

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

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

1560 

1561 Notes 

1562 ----- 

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

1564 """ 

1565 if is_period_dtype(self): 

1566 # See discussion in GH#24757 

1567 raise TypeError( 

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

1569 "meaning is ambiguous. An alternative is " 

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

1571 ) 

1572 

1573 mask = self.isna() 

1574 if skipna: 

1575 values = self[~mask] 

1576 elif mask.any(): 

1577 return NaT 

1578 else: 

1579 values = self 

1580 

1581 if not len(values): 

1582 # short-circuit for empty max / min 

1583 return NaT 

1584 

1585 result = nanops.nanmean(values.view("i8"), skipna=skipna) 

1586 # Don't have to worry about NA `result`, since no NA went in. 

1587 return self._box_func(result) 

1588 

1589 

1590DatetimeLikeArrayMixin._add_comparison_ops() 

1591 

1592# ------------------------------------------------------------------- 

1593# Shared Constructor Helpers 

1594 

1595 

1596def validate_periods(periods): 

1597 """ 

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

1599 constructor, cast it to an integer. 

1600 

1601 Parameters 

1602 ---------- 

1603 periods : None, float, int 

1604 

1605 Returns 

1606 ------- 

1607 periods : None or int 

1608 

1609 Raises 

1610 ------ 

1611 TypeError 

1612 if periods is None, float, or int 

1613 """ 

1614 if periods is not None: 

1615 if lib.is_float(periods): 

1616 periods = int(periods) 

1617 elif not lib.is_integer(periods): 

1618 raise TypeError(f"periods must be a number, got {periods}") 

1619 return periods 

1620 

1621 

1622def validate_endpoints(closed): 

1623 """ 

1624 Check that the `closed` argument is among [None, "left", "right"] 

1625 

1626 Parameters 

1627 ---------- 

1628 closed : {None, "left", "right"} 

1629 

1630 Returns 

1631 ------- 

1632 left_closed : bool 

1633 right_closed : bool 

1634 

1635 Raises 

1636 ------ 

1637 ValueError : if argument is not among valid values 

1638 """ 

1639 left_closed = False 

1640 right_closed = False 

1641 

1642 if closed is None: 

1643 left_closed = True 

1644 right_closed = True 

1645 elif closed == "left": 

1646 left_closed = True 

1647 elif closed == "right": 

1648 right_closed = True 

1649 else: 

1650 raise ValueError("Closed has to be either 'left', 'right' or None") 

1651 

1652 return left_closed, right_closed 

1653 

1654 

1655def validate_inferred_freq(freq, inferred_freq, freq_infer): 

1656 """ 

1657 If the user passes a freq and another freq is inferred from passed data, 

1658 require that they match. 

1659 

1660 Parameters 

1661 ---------- 

1662 freq : DateOffset or None 

1663 inferred_freq : DateOffset or None 

1664 freq_infer : bool 

1665 

1666 Returns 

1667 ------- 

1668 freq : DateOffset or None 

1669 freq_infer : bool 

1670 

1671 Notes 

1672 ----- 

1673 We assume at this point that `maybe_infer_freq` has been called, so 

1674 `freq` is either a DateOffset object or None. 

1675 """ 

1676 if inferred_freq is not None: 

1677 if freq is not None and freq != inferred_freq: 

1678 raise ValueError( 

1679 f"Inferred frequency {inferred_freq} from passed " 

1680 "values does not conform to passed frequency " 

1681 f"{freq.freqstr}" 

1682 ) 

1683 elif freq is None: 

1684 freq = inferred_freq 

1685 freq_infer = False 

1686 

1687 return freq, freq_infer 

1688 

1689 

1690def maybe_infer_freq(freq): 

1691 """ 

1692 Comparing a DateOffset to the string "infer" raises, so we need to 

1693 be careful about comparisons. Make a dummy variable `freq_infer` to 

1694 signify the case where the given freq is "infer" and set freq to None 

1695 to avoid comparison trouble later on. 

1696 

1697 Parameters 

1698 ---------- 

1699 freq : {DateOffset, None, str} 

1700 

1701 Returns 

1702 ------- 

1703 freq : {DateOffset, None} 

1704 freq_infer : bool 

1705 """ 

1706 freq_infer = False 

1707 if not isinstance(freq, DateOffset): 

1708 # if a passed freq is None, don't infer automatically 

1709 if freq != "infer": 

1710 freq = frequencies.to_offset(freq) 

1711 else: 

1712 freq_infer = True 

1713 freq = None 

1714 return freq, freq_infer