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

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

465 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5from typing import ( 

6 TYPE_CHECKING, 

7 Any, 

8 Literal, 

9 Self, 

10 TypeVar, 

11 cast, 

12 overload, 

13) 

14import warnings 

15 

16import numpy as np 

17 

18from pandas._libs import ( 

19 algos as libalgos, 

20 lib, 

21) 

22from pandas._libs.arrays import NDArrayBacked 

23from pandas._libs.tslibs import ( 

24 BaseOffset, 

25 Day, 

26 NaT, 

27 NaTType, 

28 Timedelta, 

29 add_overflowsafe, 

30 astype_overflowsafe, 

31 dt64arr_to_periodarr as c_dt64arr_to_periodarr, 

32 get_unit_from_dtype, 

33 iNaT, 

34 parsing, 

35 period as libperiod, 

36 to_offset, 

37) 

38from pandas._libs.tslibs.dtypes import ( 

39 FreqGroup, 

40 PeriodDtypeBase, 

41) 

42from pandas._libs.tslibs.fields import isleapyear_arr 

43from pandas._libs.tslibs.offsets import ( 

44 Tick, 

45 delta_to_tick, 

46) 

47from pandas._libs.tslibs.period import ( 

48 DIFFERENT_FREQ, 

49 IncompatibleFrequency, 

50 Period, 

51 get_period_field_arr, 

52 period_asfreq_arr, 

53) 

54from pandas.util._decorators import ( 

55 cache_readonly, 

56 doc, 

57 set_module, 

58) 

59 

60from pandas.core.dtypes.common import ( 

61 ensure_object, 

62 pandas_dtype, 

63) 

64from pandas.core.dtypes.dtypes import ( 

65 DatetimeTZDtype, 

66 PeriodDtype, 

67) 

68from pandas.core.dtypes.generic import ( 

69 ABCIndex, 

70 ABCPeriodIndex, 

71 ABCSeries, 

72 ABCTimedeltaArray, 

73) 

74from pandas.core.dtypes.missing import isna 

75 

76from pandas.core.arrays import datetimelike as dtl 

77import pandas.core.common as com 

78 

79if TYPE_CHECKING: 

80 from collections.abc import ( 

81 Callable, 

82 Sequence, 

83 ) 

84 

85 from pandas._typing import ( 

86 AnyArrayLike, 

87 Dtype, 

88 DtypeObj, 

89 FillnaOptions, 

90 NpDtype, 

91 NumpySorter, 

92 NumpyValueArrayLike, 

93 npt, 

94 ) 

95 

96 from pandas.core.dtypes.dtypes import ExtensionDtype 

97 

98 from pandas.core.arrays import ( 

99 DatetimeArray, 

100 TimedeltaArray, 

101 ) 

102 from pandas.core.arrays.base import ExtensionArray 

103 

104 

105BaseOffsetT = TypeVar("BaseOffsetT", bound=BaseOffset) 

106 

107 

108_shared_doc_kwargs = { 

109 "klass": "PeriodArray", 

110} 

111 

112 

113def _field_accessor(name: str, docstring: str | None = None): 

114 def f(self): 

115 base = self.dtype._dtype_code 

116 result = get_period_field_arr(name, self.asi8, base) 

117 return result 

118 

119 f.__name__ = name 

120 f.__doc__ = docstring 

121 return property(f) 

122 

123 

124@set_module("pandas.arrays") 

125# error: Definition of "_concat_same_type" in base class "NDArrayBacked" is 

126# incompatible with definition in base class "ExtensionArray" 

127class PeriodArray(dtl.DatelikeOps, libperiod.PeriodMixin): # type: ignore[misc] 

128 """ 

129 Pandas ExtensionArray for storing Period data. 

130 

131 Users should use :func:`~pandas.array` to create new instances. 

132 

133 Parameters 

134 ---------- 

135 values : Union[PeriodArray, Series[period], ndarray[int], PeriodIndex] 

136 The data to store. These should be arrays that can be directly 

137 converted to ordinals without inference or copy (PeriodArray, 

138 ndarray[int64]), or a box around such an array (Series[period], 

139 PeriodIndex). 

140 dtype : PeriodDtype, optional 

141 A PeriodDtype instance from which to extract a `freq`. If both 

142 `freq` and `dtype` are specified, then the frequencies must match. 

143 copy : bool, default False 

144 Whether to copy the ordinals before storing. 

145 

146 Attributes 

147 ---------- 

148 None 

149 

150 Methods 

151 ------- 

152 None 

153 

154 See Also 

155 -------- 

156 Period: Represents a period of time. 

157 PeriodIndex : Immutable Index for period data. 

158 period_range: Create a fixed-frequency PeriodArray. 

159 array: Construct a pandas array. 

160 

161 Notes 

162 ----- 

163 There are two components to a PeriodArray 

164 

165 - ordinals : integer ndarray 

166 - freq : pd.tseries.offsets.Offset 

167 

168 The values are physically stored as a 1-D ndarray of integers. These are 

169 called "ordinals" and represent some kind of offset from a base. 

170 

171 The `freq` indicates the span covered by each element of the array. 

172 All elements in the PeriodArray have the same `freq`. 

173 

174 Examples 

175 -------- 

176 >>> pd.arrays.PeriodArray(pd.PeriodIndex(["2023-01-01", "2023-01-02"], freq="D")) 

177 <PeriodArray> 

178 ['2023-01-01', '2023-01-02'] 

179 Length: 2, dtype: period[D] 

180 """ 

181 

182 # array priority higher than numpy scalars 

183 __array_priority__ = 1000 

184 _typ = "periodarray" # ABCPeriodArray 

185 _internal_fill_value = np.int64(iNaT) 

186 _recognized_scalars = (Period,) 

187 _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: isinstance( 

188 x, PeriodDtype 

189 ) # check_compatible_with checks freq match 

190 _infer_matches = ("period",) 

191 

192 @property 

193 def _scalar_type(self) -> type[Period]: 

194 return Period 

195 

196 # Names others delegate to us 

197 _other_ops: list[str] = [] 

198 _bool_ops: list[str] = ["is_leap_year"] 

199 _object_ops: list[str] = ["start_time", "end_time", "freq"] 

200 _field_ops: list[str] = [ 

201 "year", 

202 "month", 

203 "day", 

204 "hour", 

205 "minute", 

206 "second", 

207 "weekofyear", 

208 "weekday", 

209 "week", 

210 "dayofweek", 

211 "day_of_week", 

212 "dayofyear", 

213 "day_of_year", 

214 "quarter", 

215 "qyear", 

216 "days_in_month", 

217 "daysinmonth", 

218 ] 

219 _datetimelike_ops: list[str] = _field_ops + _object_ops + _bool_ops 

220 _datetimelike_methods: list[str] = ["strftime", "to_timestamp", "asfreq"] 

221 

222 _dtype: PeriodDtype 

223 

224 # -------------------------------------------------------------------- 

225 # Constructors 

226 

227 def __init__(self, values, dtype: Dtype | None = None, copy: bool = False) -> None: 

228 if dtype is not None: 

229 dtype = pandas_dtype(dtype) 

230 if not isinstance(dtype, PeriodDtype): 

231 raise ValueError(f"Invalid dtype {dtype} for PeriodArray") 

232 

233 if isinstance(values, ABCSeries): 

234 values = values._values 

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

236 raise TypeError("Incorrect dtype") 

237 

238 elif isinstance(values, ABCPeriodIndex): 

239 values = values._values 

240 

241 if isinstance(values, type(self)): 

242 if dtype is not None and dtype != values.dtype: 

243 raise raise_on_incompatible(values, dtype.freq) 

244 values, dtype = values._ndarray, values.dtype 

245 

246 if not copy: 

247 values = np.asarray(values, dtype="int64") 

248 else: 

249 values = np.array(values, dtype="int64", copy=copy) 

250 if dtype is None: 

251 raise ValueError("dtype is not specified and cannot be inferred") 

252 dtype = cast(PeriodDtype, dtype) 

253 NDArrayBacked.__init__(self, values, dtype) 

254 

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

256 @classmethod 

257 def _simple_new( # type: ignore[override] 

258 cls, 

259 values: npt.NDArray[np.int64], 

260 dtype: PeriodDtype, 

261 ) -> Self: 

262 # alias for PeriodArray.__init__ 

263 assertion_msg = "Should be numpy array of type i8" 

264 assert isinstance(values, np.ndarray) and values.dtype == "i8", assertion_msg 

265 return cls(values, dtype=dtype) 

266 

267 @classmethod 

268 def _from_sequence( 

269 cls, 

270 scalars, 

271 *, 

272 dtype: Dtype | None = None, 

273 copy: bool = False, 

274 ) -> Self: 

275 if dtype is not None: 

276 dtype = pandas_dtype(dtype) 

277 if dtype and isinstance(dtype, PeriodDtype): 

278 freq = dtype.freq 

279 else: 

280 freq = None 

281 

282 if isinstance(scalars, cls): 

283 validate_dtype_freq(scalars.dtype, freq) 

284 if copy: 

285 scalars = scalars.copy() 

286 return scalars 

287 

288 periods = np.asarray(scalars, dtype=object) 

289 

290 freq = freq or libperiod.extract_freq(periods) 

291 ordinals = libperiod.extract_ordinals(periods, freq) 

292 dtype = PeriodDtype(freq) 

293 return cls(ordinals, dtype=dtype) 

294 

295 @classmethod 

296 def _from_sequence_of_strings( 

297 cls, strings, *, dtype: ExtensionDtype, copy: bool = False 

298 ) -> Self: 

299 return cls._from_sequence(strings, dtype=dtype, copy=copy) 

300 

301 @classmethod 

302 def _from_datetime64(cls, data, freq, tz=None) -> Self: 

303 """ 

304 Construct a PeriodArray from a datetime64 array 

305 

306 Parameters 

307 ---------- 

308 data : ndarray[datetime64[ns], datetime64[ns, tz]] 

309 freq : str or Tick 

310 tz : tzinfo, optional 

311 

312 Returns 

313 ------- 

314 PeriodArray[freq] 

315 """ 

316 if isinstance(freq, BaseOffset): 

317 freq = PeriodDtype(freq)._freqstr 

318 data, freq = dt64arr_to_periodarr(data, freq, tz) 

319 dtype = PeriodDtype(freq) 

320 return cls(data, dtype=dtype) 

321 

322 @classmethod 

323 def _generate_range(cls, start, end, periods, freq): 

324 periods = dtl.validate_periods(periods) 

325 

326 if freq is not None: 

327 freq = Period._maybe_convert_freq(freq) 

328 

329 if start is not None or end is not None: 

330 subarr, freq = _get_ordinal_range(start, end, periods, freq) 

331 else: 

332 raise ValueError("Not enough parameters to construct Period range") 

333 

334 return subarr, freq 

335 

336 @classmethod 

337 def _from_fields(cls, *, fields: dict, freq) -> Self: 

338 subarr, freq = _range_from_fields(freq=freq, **fields) 

339 dtype = PeriodDtype(freq) 

340 return cls._simple_new(subarr, dtype=dtype) 

341 

342 # ----------------------------------------------------------------- 

343 # DatetimeLike Interface 

344 

345 # error: Argument 1 of "_unbox_scalar" is incompatible with supertype 

346 # "DatetimeLikeArrayMixin"; supertype defines the argument type as 

347 # "Union[Union[Period, Any, Timedelta], NaTType]" 

348 def _unbox_scalar( # type: ignore[override] 

349 self, 

350 value: Period | NaTType, 

351 ) -> np.int64: 

352 if value is NaT: 

353 # error: Item "Period" of "Union[Period, NaTType]" has no attribute "value" 

354 return np.int64(value._value) # type: ignore[union-attr] 

355 elif isinstance(value, self._scalar_type): 

356 self._check_compatible_with(value) 

357 return np.int64(value.ordinal) 

358 else: 

359 raise ValueError(f"'value' should be a Period. Got '{value}' instead.") 

360 

361 def _scalar_from_string(self, value: str) -> Period: 

362 return Period(value, freq=self.freq) 

363 

364 # error: Argument 1 of "_check_compatible_with" is incompatible with 

365 # supertype "DatetimeLikeArrayMixin"; supertype defines the argument type 

366 # as "Period | Timestamp | Timedelta | NaTType" 

367 def _check_compatible_with(self, other: Period | NaTType | PeriodArray) -> None: # type: ignore[override] 

368 if other is NaT: 

369 return 

370 # error: Item "NaTType" of "Period | NaTType | PeriodArray" has no 

371 # attribute "freq" 

372 self._require_matching_freq(other.freq) # type: ignore[union-attr] 

373 

374 # -------------------------------------------------------------------- 

375 # Data / Attributes 

376 

377 @cache_readonly 

378 def dtype(self) -> PeriodDtype: 

379 return self._dtype 

380 

381 # error: Cannot override writeable attribute with read-only property 

382 @property 

383 def freq(self) -> BaseOffset: # type: ignore[override] 

384 """ 

385 Return the frequency object for this PeriodArray. 

386 """ 

387 return self.dtype.freq 

388 

389 @property 

390 def freqstr(self) -> str: 

391 return PeriodDtype(self.freq)._freqstr 

392 

393 def __array__( 

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

395 ) -> np.ndarray: 

396 if dtype == "i8": 

397 # For NumPy 1.x compatibility we cannot use copy=None. And 

398 # `copy=False` has the meaning of `copy=None` here: 

399 if not copy: 

400 result = np.asarray(self.asi8, dtype=dtype) 

401 if self._readonly: 

402 result = result.view() 

403 result.flags.writeable = False 

404 return result 

405 else: 

406 return np.array(self.asi8, dtype=dtype) 

407 

408 if copy is False: 

409 raise ValueError( 

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

411 ) 

412 

413 if dtype == bool: 

414 return ~self._isnan 

415 

416 # This will raise TypeError for non-object dtypes 

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

418 

419 def __arrow_array__(self, type=None): 

420 """ 

421 Convert myself into a pyarrow Array. 

422 """ 

423 import pyarrow 

424 

425 from pandas.core.arrays.arrow.extension_types import ArrowPeriodType 

426 

427 if type is not None: 

428 if pyarrow.types.is_integer(type): 

429 return pyarrow.array(self._ndarray, mask=self.isna(), type=type) 

430 elif isinstance(type, ArrowPeriodType): 

431 # ensure we have the same freq 

432 if self.freqstr != type.freq: 

433 raise TypeError( 

434 "Not supported to convert PeriodArray to array with different " 

435 f"'freq' ({self.freqstr} vs {type.freq})" 

436 ) 

437 else: 

438 raise TypeError( 

439 f"Not supported to convert PeriodArray to '{type}' type" 

440 ) 

441 

442 period_type = ArrowPeriodType(self.freqstr) 

443 storage_array = pyarrow.array(self._ndarray, mask=self.isna(), type="int64") 

444 return pyarrow.ExtensionArray.from_storage(period_type, storage_array) 

445 

446 # -------------------------------------------------------------------- 

447 # Vectorized analogues of Period properties 

448 

449 year = _field_accessor( 

450 "year", 

451 """ 

452 The year of the period. 

453 

454 See Also 

455 -------- 

456 PeriodIndex.day_of_year : The ordinal day of the year. 

457 PeriodIndex.dayofyear : The ordinal day of the year. 

458 PeriodIndex.is_leap_year : Logical indicating if the date belongs to a 

459 leap year. 

460 PeriodIndex.weekofyear : The week ordinal of the year. 

461 PeriodIndex.year : The year of the period. 

462 

463 Examples 

464 -------- 

465 >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y") 

466 >>> idx.year 

467 Index([2023, 2024, 2025], dtype='int64') 

468 """, 

469 ) 

470 month = _field_accessor( 

471 "month", 

472 """ 

473 The month as January=1, December=12. 

474 

475 See Also 

476 -------- 

477 PeriodIndex.days_in_month : The number of days in the month. 

478 PeriodIndex.daysinmonth : The number of days in the month. 

479 

480 Examples 

481 -------- 

482 >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M") 

483 >>> idx.month 

484 Index([1, 2, 3], dtype='int64') 

485 """, 

486 ) 

487 day = _field_accessor( 

488 "day", 

489 """ 

490 The days of the period. 

491 

492 See Also 

493 -------- 

494 PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6. 

495 PeriodIndex.day_of_year : The ordinal day of the year. 

496 PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6. 

497 PeriodIndex.dayofyear : The ordinal day of the year. 

498 PeriodIndex.days_in_month : The number of days in the month. 

499 PeriodIndex.daysinmonth : The number of days in the month. 

500 PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6. 

501 

502 Examples 

503 -------- 

504 >>> idx = pd.PeriodIndex(['2020-01-31', '2020-02-28'], freq='D') 

505 >>> idx.day 

506 Index([31, 28], dtype='int64') 

507 """, 

508 ) 

509 hour = _field_accessor( 

510 "hour", 

511 """ 

512 The hour of the period. 

513 

514 See Also 

515 -------- 

516 PeriodIndex.minute : The minute of the period. 

517 PeriodIndex.second : The second of the period. 

518 PeriodIndex.to_timestamp : Cast to DatetimeArray/Index. 

519 

520 Examples 

521 -------- 

522 >>> idx = pd.PeriodIndex(["2023-01-01 10:00", "2023-01-01 11:00"], freq='h') 

523 >>> idx.hour 

524 Index([10, 11], dtype='int64') 

525 """, 

526 ) 

527 minute = _field_accessor( 

528 "minute", 

529 """ 

530 The minute of the period. 

531 

532 See Also 

533 -------- 

534 PeriodIndex.hour : The hour of the period. 

535 PeriodIndex.second : The second of the period. 

536 PeriodIndex.to_timestamp : Cast to DatetimeArray/Index. 

537 

538 Examples 

539 -------- 

540 >>> idx = pd.PeriodIndex(["2023-01-01 10:30:00", 

541 ... "2023-01-01 11:50:00"], freq='min') 

542 >>> idx.minute 

543 Index([30, 50], dtype='int64') 

544 """, 

545 ) 

546 second = _field_accessor( 

547 "second", 

548 """ 

549 The second of the period. 

550 

551 See Also 

552 -------- 

553 PeriodIndex.hour : The hour of the period. 

554 PeriodIndex.minute : The minute of the period. 

555 PeriodIndex.to_timestamp : Cast to DatetimeArray/Index. 

556 

557 Examples 

558 -------- 

559 >>> idx = pd.PeriodIndex(["2023-01-01 10:00:30", 

560 ... "2023-01-01 10:00:31"], freq='s') 

561 >>> idx.second 

562 Index([30, 31], dtype='int64') 

563 """, 

564 ) 

565 weekofyear = _field_accessor( 

566 "week", 

567 """ 

568 The week ordinal of the year. 

569 

570 See Also 

571 -------- 

572 PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6. 

573 PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6. 

574 PeriodIndex.week : The week ordinal of the year. 

575 PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6. 

576 PeriodIndex.year : The year of the period. 

577 

578 Examples 

579 -------- 

580 >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M") 

581 >>> idx.week # It can be written `weekofyear` 

582 Index([5, 9, 13], dtype='int64') 

583 """, 

584 ) 

585 week = weekofyear 

586 day_of_week = _field_accessor( 

587 "day_of_week", 

588 """ 

589 The day of the week with Monday=0, Sunday=6. 

590 

591 See Also 

592 -------- 

593 PeriodIndex.day : The days of the period. 

594 PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6. 

595 PeriodIndex.day_of_year : The ordinal day of the year. 

596 PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6. 

597 PeriodIndex.dayofyear : The ordinal day of the year. 

598 PeriodIndex.week : The week ordinal of the year. 

599 PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6. 

600 PeriodIndex.weekofyear : The week ordinal of the year. 

601 

602 Examples 

603 -------- 

604 >>> idx = pd.PeriodIndex(["2023-01-01", "2023-01-02", "2023-01-03"], freq="D") 

605 >>> idx.weekday 

606 Index([6, 0, 1], dtype='int64') 

607 """, 

608 ) 

609 dayofweek = day_of_week 

610 weekday = dayofweek 

611 dayofyear = day_of_year = _field_accessor( 

612 "day_of_year", 

613 """ 

614 The ordinal day of the year. 

615 

616 See Also 

617 -------- 

618 PeriodIndex.day : The days of the period. 

619 PeriodIndex.day_of_week : The day of the week with Monday=0, Sunday=6. 

620 PeriodIndex.day_of_year : The ordinal day of the year. 

621 PeriodIndex.dayofweek : The day of the week with Monday=0, Sunday=6. 

622 PeriodIndex.dayofyear : The ordinal day of the year. 

623 PeriodIndex.weekday : The day of the week with Monday=0, Sunday=6. 

624 PeriodIndex.weekofyear : The week ordinal of the year. 

625 PeriodIndex.year : The year of the period. 

626 

627 Examples 

628 -------- 

629 >>> idx = pd.PeriodIndex(["2023-01-10", "2023-02-01", "2023-03-01"], freq="D") 

630 >>> idx.dayofyear 

631 Index([10, 32, 60], dtype='int64') 

632 

633 >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y") 

634 >>> idx 

635 PeriodIndex(['2023', '2024', '2025'], dtype='period[Y-DEC]') 

636 >>> idx.dayofyear 

637 Index([365, 366, 365], dtype='int64') 

638 """, 

639 ) 

640 quarter = _field_accessor( 

641 "quarter", 

642 """ 

643 The quarter of the date. 

644 

645 See Also 

646 -------- 

647 PeriodIndex.qyear : Fiscal year the Period lies in according to its 

648 starting-quarter. 

649 

650 Examples 

651 -------- 

652 >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M") 

653 >>> idx.quarter 

654 Index([1, 1, 1], dtype='int64') 

655 """, 

656 ) 

657 qyear = _field_accessor( 

658 "qyear", 

659 """ 

660 Fiscal year the Period lies in according to its starting-quarter. 

661 

662 The `year` and the `qyear` of the period will be the same if the fiscal 

663 and calendar years are the same. When they are not, the fiscal year 

664 can be different from the calendar year of the period. 

665 

666 Returns 

667 ------- 

668 int 

669 The fiscal year of the period. 

670 

671 See Also 

672 -------- 

673 PeriodIndex.quarter : The quarter of the date. 

674 PeriodIndex.year : The year of the period. 

675 

676 Examples 

677 -------- 

678 If the natural and fiscal year are the same, `qyear` and `year` will 

679 be the same. 

680 

681 >>> per = pd.Period('2018Q1', freq='Q') 

682 >>> per.qyear 

683 2018 

684 >>> per.year 

685 2018 

686 

687 If the fiscal year starts in April (`Q-MAR`), the first quarter of 

688 2018 will start in April 2017. `year` will then be 2017, but `qyear` 

689 will be the fiscal year, 2018. 

690 

691 >>> per = pd.Period('2018Q1', freq='Q-MAR') 

692 >>> per.start_time 

693 Timestamp('2017-04-01 00:00:00') 

694 >>> per.qyear 

695 2018 

696 >>> per.year 

697 2017 

698 """, 

699 ) 

700 

701 days_in_month = _field_accessor( 

702 "days_in_month", 

703 """ 

704 The number of days in the month. 

705 

706 See Also 

707 -------- 

708 PeriodIndex.day : The days of the period. 

709 PeriodIndex.days_in_month : The number of days in the month. 

710 PeriodIndex.daysinmonth : The number of days in the month. 

711 PeriodIndex.month : The month as January=1, December=12. 

712 

713 Examples 

714 -------- 

715 For Series: 

716 

717 >>> period = pd.period_range('2020-1-1 00:00', '2020-3-1 00:00', freq='M') 

718 >>> s = pd.Series(period) 

719 >>> s 

720 0 2020-01 

721 1 2020-02 

722 2 2020-03 

723 dtype: period[M] 

724 >>> s.dt.days_in_month 

725 0 31 

726 1 29 

727 2 31 

728 dtype: int64 

729 

730 For PeriodIndex: 

731 

732 >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M") 

733 >>> idx.days_in_month # It can be also entered as `daysinmonth` 

734 Index([31, 28, 31], dtype='int64') 

735 """, 

736 ) 

737 daysinmonth = days_in_month 

738 

739 @property 

740 def is_leap_year(self) -> npt.NDArray[np.bool_]: 

741 """ 

742 Logical indicating if the date belongs to a leap year. 

743 

744 See Also 

745 -------- 

746 PeriodIndex.qyear : Fiscal year the Period lies in according to its 

747 starting-quarter. 

748 PeriodIndex.year : The year of the period. 

749 

750 Examples 

751 -------- 

752 >>> idx = pd.PeriodIndex(["2023", "2024", "2025"], freq="Y") 

753 >>> idx.is_leap_year 

754 array([False, True, False]) 

755 """ 

756 return isleapyear_arr(np.asarray(self.year)) 

757 

758 def to_timestamp(self, freq=None, how: str = "start") -> DatetimeArray: 

759 """ 

760 Cast to DatetimeArray/Index. 

761 

762 If possible, gives microsecond-unit DatetimeArray/Index. Otherwise 

763 gives nanosecond unit. 

764 

765 Parameters 

766 ---------- 

767 freq : str or DateOffset, optional 

768 Target frequency. The default is 'D' for week or longer, 

769 's' otherwise. 

770 how : {'s', 'e', 'start', 'end'} 

771 Whether to use the start or end of the time period being converted. 

772 

773 Returns 

774 ------- 

775 DatetimeArray/Index 

776 Timestamp representation of given Period-like object. 

777 

778 See Also 

779 -------- 

780 PeriodIndex.day : The days of the period. 

781 PeriodIndex.from_fields : Construct a PeriodIndex from fields 

782 (year, month, day, etc.). 

783 PeriodIndex.from_ordinals : Construct a PeriodIndex from ordinals. 

784 PeriodIndex.hour : The hour of the period. 

785 PeriodIndex.minute : The minute of the period. 

786 PeriodIndex.month : The month as January=1, December=12. 

787 PeriodIndex.second : The second of the period. 

788 PeriodIndex.year : The year of the period. 

789 

790 Examples 

791 -------- 

792 >>> idx = pd.PeriodIndex(["2023-01", "2023-02", "2023-03"], freq="M") 

793 >>> idx.to_timestamp() 

794 DatetimeIndex(['2023-01-01', '2023-02-01', '2023-03-01'], 

795 dtype='datetime64[us]', freq='MS') 

796 

797 The frequency will not be inferred if the index contains less than 

798 three elements, or if the values of index are not strictly monotonic: 

799 

800 >>> idx = pd.PeriodIndex(["2023-01", "2023-02"], freq="M") 

801 >>> idx.to_timestamp() 

802 DatetimeIndex(['2023-01-01', '2023-02-01'], dtype='datetime64[us]', freq=None) 

803 

804 >>> idx = pd.PeriodIndex( 

805 ... ["2023-01", "2023-02", "2023-02", "2023-03"], freq="2M" 

806 ... ) 

807 >>> idx.to_timestamp() 

808 DatetimeIndex(['2023-01-01', '2023-02-01', '2023-02-01', '2023-03-01'], 

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

810 """ 

811 from pandas.core.arrays import DatetimeArray 

812 

813 how = libperiod.validate_end_alias(how) 

814 

815 if self.freq.base == "ns" or freq == "ns": 

816 unit = "ns" 

817 else: 

818 unit = "us" 

819 

820 end = how == "E" 

821 if end: 

822 if freq == "B" or self.freq == "B": 

823 # roll forward to ensure we land on B date 

824 adjust = Timedelta(1, unit="D") - Timedelta(1, unit=unit) 

825 return self.to_timestamp(how="start") + adjust 

826 else: 

827 adjust = Timedelta(1, unit=unit) 

828 return (self + self.freq).to_timestamp(how="start") - adjust 

829 

830 if freq is None: 

831 freq_code = self._dtype._get_to_timestamp_base() 

832 dtype = PeriodDtypeBase(freq_code, 1) 

833 freq = dtype._freqstr 

834 base = freq_code 

835 else: 

836 freq = Period._maybe_convert_freq(freq) 

837 base = freq._period_dtype_code 

838 

839 new_parr = self.asfreq(freq, how=how) 

840 

841 new_data = libperiod.periodarr_to_dt64arr(new_parr.asi8, base) 

842 dta = DatetimeArray._from_sequence(new_data, dtype=new_data.dtype) 

843 assert dta.unit == unit 

844 

845 if self.freq.name == "B": 

846 # See if we can retain BDay instead of Day in cases where 

847 # len(self) is too small for infer_freq to distinguish between them 

848 diffs = libalgos.unique_deltas(self.asi8) 

849 if len(diffs) == 1: 

850 diff = diffs[0] 

851 if diff == self.dtype._n: 

852 dta._freq = self.freq 

853 elif diff == 1: 

854 dta._freq = self.freq.base 

855 # TODO: other cases? 

856 return dta 

857 else: 

858 dta = dta._with_freq("infer") 

859 if freq is not None: 

860 freq = to_offset(freq) 

861 if ( 

862 isinstance(dta.freq, Day) 

863 and not isinstance(freq, Day) 

864 and Timedelta(freq) == Timedelta(days=dta.freq.n) 

865 ): 

866 dta._freq = freq 

867 return dta 

868 

869 # -------------------------------------------------------------------- 

870 

871 def _box_func(self, x) -> Period | NaTType: 

872 return Period._from_ordinal(ordinal=x, freq=self.freq) 

873 

874 @doc(**_shared_doc_kwargs, other="PeriodIndex", other_name="PeriodIndex") 

875 def asfreq(self, freq=None, how: str = "E") -> Self: 

876 """ 

877 Convert the {klass} to the specified frequency `freq`. 

878 

879 Equivalent to applying :meth:`pandas.Period.asfreq` with the given arguments 

880 to each :class:`~pandas.Period` in this {klass}. 

881 

882 Parameters 

883 ---------- 

884 freq : str 

885 A frequency. 

886 how : str {{'E', 'S'}}, default 'E' 

887 Whether the elements should be aligned to the end 

888 or start within pa period. 

889 

890 * 'E', 'END', or 'FINISH' for end, 

891 * 'S', 'START', or 'BEGIN' for start. 

892 

893 January 31st ('END') vs. January 1st ('START') for example. 

894 

895 Returns 

896 ------- 

897 {klass} 

898 The transformed {klass} with the new frequency. 

899 

900 See Also 

901 -------- 

902 {other}.asfreq: Convert each Period in a {other_name} to the given frequency. 

903 Period.asfreq : Convert a :class:`~pandas.Period` object to the given frequency. 

904 

905 Examples 

906 -------- 

907 >>> pidx = pd.period_range("2010-01-01", "2015-01-01", freq="Y") 

908 >>> pidx 

909 PeriodIndex(['2010', '2011', '2012', '2013', '2014', '2015'], 

910 dtype='period[Y-DEC]') 

911 

912 >>> pidx.asfreq("M") 

913 PeriodIndex(['2010-12', '2011-12', '2012-12', '2013-12', '2014-12', 

914 '2015-12'], dtype='period[M]') 

915 

916 >>> pidx.asfreq("M", how="S") 

917 PeriodIndex(['2010-01', '2011-01', '2012-01', '2013-01', '2014-01', 

918 '2015-01'], dtype='period[M]') 

919 """ 

920 how = libperiod.validate_end_alias(how) 

921 if isinstance(freq, BaseOffset) and hasattr(freq, "_period_dtype_code"): 

922 freq = PeriodDtype(freq)._freqstr 

923 freq = Period._maybe_convert_freq(freq) 

924 

925 base1 = self._dtype._dtype_code 

926 base2 = freq._period_dtype_code 

927 

928 asi8 = self.asi8 

929 # self.freq.n can't be negative or 0 

930 end = how == "E" 

931 if end: 

932 ordinal = asi8 + self.dtype._n - 1 

933 else: 

934 ordinal = asi8 

935 

936 new_data = period_asfreq_arr(ordinal, base1, base2, end) 

937 

938 if self._hasna: 

939 new_data[self._isnan] = iNaT 

940 

941 dtype = PeriodDtype(freq) 

942 return type(self)(new_data, dtype=dtype) 

943 

944 # ------------------------------------------------------------------ 

945 # Rendering Methods 

946 

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

948 if boxed: 

949 return str 

950 return "'{}'".format 

951 

952 def _format_native_types( 

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

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

955 """ 

956 actually format my specific types 

957 """ 

958 return libperiod.period_array_strftime( 

959 self.asi8, self.dtype._dtype_code, na_rep, date_format 

960 ) 

961 

962 # ------------------------------------------------------------------ 

963 

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

965 # We handle Period[T] -> Period[U] 

966 # Our parent handles everything else. 

967 dtype = pandas_dtype(dtype) 

968 if dtype == self._dtype: 

969 if not copy: 

970 return self 

971 else: 

972 return self.copy() 

973 if isinstance(dtype, PeriodDtype): 

974 return self.asfreq(dtype.freq) 

975 

976 if lib.is_np_dtype(dtype, "M") or isinstance(dtype, DatetimeTZDtype): 

977 # GH#45038 match PeriodIndex behavior. 

978 tz = getattr(dtype, "tz", None) 

979 unit = dtl.dtype_to_unit(dtype) 

980 # error: Argument 1 to "as_unit" of "TimelikeOps" has incompatible 

981 # type "str"; expected "Literal['s', 'ms', 'us', 'ns']" [arg-type] 

982 return self.to_timestamp().tz_localize(tz).as_unit(unit) # type: ignore[arg-type] 

983 

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

985 

986 def searchsorted( 

987 self, 

988 value: NumpyValueArrayLike | ExtensionArray, 

989 side: Literal["left", "right"] = "left", 

990 sorter: NumpySorter | None = None, 

991 ) -> npt.NDArray[np.intp] | np.intp: 

992 npvalue = self._validate_setitem_value(value).view("M8[ns]") 

993 

994 # Cast to M8 to get datetime-like NaT placement, 

995 # similar to dtl._period_dispatch 

996 m8arr = self._ndarray.view("M8[ns]") 

997 return m8arr.searchsorted(npvalue, side=side, sorter=sorter) 

998 

999 def _pad_or_backfill( 

1000 self, 

1001 *, 

1002 method: FillnaOptions, 

1003 limit: int | None = None, 

1004 limit_area: Literal["inside", "outside"] | None = None, 

1005 copy: bool = True, 

1006 ) -> Self: 

1007 # view as dt64 so we get treated as timelike in core.missing, 

1008 # similar to dtl._period_dispatch 

1009 dta = self.view("M8[ns]") 

1010 result = dta._pad_or_backfill( 

1011 method=method, limit=limit, limit_area=limit_area, copy=copy 

1012 ) 

1013 if copy: 

1014 return cast("Self", result.view(self.dtype)) 

1015 else: 

1016 return self 

1017 

1018 # ------------------------------------------------------------------ 

1019 # Arithmetic Methods 

1020 

1021 def _addsub_int_array_or_scalar( 

1022 self, other: np.ndarray | int, op: Callable[[Any, Any], Any] 

1023 ) -> Self: 

1024 """ 

1025 Add or subtract array of integers. 

1026 

1027 Parameters 

1028 ---------- 

1029 other : np.ndarray[int64] or int 

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

1031 

1032 Returns 

1033 ------- 

1034 result : PeriodArray 

1035 """ 

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

1037 if op is operator.sub: 

1038 other = -other 

1039 res_values = add_overflowsafe(self.asi8, np.asarray(other, dtype="i8")) 

1040 return type(self)(res_values, dtype=self.dtype) 

1041 

1042 def _add_offset(self, other: BaseOffset): 

1043 assert not isinstance(other, Tick) 

1044 

1045 if isinstance(other, Day): 

1046 return self + np.timedelta64(other.n, "D") 

1047 

1048 self._require_matching_freq(other, base=True) 

1049 return self._addsub_int_array_or_scalar(other.n, operator.add) 

1050 

1051 # TODO: can we de-duplicate with Period._add_timedeltalike_scalar? 

1052 def _add_timedeltalike_scalar(self, other): 

1053 """ 

1054 Parameters 

1055 ---------- 

1056 other : timedelta, Tick, np.timedelta64 

1057 

1058 Returns 

1059 ------- 

1060 PeriodArray 

1061 """ 

1062 if not isinstance(self.freq, (Tick, Day)): 

1063 # We cannot add timedelta-like to non-tick PeriodArray 

1064 raise raise_on_incompatible(self, other) 

1065 

1066 if isna(other): 

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

1068 return super()._add_timedeltalike_scalar(other) 

1069 

1070 if isinstance(other, Day): 

1071 td = np.asarray(Timedelta(days=other.n).asm8) 

1072 else: 

1073 td = np.asarray(Timedelta(other).asm8) 

1074 return self._add_timedelta_arraylike(td) 

1075 

1076 def _add_timedelta_arraylike( 

1077 self, other: TimedeltaArray | npt.NDArray[np.timedelta64] 

1078 ) -> Self: 

1079 """ 

1080 Parameters 

1081 ---------- 

1082 other : TimedeltaArray or ndarray[timedelta64] 

1083 

1084 Returns 

1085 ------- 

1086 PeriodArray 

1087 """ 

1088 if not self.dtype._is_tick_like(): 

1089 # We cannot add timedelta-like to non-tick PeriodArray 

1090 raise TypeError( 

1091 f"Cannot add or subtract timedelta64[ns] dtype from {self.dtype}" 

1092 ) 

1093 

1094 dtype = np.dtype(f"m8[{self.dtype._td64_unit}]") 

1095 

1096 # Similar to _check_timedeltalike_freq_compat, but we raise with a 

1097 # more specific exception message if necessary. 

1098 try: 

1099 delta = astype_overflowsafe( 

1100 np.asarray(other), dtype=dtype, copy=False, round_ok=False 

1101 ) 

1102 except ValueError as err: 

1103 # e.g. if we have minutes freq and try to add 30s 

1104 # "Cannot losslessly convert units" 

1105 raise IncompatibleFrequency( 

1106 "Cannot add/subtract timedelta-like from PeriodArray that is " 

1107 "not an integer multiple of the PeriodArray's freq." 

1108 ) from err 

1109 

1110 res_values = add_overflowsafe(self.asi8, np.asarray(delta.view("i8"))) 

1111 return type(self)(res_values, dtype=self.dtype) 

1112 

1113 def _check_timedeltalike_freq_compat(self, other): 

1114 """ 

1115 Arithmetic operations with timedelta-like scalars or array `other` 

1116 are only valid if `other` is an integer multiple of `self.freq`. 

1117 If the operation is valid, find that integer multiple. Otherwise, 

1118 raise because the operation is invalid. 

1119 

1120 Parameters 

1121 ---------- 

1122 other : timedelta, np.timedelta64, Tick, 

1123 ndarray[timedelta64], TimedeltaArray, TimedeltaIndex 

1124 

1125 Returns 

1126 ------- 

1127 multiple : int or ndarray[int64] 

1128 

1129 Raises 

1130 ------ 

1131 IncompatibleFrequency 

1132 """ 

1133 assert self.dtype._is_tick_like() # checked by calling function 

1134 

1135 dtype = np.dtype(f"m8[{self.dtype._td64_unit}]") 

1136 

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

1138 td = np.asarray(Timedelta(other).asm8) 

1139 else: 

1140 td = np.asarray(other) 

1141 

1142 try: 

1143 delta = astype_overflowsafe(td, dtype=dtype, copy=False, round_ok=False) 

1144 except ValueError as err: 

1145 raise raise_on_incompatible(self, other) from err 

1146 

1147 delta = delta.view("i8") 

1148 return lib.item_from_zerodim(delta) 

1149 

1150 # ------------------------------------------------------------------ 

1151 # Reductions 

1152 

1153 def _reduce( 

1154 self, name: str, *, skipna: bool = True, keepdims: bool = False, **kwargs 

1155 ): 

1156 result = super()._reduce(name, skipna=skipna, keepdims=keepdims, **kwargs) 

1157 if keepdims and isinstance(result, np.ndarray): 

1158 return self._from_sequence(result, dtype=self.dtype) 

1159 return result 

1160 

1161 

1162def raise_on_incompatible(left, right) -> IncompatibleFrequency: 

1163 """ 

1164 Helper function to render a consistent error message when raising 

1165 IncompatibleFrequency. 

1166 

1167 Parameters 

1168 ---------- 

1169 left : PeriodArray 

1170 right : None, DateOffset, Period, ndarray, or timedelta-like 

1171 

1172 Returns 

1173 ------- 

1174 IncompatibleFrequency 

1175 Exception to be raised by the caller. 

1176 """ 

1177 # GH#24283 error message format depends on whether right is scalar 

1178 if isinstance(right, (np.ndarray, ABCTimedeltaArray)) or right is None: 

1179 other_freq = None 

1180 elif isinstance(right, BaseOffset): 

1181 with warnings.catch_warnings(): 

1182 warnings.filterwarnings( 

1183 "ignore", r"PeriodDtype\[B\] is deprecated", category=FutureWarning 

1184 ) 

1185 other_freq = PeriodDtype(right)._freqstr 

1186 elif isinstance(right, (ABCPeriodIndex, PeriodArray, Period)): 

1187 other_freq = right.freqstr 

1188 else: 

1189 other_freq = delta_to_tick(Timedelta(right)).freqstr 

1190 

1191 own_freq = PeriodDtype(left.freq)._freqstr 

1192 msg = DIFFERENT_FREQ.format( 

1193 cls=type(left).__name__, own_freq=own_freq, other_freq=other_freq 

1194 ) 

1195 return IncompatibleFrequency(msg) 

1196 

1197 

1198# ------------------------------------------------------------------- 

1199# Constructor Helpers 

1200 

1201 

1202def period_array( 

1203 data: Sequence[Period | str | None] | AnyArrayLike, 

1204 freq: str | Tick | BaseOffset | None = None, 

1205 copy: bool = False, 

1206) -> PeriodArray: 

1207 """ 

1208 Construct a new PeriodArray from a sequence of Period scalars. 

1209 

1210 Parameters 

1211 ---------- 

1212 data : Sequence of Period objects 

1213 A sequence of Period objects. These are required to all have 

1214 the same ``freq.`` Missing values can be indicated by ``None`` 

1215 or ``pandas.NaT``. 

1216 freq : str, Tick, or Offset 

1217 The frequency of every element of the array. This can be specified 

1218 to avoid inferring the `freq` from `data`. 

1219 copy : bool, default False 

1220 Whether to ensure a copy of the data is made. 

1221 

1222 Returns 

1223 ------- 

1224 PeriodArray 

1225 

1226 See Also 

1227 -------- 

1228 PeriodArray 

1229 pandas.PeriodIndex 

1230 

1231 Examples 

1232 -------- 

1233 >>> period_array([pd.Period("2017", freq="Y"), pd.Period("2018", freq="Y")]) 

1234 <PeriodArray> 

1235 ['2017', '2018'] 

1236 Length: 2, dtype: period[Y-DEC] 

1237 

1238 >>> period_array([pd.Period("2017", freq="Y"), pd.Period("2018", freq="Y"), pd.NaT]) 

1239 <PeriodArray> 

1240 ['2017', '2018', 'NaT'] 

1241 Length: 3, dtype: period[Y-DEC] 

1242 

1243 Integers that look like years are handled 

1244 

1245 >>> period_array([2000, 2001, 2002], freq="D") 

1246 <PeriodArray> 

1247 ['2000-01-01', '2001-01-01', '2002-01-01'] 

1248 Length: 3, dtype: period[D] 

1249 

1250 Datetime-like strings may also be passed 

1251 

1252 >>> period_array(["2000-Q1", "2000-Q2", "2000-Q3", "2000-Q4"], freq="Q") 

1253 <PeriodArray> 

1254 ['2000Q1', '2000Q2', '2000Q3', '2000Q4'] 

1255 Length: 4, dtype: period[Q-DEC] 

1256 """ 

1257 data_dtype = getattr(data, "dtype", None) 

1258 

1259 if lib.is_np_dtype(data_dtype, "M"): 

1260 return PeriodArray._from_datetime64(data, freq) 

1261 if isinstance(data_dtype, PeriodDtype): 

1262 out = PeriodArray(data) 

1263 if freq is not None: 

1264 if freq == data_dtype.freq: 

1265 return out 

1266 return out.asfreq(freq) 

1267 return out 

1268 

1269 # other iterable of some kind 

1270 if not isinstance(data, (np.ndarray, list, tuple, ABCSeries)): 

1271 data = list(data) 

1272 

1273 arrdata = np.asarray(data) 

1274 

1275 dtype: PeriodDtype | None 

1276 if freq: 

1277 dtype = PeriodDtype(freq) 

1278 else: 

1279 dtype = None 

1280 

1281 if arrdata.dtype.kind == "f" and len(arrdata) > 0: 

1282 raise TypeError("PeriodIndex does not allow floating point in construction") 

1283 

1284 if arrdata.dtype.kind in "iu": 

1285 arr = arrdata.astype(np.int64, copy=False) 

1286 # error: Argument 2 to "from_ordinals" has incompatible type "Union[str, 

1287 # Tick, None]"; expected "Union[timedelta, BaseOffset, str]" 

1288 ordinals = libperiod.from_ordinals(arr, freq) # type: ignore[arg-type] 

1289 return PeriodArray(ordinals, dtype=dtype) 

1290 

1291 data = ensure_object(arrdata) 

1292 if freq is None: 

1293 freq = libperiod.extract_freq(data) 

1294 dtype = PeriodDtype(freq) 

1295 return PeriodArray._from_sequence(data, dtype=dtype) 

1296 

1297 

1298@overload 

1299def validate_dtype_freq(dtype, freq: BaseOffsetT) -> BaseOffsetT: ... 

1300 

1301 

1302@overload 

1303def validate_dtype_freq(dtype, freq: timedelta | str | None) -> BaseOffset: ... 

1304 

1305 

1306def validate_dtype_freq( 

1307 dtype, freq: BaseOffsetT | BaseOffset | timedelta | str | None 

1308) -> BaseOffsetT: 

1309 """ 

1310 If both a dtype and a freq are available, ensure they match. If only 

1311 dtype is available, extract the implied freq. 

1312 

1313 Parameters 

1314 ---------- 

1315 dtype : dtype 

1316 freq : DateOffset or None 

1317 

1318 Returns 

1319 ------- 

1320 freq : DateOffset 

1321 

1322 Raises 

1323 ------ 

1324 ValueError : non-period dtype 

1325 IncompatibleFrequency : mismatch between dtype and freq 

1326 """ 

1327 if freq is not None: 

1328 freq = to_offset(freq, is_period=True) 

1329 

1330 if dtype is not None: 

1331 dtype = pandas_dtype(dtype) 

1332 if not isinstance(dtype, PeriodDtype): 

1333 raise ValueError("dtype must be PeriodDtype") 

1334 if freq is None: 

1335 freq = dtype.freq 

1336 elif freq != dtype.freq: 

1337 raise IncompatibleFrequency("specified freq and dtype are different") 

1338 # error: Incompatible return value type (got "Union[BaseOffset, Any, None]", 

1339 # expected "BaseOffset") 

1340 return freq # type: ignore[return-value] 

1341 

1342 

1343def dt64arr_to_periodarr( 

1344 data, freq, tz=None 

1345) -> tuple[npt.NDArray[np.int64], BaseOffset]: 

1346 """ 

1347 Convert a datetime-like array to values Period ordinals. 

1348 

1349 Parameters 

1350 ---------- 

1351 data : Union[Series[datetime64[ns]], DatetimeIndex, ndarray[datetime64ns]] 

1352 freq : Optional[Union[str, Tick]] 

1353 Must match the `freq` on the `data` if `data` is a DatetimeIndex 

1354 or Series. 

1355 tz : Optional[tzinfo] 

1356 

1357 Returns 

1358 ------- 

1359 ordinals : ndarray[int64] 

1360 freq : Tick 

1361 The frequency extracted from the Series or DatetimeIndex if that's 

1362 used. 

1363 

1364 """ 

1365 if not isinstance(data.dtype, np.dtype) or data.dtype.kind != "M": 

1366 raise ValueError(f"Wrong dtype: {data.dtype}") 

1367 

1368 if freq is None: 

1369 if isinstance(data, ABCIndex): 

1370 data, freq = data._values, data.freq 

1371 elif isinstance(data, ABCSeries): 

1372 data, freq = data._values, data.dt.freq 

1373 

1374 elif isinstance(data, (ABCIndex, ABCSeries)): 

1375 data = data._values 

1376 

1377 reso = get_unit_from_dtype(data.dtype) 

1378 freq = Period._maybe_convert_freq(freq) 

1379 base = freq._period_dtype_code 

1380 return c_dt64arr_to_periodarr(data.view("i8"), base, tz, reso=reso), freq 

1381 

1382 

1383def _get_ordinal_range(start, end, periods, freq, mult: int = 1): 

1384 if com.count_not_none(start, end, periods) != 2: 

1385 raise ValueError( 

1386 "Of the three parameters: start, end, and periods, " 

1387 "exactly two must be specified" 

1388 ) 

1389 

1390 if freq is not None: 

1391 freq = to_offset(freq, is_period=True) 

1392 mult = freq.n 

1393 

1394 if start is not None: 

1395 start = Period(start, freq) 

1396 if end is not None: 

1397 end = Period(end, freq) 

1398 

1399 is_start_per = isinstance(start, Period) 

1400 is_end_per = isinstance(end, Period) 

1401 

1402 if is_start_per and is_end_per and start.freq != end.freq: 

1403 raise ValueError("start and end must have same freq") 

1404 if start is NaT or end is NaT: 

1405 raise ValueError("start and end must not be NaT") 

1406 

1407 if freq is None: 

1408 if is_start_per: 

1409 freq = start.freq 

1410 elif is_end_per: 

1411 freq = end.freq 

1412 else: # pragma: no cover 

1413 raise ValueError("Could not infer freq from start/end") 

1414 mult = freq.n 

1415 

1416 if periods is not None: 

1417 periods = periods * mult 

1418 if start is None: 

1419 data = np.arange( 

1420 end.ordinal - periods + mult, end.ordinal + 1, mult, dtype=np.int64 

1421 ) 

1422 else: 

1423 data = np.arange( 

1424 start.ordinal, start.ordinal + periods, mult, dtype=np.int64 

1425 ) 

1426 else: 

1427 data = np.arange(start.ordinal, end.ordinal + 1, mult, dtype=np.int64) 

1428 

1429 return data, freq 

1430 

1431 

1432def _range_from_fields( 

1433 year=None, 

1434 month=None, 

1435 quarter=None, 

1436 day=None, 

1437 hour=None, 

1438 minute=None, 

1439 second=None, 

1440 freq=None, 

1441) -> tuple[np.ndarray, BaseOffset]: 

1442 if hour is None: 

1443 hour = 0 

1444 if minute is None: 

1445 minute = 0 

1446 if second is None: 

1447 second = 0 

1448 if day is None: 

1449 day = 1 

1450 

1451 ordinals = [] 

1452 

1453 if quarter is not None: 

1454 if freq is None: 

1455 freq = to_offset("Q", is_period=True) 

1456 base = cast(int, FreqGroup.FR_QTR.value) 

1457 else: 

1458 freq = to_offset(freq, is_period=True) 

1459 base = libperiod.freq_to_dtype_code(freq) 

1460 if base != cast(int, FreqGroup.FR_QTR.value): 

1461 raise AssertionError("base must equal FR_QTR") 

1462 

1463 freqstr = freq.freqstr 

1464 year, quarter = _make_field_arrays(year, quarter) 

1465 for y, q in zip(year, quarter, strict=True): 

1466 calendar_year, calendar_month = parsing.quarter_to_myear(y, q, freqstr) 

1467 val = libperiod.period_ordinal( 

1468 calendar_year, calendar_month, 1, 1, 1, 1, 0, 0, base 

1469 ) 

1470 ordinals.append(val) 

1471 else: 

1472 freq = to_offset(freq, is_period=True) 

1473 base = libperiod.freq_to_dtype_code(freq) 

1474 arrays = _make_field_arrays(year, month, day, hour, minute, second) 

1475 for y, mth, d, h, mn, s in zip(*arrays, strict=True): 

1476 ordinals.append(libperiod.period_ordinal(y, mth, d, h, mn, s, 0, 0, base)) 

1477 

1478 return np.array(ordinals, dtype=np.int64), freq 

1479 

1480 

1481def _make_field_arrays(*fields) -> list[np.ndarray]: 

1482 length = None 

1483 for x in fields: 

1484 if isinstance(x, (list, np.ndarray, ABCSeries)): 

1485 if length is not None and len(x) != length: 

1486 raise ValueError("Mismatched Period array lengths") 

1487 if length is None: 

1488 length = len(x) 

1489 

1490 # error: Argument 2 to "repeat" has incompatible type "Optional[int]"; expected 

1491 # "Union[Union[int, integer[Any]], Union[bool, bool_], ndarray, Sequence[Union[int, 

1492 # integer[Any]]], Sequence[Union[bool, bool_]], Sequence[Sequence[Any]]]" 

1493 return [ 

1494 ( 

1495 np.asarray(x) 

1496 if isinstance(x, (np.ndarray, list, ABCSeries)) 

1497 else np.repeat(x, length) # type: ignore[arg-type] 

1498 ) 

1499 for x in fields 

1500 ]