1from __future__ import annotations
2
3import datetime as dt
4import operator
5from typing import (
6 TYPE_CHECKING,
7 Self,
8)
9import warnings
10
11import numpy as np
12
13from pandas._libs import (
14 NaT,
15 Period,
16 Timestamp,
17 index as libindex,
18 lib,
19)
20from pandas._libs.tslibs import (
21 Resolution,
22 Tick,
23 Timedelta,
24 periods_per_day,
25 timezones,
26 to_offset,
27)
28from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
29from pandas._libs.tslibs.offsets import (
30 DateOffset,
31 prefix_mapping,
32)
33from pandas.errors import Pandas4Warning
34from pandas.util._decorators import (
35 cache_readonly,
36 set_module,
37)
38from pandas.util._exceptions import find_stack_level
39
40from pandas.core.dtypes.common import is_scalar
41from pandas.core.dtypes.dtypes import (
42 ArrowDtype,
43 DatetimeTZDtype,
44)
45from pandas.core.dtypes.generic import ABCSeries
46from pandas.core.dtypes.missing import is_valid_na_for_dtype
47
48from pandas.core.arrays.datetimes import (
49 DatetimeArray,
50 tz_to_dtype,
51)
52import pandas.core.common as com
53from pandas.core.indexes.base import (
54 Index,
55 maybe_extract_name,
56)
57from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
58from pandas.core.tools.times import to_time
59
60if TYPE_CHECKING:
61 from collections.abc import Hashable
62
63 from pandas._typing import (
64 Dtype,
65 DtypeObj,
66 Frequency,
67 IntervalClosedType,
68 TimeAmbiguous,
69 TimeNonexistent,
70 TimeUnit,
71 npt,
72 )
73 from pandas.core.api import (
74 DataFrame,
75 PeriodIndex,
76 )
77
78from pandas._libs.tslibs.dtypes import OFFSET_TO_PERIOD_FREQSTR
79
80
81def _new_DatetimeIndex(cls, d):
82 """
83 This is called upon unpickling, rather than the default which doesn't
84 have arguments and breaks __new__
85 """
86 if "data" in d and not isinstance(d["data"], DatetimeIndex):
87 # Avoid need to verify integrity by calling simple_new directly
88 data = d.pop("data")
89 if not isinstance(data, DatetimeArray):
90 # For backward compat with older pickles, we may need to construct
91 # a DatetimeArray to adapt to the newer _simple_new signature
92 tz = d.pop("tz")
93 freq = d.pop("freq")
94 dta = DatetimeArray._simple_new(data, dtype=tz_to_dtype(tz))
95 else:
96 dta = data
97 if "tz" in d:
98 assert d.pop("tz") == dta.tz
99 # Legacy pickles stored freq on the DatetimeArray; current pickles
100 # include it in ``d``. Migrate either up onto the Index.
101 legacy_freq = vars(dta).pop("_freq", None)
102 freq = d.pop("freq", legacy_freq)
103 result = cls._simple_new(dta, **d)
104 result._freq = freq
105 else:
106 with warnings.catch_warnings():
107 # TODO: If we knew what was going in to **d, we might be able to
108 # go through _simple_new instead
109 warnings.simplefilter("ignore")
110 result = cls.__new__(cls, **d)
111
112 return result
113
114
115@set_module("pandas")
116class DatetimeIndex(DatetimeTimedeltaMixin):
117 """
118 Immutable ndarray-like of datetime64 data.
119
120 Represented internally as int64, and which can be boxed to Timestamp objects
121 that are subclasses of datetime and carry metadata.
122
123 .. versionchanged:: 2.0.0
124 The various numeric date/time attributes (:attr:`~DatetimeIndex.day`,
125 :attr:`~DatetimeIndex.month`, :attr:`~DatetimeIndex.year` etc.) now have dtype
126 ``int32``. Previously they had dtype ``int64``.
127
128 Parameters
129 ----------
130 data : array-like (1-dimensional)
131 Datetime-like data to construct index with.
132 freq : str or pandas offset object, optional
133 One of pandas date offset strings or corresponding objects. The string
134 'infer' can be passed in order to set the frequency of the index as the
135 inferred frequency upon creation.
136 tz : zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or str
137 Set the Timezone of the data.
138 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
139 When clocks moved backward due to DST, ambiguous times may arise.
140 For example in Central European Time (UTC+01), when going from 03:00
141 DST to 02:00 non-DST, 02:30:00 local time occurs both at 00:30:00 UTC
142 and at 01:30:00 UTC. In such a situation, the `ambiguous` parameter
143 dictates how ambiguous times should be handled.
144
145 - 'infer' will attempt to infer fall dst-transition hours based on
146 order. Requires that the timestamps are monotonically increasing.
147 - bool-ndarray where True signifies a DST time, False signifies a
148 non-DST time (note that this flag is only applicable for ambiguous
149 times)
150 - 'NaT' will return NaT where there are ambiguous times
151 - 'raise' will raise a ValueError if there are ambiguous times.
152
153 .. deprecated:: 3.1.0
154 Use :meth:`DatetimeIndex.tz_localize` instead.
155 dayfirst : bool, default False
156 If True, parse dates in `data` with the day first order.
157
158 .. deprecated:: 3.1.0
159 Use :func:`to_datetime` instead.
160 yearfirst : bool, default False
161 If True parse dates in `data` with the year first order.
162
163 .. deprecated:: 3.1.0
164 Use :func:`to_datetime` instead.
165 dtype : numpy.dtype or DatetimeTZDtype or str, default None
166 Note that the only NumPy dtypes allowed are 'datetime64[ns]',
167 'datetime64[us]', 'datetime64[ms]', 'datetime64[s]'.
168 copy : bool, default None
169 Whether to copy input data, only relevant for array, Series, and Index
170 inputs (for other input, e.g. a list, a new array is created anyway).
171 Defaults to True for array input and False for Index/Series.
172 Set to False to avoid copying array input at your own risk (if you
173 know the input data won't be modified elsewhere).
174 Set to True to force copying Series/Index up front.
175 name : label, default None
176 Name to be stored in the index.
177
178 Attributes
179 ----------
180 year
181 month
182 day
183 hour
184 minute
185 second
186 microsecond
187 nanosecond
188 date
189 time
190 timetz
191 dayofyear
192 day_of_year
193 dayofweek
194 day_of_week
195 weekday
196 quarter
197 tz
198 freq
199 freqstr
200 is_month_start
201 is_month_end
202 is_quarter_start
203 is_quarter_end
204 is_year_start
205 is_year_end
206 is_leap_year
207 inferred_freq
208
209 Methods
210 -------
211 normalize
212 strftime
213 snap
214 tz_convert
215 tz_localize
216 round
217 floor
218 ceil
219 to_period
220 to_pydatetime
221 to_series
222 to_frame
223 to_julian_date
224 month_name
225 day_name
226 mean
227 std
228
229 See Also
230 --------
231 Index : The base pandas Index type.
232 TimedeltaIndex : Index of timedelta64 data.
233 PeriodIndex : Index of Period data.
234 to_datetime : Convert argument to datetime.
235 date_range : Create a fixed-frequency DatetimeIndex.
236
237 Notes
238 -----
239 To learn more about the frequency strings, please see
240 :ref:`this link<timeseries.offset_aliases>`.
241
242 Examples
243 --------
244 >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
245 >>> idx
246 DatetimeIndex(['2020-01-01 10:00:00+00:00', '2020-02-01 11:00:00+00:00'],
247 dtype='datetime64[us, UTC]', freq=None)
248 """
249
250 _typ = "datetimeindex"
251
252 _data_cls = DatetimeArray
253 _supports_partial_string_indexing = True
254
255 @property
256 def _engine_type(self) -> type[libindex.DatetimeEngine]:
257 return libindex.DatetimeEngine
258
259 _data: DatetimeArray
260 _values: DatetimeArray
261
262 # --------------------------------------------------------------------
263 # properties dispatching to DatetimeArray without wrapping the result
264
265 @property
266 def tz(self) -> dt.tzinfo | None:
267 """
268 Return the timezone.
269
270 This property returns the timezone information associated with the
271 DatetimeIndex. If the data is timezone-naive (i.e. has no timezone
272 information), it returns None.
273
274 Returns
275 -------
276 zoneinfo.ZoneInfo, datetime.tzinfo, pytz.tzinfo.BaseTZInfo, dateutil.tz.tz.tzfile, or None
277 Returns None when the DatetimeIndex is tz-naive.
278
279 See Also
280 --------
281 DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
282 given time zone, or remove timezone from a tz-aware DatetimeIndex.
283 DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
284 one time zone to another.
285
286 Examples
287 --------
288 >>> idx = pd.DatetimeIndex(
289 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
290 ... )
291 >>> idx.tz
292 datetime.timezone.utc
293 """ # noqa: E501
294 return self._data.tz
295
296 @tz.setter
297 def tz(self, value) -> None:
298 # GH#3746 disallow localizing or converting via attribute assignment;
299 # delegate to DatetimeArray.tz for the raised error message
300 self._data.tz = value
301
302 @property
303 def tzinfo(self) -> dt.tzinfo | None:
304 """
305 Alias for tz attribute.
306
307 Examples
308 --------
309 >>> idx = pd.DatetimeIndex(
310 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
311 ... )
312 >>> idx.tzinfo
313 datetime.timezone.utc
314 """
315 return self.tz
316
317 @cache_readonly
318 def is_normalized(self) -> bool:
319 """
320 Returns True if all of the dates are at midnight ("no time").
321
322 Examples
323 --------
324 >>> idx = pd.DatetimeIndex(["2020-01-01 00:00:00", "2020-02-01 00:00:00"])
325 >>> idx.is_normalized
326 True
327
328 >>> idx = pd.DatetimeIndex(["2020-01-01 00:00:01", "2020-02-01 00:00:00"])
329 >>> idx.is_normalized
330 False
331 """
332 return self._data.is_normalized
333
334 # field_ops: wrap result in Index
335
336 def _wrap_field(self, name: str) -> Index:
337 result = getattr(self._data, name)
338 return Index(result, name=self.name, dtype=result.dtype, copy=False)
339
340 @property
341 def year(self) -> Index:
342 """
343 The year of the datetime.
344
345 This attribute returns the year component of each datetime value
346 in the DatetimeIndex.
347
348 See Also
349 --------
350 DatetimeIndex.month: The month as January=1, December=12.
351 DatetimeIndex.day: The day of the datetime.
352
353 Examples
354 --------
355 >>> idx = pd.DatetimeIndex(["2000-12-31", "2001-12-31", "2002-12-31"])
356 >>> idx.year
357 Index([2000, 2001, 2002], dtype='int32')
358 """
359 return self._wrap_field("year")
360
361 @property
362 def month(self) -> Index:
363 """
364 The month as January=1, December=12.
365
366 This attribute returns the month component of each datetime value
367 in the DatetimeIndex.
368
369 See Also
370 --------
371 DatetimeIndex.year: The year of the datetime.
372 DatetimeIndex.day: The day of the datetime.
373
374 Examples
375 --------
376 >>> idx = pd.DatetimeIndex(["2000-01-31", "2000-02-29", "2000-03-31"])
377 >>> idx.month
378 Index([1, 2, 3], dtype='int32')
379 """
380 return self._wrap_field("month")
381
382 @property
383 def day(self) -> Index:
384 """
385 The day of the datetime.
386
387 This attribute returns the day of the month component of each
388 datetime value in the DatetimeIndex.
389
390 See Also
391 --------
392 DatetimeIndex.year: The year of the datetime.
393 DatetimeIndex.month: The month as January=1, December=12.
394 DatetimeIndex.hour: The hours of the datetime.
395
396 Examples
397 --------
398 >>> idx = pd.DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"])
399 >>> idx.day
400 Index([1, 2, 3], dtype='int32')
401 """
402 return self._wrap_field("day")
403
404 @property
405 def hour(self) -> Index:
406 """
407 The hours of the datetime.
408
409 This attribute returns the hour component of each datetime value
410 in the DatetimeIndex.
411
412 See Also
413 --------
414 DatetimeIndex.day: The day of the datetime.
415 DatetimeIndex.minute: The minutes of the datetime.
416 DatetimeIndex.second: The seconds of the datetime.
417
418 Examples
419 --------
420 >>> idx = pd.date_range("2000-01-01", periods=3, freq="h")
421 >>> idx.hour
422 Index([0, 1, 2], dtype='int32')
423 """
424 return self._wrap_field("hour")
425
426 @property
427 def minute(self) -> Index:
428 """
429 The minutes of the datetime.
430
431 This attribute returns the minute component of each datetime value
432 in the DatetimeIndex.
433
434 See Also
435 --------
436 DatetimeIndex.hour: The hours of the datetime.
437 DatetimeIndex.second: The seconds of the datetime.
438
439 Examples
440 --------
441 >>> idx = pd.date_range("2000-01-01", periods=3, freq="min")
442 >>> idx.minute
443 Index([0, 1, 2], dtype='int32')
444 """
445 return self._wrap_field("minute")
446
447 @property
448 def second(self) -> Index:
449 """
450 The seconds of the datetime.
451
452 This attribute returns the second component of each datetime value
453 in the DatetimeIndex.
454
455 See Also
456 --------
457 DatetimeIndex.minute: The minutes of the datetime.
458 DatetimeIndex.microsecond: The microseconds of the datetime.
459 DatetimeIndex.nanosecond: The nanoseconds of the datetime.
460
461 Examples
462 --------
463 >>> idx = pd.date_range("2000-01-01", periods=3, freq="s")
464 >>> idx.second
465 Index([0, 1, 2], dtype='int32')
466 """
467 return self._wrap_field("second")
468
469 @property
470 def weekday(self) -> Index:
471 """
472 The day of the week with Monday=0, Sunday=6.
473
474 .. deprecated:: 3.1.0
475 Use :attr:`DatetimeIndex.day_of_week` instead.
476 """
477 # GH#12816
478 warnings.warn(
479 "DatetimeIndex.weekday is deprecated and will be removed "
480 "in a future version. Use DatetimeIndex.day_of_week instead.",
481 Pandas4Warning,
482 stacklevel=find_stack_level(),
483 )
484 return self._wrap_field("day_of_week")
485
486 @property
487 def dayofweek(self) -> Index:
488 """
489 The day of the week with Monday=0, Sunday=6.
490
491 .. deprecated:: 3.1.0
492 Use :attr:`DatetimeIndex.day_of_week` instead.
493 """
494 warnings.warn(
495 "DatetimeIndex.dayofweek is deprecated and will be removed in a "
496 "future version. Use DatetimeIndex.day_of_week instead.",
497 Pandas4Warning,
498 stacklevel=find_stack_level(),
499 )
500 return self._wrap_field("day_of_week")
501
502 @property
503 def day_of_week(self) -> Index:
504 """
505 The day of the week with Monday=0, Sunday=6.
506
507 Return the day of the week. It is assumed the week starts on
508 Monday, which is denoted by 0 and ends on Sunday which is denoted
509 by 6.
510
511 See Also
512 --------
513 DatetimeIndex.day_of_year : The ordinal day of the year.
514 DatetimeIndex.day_name : Return the name of the day of the week.
515
516 Examples
517 --------
518 >>> idx = pd.DatetimeIndex(["2016-12-31", "2017-01-01", "2017-01-02"])
519 >>> idx.day_of_week
520 Index([5, 6, 0], dtype='int32')
521 """
522 return self._wrap_field("day_of_week")
523
524 @property
525 def dayofyear(self) -> Index:
526 """
527 The ordinal day of the year.
528
529 .. deprecated:: 3.1.0
530 Use :attr:`DatetimeIndex.day_of_year` instead.
531 """
532 warnings.warn(
533 "DatetimeIndex.dayofyear is deprecated and will be removed in a "
534 "future version. Use DatetimeIndex.day_of_year instead.",
535 Pandas4Warning,
536 stacklevel=find_stack_level(),
537 )
538 return self._wrap_field("day_of_year")
539
540 @property
541 def day_of_year(self) -> Index:
542 """
543 The ordinal day of the year.
544
545 This attribute returns the day of the year for each datetime value
546 in the DatetimeIndex. Values range from 1 to 365 (or 366 for leap
547 years).
548
549 See Also
550 --------
551 DatetimeIndex.day_of_week : The day of the week with Monday=0, Sunday=6.
552 DatetimeIndex.day : The day of the datetime.
553
554 Examples
555 --------
556 >>> idx = pd.DatetimeIndex(["2020-01-01", "2020-02-01"])
557 >>> idx.day_of_year
558 Index([1, 32], dtype='int32')
559 """
560 return self._wrap_field("day_of_year")
561
562 @property
563 def quarter(self) -> Index:
564 """
565 The quarter of the date.
566
567 This attribute returns the quarter of the year for each datetime
568 value in the DatetimeIndex. Quarter 1 includes January through
569 March, quarter 2 includes April through June, quarter 3 includes
570 July through September, and quarter 4 includes October through
571 December.
572
573 See Also
574 --------
575 DatetimeIndex.month : The month as January=1, December=12.
576 DatetimeIndex.is_quarter_start : Indicator for whether the date is the
577 first day of a quarter.
578 DatetimeIndex.is_quarter_end : Indicator for whether the date is the
579 last day of a quarter.
580
581 Examples
582 --------
583 >>> idx = pd.DatetimeIndex(["2020-01-01", "2020-04-01"])
584 >>> idx.quarter
585 Index([1, 2], dtype='int32')
586 """
587 return self._wrap_field("quarter")
588
589 @property
590 def days_in_month(self) -> Index:
591 """
592 The number of days in the month.
593
594 This attribute returns the total number of days in the month for
595 each datetime value in the DatetimeIndex. The value depends on the
596 month and whether the year is a leap year (e.g., February has 29
597 days in a leap year).
598
599 See Also
600 --------
601 DatetimeIndex.day : Return the day of the month.
602 DatetimeIndex.is_month_end : Return a boolean indicating if the
603 date is the last day of the month.
604 DatetimeIndex.is_month_start : Return a boolean indicating if the
605 date is the first day of the month.
606 DatetimeIndex.month : Return the month as January=1 through
607 December=12.
608
609 Examples
610 --------
611 >>> idx = pd.DatetimeIndex(["2020-01-01", "2020-02-01"])
612 >>> idx.days_in_month
613 Index([31, 29], dtype='int32')
614 """
615 return self._wrap_field("days_in_month")
616
617 @property
618 def daysinmonth(self) -> Index:
619 """
620 The number of days in the month.
621
622 .. deprecated:: 3.1.0
623 Use :attr:`DatetimeIndex.days_in_month` instead.
624 """
625 warnings.warn(
626 "DatetimeIndex.daysinmonth is deprecated and will be removed in a "
627 "future version. Use DatetimeIndex.days_in_month instead.",
628 Pandas4Warning,
629 stacklevel=find_stack_level(),
630 )
631 return self._wrap_field("days_in_month")
632
633 @property
634 def microsecond(self) -> Index:
635 """
636 The microseconds of the datetime.
637
638 This attribute returns the microsecond component of each datetime
639 value in the DatetimeIndex. Values range from 0 to 999999 (one
640 microsecond is one millionth of a second).
641
642 See Also
643 --------
644 DatetimeIndex.second: The seconds of the datetime.
645 DatetimeIndex.nanosecond: The nanoseconds of the datetime.
646
647 Examples
648 --------
649 >>> idx = pd.date_range("2000-01-01", periods=3, freq="us")
650 >>> idx.microsecond
651 Index([0, 1, 2], dtype='int32')
652 """
653 return self._wrap_field("microsecond")
654
655 @property
656 def nanosecond(self) -> Index:
657 """
658 The nanoseconds of the datetime.
659
660 This attribute returns the nanosecond component of each datetime
661 value in the DatetimeIndex. Values range from 0 to 999 (one
662 nanosecond is one billionth of a second).
663
664 See Also
665 --------
666 DatetimeIndex.second: The seconds of the datetime.
667 DatetimeIndex.microsecond: The microseconds of the datetime.
668
669 Examples
670 --------
671 >>> idx = pd.date_range("2000-01-01", periods=3, freq="ns")
672 >>> idx.nanosecond
673 Index([0, 1, 2], dtype='int32')
674 """
675 return self._wrap_field("nanosecond")
676
677 # bool_ops: return raw result
678
679 @property
680 def is_month_start(self) -> npt.NDArray[np.bool_]:
681 """
682 Indicates whether the date is the first day of the month.
683
684 This boolean attribute evaluates to True if the date falls on the
685 first day of a calendar month, and False otherwise.
686 If the index has a business ``freq`` (e.g. ``"BMS"``), the result
687 follows that frequency's month boundaries instead of the calendar's.
688
689 See Also
690 --------
691 DatetimeIndex.is_month_end : Return a boolean indicating whether the
692 date is the last day of the month.
693
694 Examples
695 --------
696 >>> idx = pd.date_range("2018-02-27", periods=3)
697 >>> idx.is_month_start
698 array([False, False, True])
699 """
700 return self._data._get_start_end_field("is_month_start", self.freq)
701
702 @property
703 def is_month_end(self) -> npt.NDArray[np.bool_]:
704 """
705 Indicates whether the date is the last day of the month.
706
707 This boolean attribute evaluates to True if the date falls on the
708 last day of a calendar month, and False otherwise.
709 If the index has a business ``freq`` (e.g. ``"BME"``), the result
710 follows that frequency's month boundaries instead of the calendar's.
711
712 See Also
713 --------
714 DatetimeIndex.is_month_start : Return a boolean indicating whether
715 the date is the first day of the month.
716
717 Examples
718 --------
719 >>> idx = pd.date_range("2018-02-27", periods=3)
720 >>> idx.is_month_end
721 array([False, True, False])
722 """
723 return self._data._get_start_end_field("is_month_end", self.freq)
724
725 @property
726 def is_quarter_start(self) -> npt.NDArray[np.bool_]:
727 """
728 Indicator for whether the date is the first day of a quarter.
729
730 This boolean attribute evaluates to True if the date falls on the
731 first day of a calendar quarter (January 1, April 1, July 1, or
732 October 1), and False otherwise.
733 If the index has a business or anchored ``freq`` (e.g. ``"BQS"`` or
734 ``"QS-FEB"``), the result follows that frequency's quarter boundaries
735 instead of the calendar's.
736
737 See Also
738 --------
739 DatetimeIndex.quarter : Return the quarter of the date.
740 DatetimeIndex.is_quarter_end : Similar property for indicating the
741 quarter end.
742
743 Examples
744 --------
745 >>> idx = pd.date_range("2017-03-30", periods=4)
746 >>> idx.is_quarter_start
747 array([False, False, True, False])
748 """
749 return self._data._get_start_end_field("is_quarter_start", self.freq)
750
751 @property
752 def is_quarter_end(self) -> npt.NDArray[np.bool_]:
753 """
754 Indicator for whether the date is the last day of a quarter.
755
756 This boolean attribute evaluates to True if the date falls on the
757 last day of a calendar quarter (March 31, June 30, September 30, or
758 December 31), and False otherwise.
759 If the index has a business or anchored ``freq`` (e.g. ``"BQE"`` or
760 ``"QE-NOV"``), the result follows that frequency's quarter boundaries
761 instead of the calendar's.
762
763 See Also
764 --------
765 DatetimeIndex.quarter : Return the quarter of the date.
766 DatetimeIndex.is_quarter_start : Similar property indicating the
767 quarter start.
768
769 Examples
770 --------
771 >>> idx = pd.date_range("2017-03-30", periods=4)
772 >>> idx.is_quarter_end
773 array([False, True, False, False])
774 """
775 return self._data._get_start_end_field("is_quarter_end", self.freq)
776
777 @property
778 def is_year_start(self) -> npt.NDArray[np.bool_]:
779 """
780 Indicate whether the date is the first day of a year.
781
782 This boolean attribute evaluates to True if the date is January 1st,
783 and False otherwise.
784 If the index has a business or anchored ``freq`` (e.g. ``"BYS"`` or
785 ``"YS-APR"``), the result follows that frequency's year boundaries
786 instead of the calendar's.
787
788 See Also
789 --------
790 DatetimeIndex.is_year_end : Similar property indicating the last day
791 of the year.
792
793 Examples
794 --------
795 >>> idx = pd.date_range("2017-12-30", periods=3)
796 >>> idx.is_year_start
797 array([False, False, True])
798
799 With a business ``freq``, the first business day of the year counts
800 as the year start even when it is not January 1st:
801
802 >>> idx = pd.date_range("2020-10-30", periods=4, freq="BYS")
803 >>> idx
804 DatetimeIndex(['2021-01-01', '2022-01-03', '2023-01-02', '2024-01-01'],
805 dtype='datetime64[us]', freq='BYS-JAN')
806 >>> idx.is_year_start
807 array([ True, True, True, True])
808 """
809 return self._data._get_start_end_field("is_year_start", self.freq)
810
811 @property
812 def is_year_end(self) -> npt.NDArray[np.bool_]:
813 """
814 Indicate whether the date is the last day of the year.
815
816 This boolean attribute evaluates to True if the date is December
817 31st, and False otherwise.
818 If the index has a business or anchored ``freq`` (e.g. ``"BYE"`` or
819 ``"YE-MAR"``), the result follows that frequency's year boundaries
820 instead of the calendar's.
821
822 See Also
823 --------
824 DatetimeIndex.is_year_start : Similar property indicating the start
825 of the year.
826
827 Examples
828 --------
829 >>> idx = pd.date_range("2017-12-30", periods=3)
830 >>> idx.is_year_end
831 array([False, True, False])
832 """
833 return self._data._get_start_end_field("is_year_end", self.freq)
834
835 @property
836 def is_leap_year(self) -> npt.NDArray[np.bool_]:
837 """
838 Boolean indicator if the date belongs to a leap year.
839
840 A leap year is a year, which has 366 days (instead of 365) including
841 29th of February as an intercalary day. Leap years are years which
842 are multiples of four with the exception of years divisible by 100
843 but not by 400.
844
845 See Also
846 --------
847 DatetimeIndex.is_year_end : Indicate whether the date is the last
848 day of the year.
849 DatetimeIndex.is_year_start : Indicate whether the date is the first
850 day of a year.
851
852 Examples
853 --------
854 >>> idx = pd.date_range("2012-01-01", "2015-01-01", freq="YE")
855 >>> idx.is_leap_year
856 array([ True, False, False])
857 """
858 return self._data.is_leap_year
859
860 # --------------------------------------------------------------------
861 # properties and methods dispatching to DatetimeArray, result not wrapped
862
863 @property
864 def date(self) -> npt.NDArray[np.object_]:
865 """
866 Returns numpy array of python :class:`datetime.date` objects.
867
868 Namely, the date part of Timestamps without time and
869 timezone information.
870
871 See Also
872 --------
873 DatetimeIndex.time : Returns numpy array of :class:`datetime.time` objects.
874 The time part of the Timestamps.
875 DatetimeIndex.year : The year of the datetime.
876 DatetimeIndex.month : The month as January=1, December=12.
877 DatetimeIndex.day : The day of the datetime.
878
879 Examples
880 --------
881 >>> idx = pd.DatetimeIndex(
882 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
883 ... )
884 >>> idx.date
885 array([datetime.date(2020, 1, 1), datetime.date(2020, 2, 1)], dtype=object)
886 """
887 return self._data.date
888
889 @property
890 def time(self) -> npt.NDArray[np.object_]:
891 """
892 Returns numpy array of :class:`datetime.time` objects.
893
894 The time part of the Timestamps.
895
896 See Also
897 --------
898 DatetimeIndex.timetz : Returns numpy array of :class:`datetime.time`
899 objects with timezones. The time part of the Timestamps.
900 DatetimeIndex.date : Returns numpy array of python :class:`datetime.date`
901 objects. Namely, the date part of Timestamps without time and timezone
902 information.
903
904 Examples
905 --------
906 >>> idx = pd.DatetimeIndex(
907 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
908 ... )
909 >>> idx.time
910 array([datetime.time(10, 0), datetime.time(11, 0)], dtype=object)
911 """
912 return self._data.time
913
914 @property
915 def timetz(self) -> npt.NDArray[np.object_]:
916 """
917 Returns numpy array of :class:`datetime.time` objects with timezones.
918
919 The time part of the Timestamps.
920
921 See Also
922 --------
923 DatetimeIndex.time : Returns numpy array of :class:`datetime.time` objects.
924 The time part of the Timestamps.
925 DatetimeIndex.tz : Return the timezone.
926
927 Examples
928 --------
929 >>> idx = pd.DatetimeIndex(
930 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
931 ... )
932 >>> idx.timetz
933 array([datetime.time(10, 0, tzinfo=datetime.timezone.utc),
934 datetime.time(11, 0, tzinfo=datetime.timezone.utc)], dtype=object)
935 """
936 return self._data.timetz
937
938 def to_pydatetime(self) -> npt.NDArray[np.object_]:
939 """
940 Return an ndarray of ``datetime.datetime`` objects.
941
942 This method converts each element in the DatetimeIndex to a native
943 Python ``datetime.datetime`` object, including timezone information
944 if present.
945
946 Returns
947 -------
948 numpy.ndarray
949 An ndarray of ``datetime.datetime`` objects.
950
951 See Also
952 --------
953 DatetimeIndex.to_julian_date : Converts Datetime Array to float64 ndarray
954 of Julian Dates.
955
956 Examples
957 --------
958 >>> idx = pd.date_range("2018-02-27", periods=3)
959 >>> idx.to_pydatetime()
960 array([datetime.datetime(2018, 2, 27, 0, 0),
961 datetime.datetime(2018, 2, 28, 0, 0),
962 datetime.datetime(2018, 3, 1, 0, 0)], dtype=object)
963 """
964 return self._data.to_pydatetime()
965
966 def std(
967 self,
968 axis=None,
969 dtype=None,
970 out=None,
971 ddof: int = 1,
972 keepdims: bool = False,
973 skipna: bool = True,
974 ) -> Timedelta:
975 """
976 Return sample standard deviation over requested axis.
977
978 Normalized by `N-1` by default. This can be changed using ``ddof``.
979
980 Parameters
981 ----------
982 axis : int, optional
983 Axis for the function to be applied on.
984 dtype : None
985 Unused; for compatibility with :func:`numpy.std`.
986 out : None
987 Not supported; for compatibility with :func:`numpy.std`.
988 ddof : int, default 1
989 Degrees of Freedom. The divisor used in calculations is `N - ddof`,
990 where `N` represents the number of elements.
991 keepdims : bool, default False
992 Not supported; for compatibility with :func:`numpy.std`.
993 skipna : bool, default True
994 Exclude NA/null values.
995
996 Returns
997 -------
998 Timedelta
999 Standard deviation over requested axis.
1000
1001 See Also
1002 --------
1003 numpy.ndarray.std : Returns the standard deviation of the array elements
1004 along given axis.
1005 Series.std : Return sample standard deviation over requested axis.
1006
1007 Examples
1008 --------
1009 >>> idx = pd.date_range("2001-01-01 00:00", periods=3)
1010 >>> idx
1011 DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'],
1012 dtype='datetime64[us]', freq='D')
1013 >>> idx.std()
1014 Timedelta('1 days 00:00:00')
1015 """
1016 return self._data.std(
1017 axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims, skipna=skipna
1018 )
1019
1020 # --------------------------------------------------------------------
1021 # methods that dispatch to DatetimeArray and wrap result
1022
1023 def strftime(self, date_format) -> Index:
1024 """
1025 Convert to Index using specified date_format.
1026
1027 Return an Index of formatted strings specified by date_format, which
1028 supports the same string format as the python standard library. Details
1029 of the string format can be found in `python string format
1030 doc <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior>`__.
1031
1032 Formats supported by the C `strftime` API but not by the python string format
1033 doc (such as `"%R"`, `"%r"`) are not officially supported and should be
1034 preferably replaced with their supported equivalents (such as `"%H:%M"`,
1035 `"%I:%M:%S %p"`).
1036 Note that `PeriodIndex` support additional directives, detailed in
1037 `Period.strftime`.
1038
1039 Parameters
1040 ----------
1041 date_format : str
1042 Date format string (e.g. "%Y-%m-%d").
1043
1044 Returns
1045 -------
1046 ndarray[object]
1047 NumPy ndarray of formatted strings.
1048
1049 See Also
1050 --------
1051 to_datetime : Convert the given argument to datetime.
1052 DatetimeIndex.normalize : Return DatetimeIndex with times to midnight.
1053 DatetimeIndex.round : Round the DatetimeIndex to the specified freq.
1054 DatetimeIndex.floor : Floor the DatetimeIndex to the specified freq.
1055 Timestamp.strftime : Format a single Timestamp.
1056 Period.strftime : Format a single Period.
1057
1058 Examples
1059 --------
1060 >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), periods=3, freq="s")
1061 >>> rng.strftime("%B %d, %Y, %r")
1062 Index(['March 10, 2018, 09:00:00 AM', 'March 10, 2018, 09:00:01 AM',
1063 'March 10, 2018, 09:00:02 AM'],
1064 dtype='str')
1065 """
1066 arr = self._data.strftime(date_format)
1067 return Index(arr, name=self.name, dtype=arr.dtype, copy=False)
1068
1069 def month_name(self, locale=None) -> Index:
1070 """
1071 Return the month names with specified locale.
1072
1073 This method returns the full name of the month (e.g., "January", "February")
1074 for each datetime value in the Series/Index. The names can be localized
1075 to different languages using the locale parameter.
1076
1077 Parameters
1078 ----------
1079 locale : str, optional
1080 Locale determining the language in which to return the month name.
1081 Default is English locale (``'en_US.utf8'``). Use the command
1082 ``locale -a`` on your terminal on Unix systems to find your locale
1083 language code.
1084
1085 Returns
1086 -------
1087 Index
1088 Index of month names.
1089
1090 See Also
1091 --------
1092 DatetimeIndex.day_name : Return the day names with specified locale.
1093
1094 Examples
1095 --------
1096 >>> idx = pd.date_range(start="2018-01", freq="ME", periods=3)
1097 >>> idx
1098 DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31'],
1099 dtype='datetime64[us]', freq='ME')
1100 >>> idx.month_name()
1101 Index(['January', 'February', 'March'], dtype='str')
1102
1103 Using the ``locale`` parameter you can set a different locale language,
1104 for example: ``idx.month_name(locale='pt_BR.utf8')`` will return month
1105 names in Brazilian Portuguese language.
1106
1107 >>> idx.month_name(locale="pt_BR.utf8") # doctest: +SKIP
1108 Index(['Janeiro', 'Fevereiro', 'Março'], dtype='str')
1109 """
1110 arr = self._data.month_name(locale=locale)
1111 return Index(arr, name=self.name, dtype=arr.dtype, copy=False)
1112
1113 def day_name(self, locale=None) -> Index:
1114 """
1115 Return the day names with specified locale.
1116
1117 This method returns the full name of the day of the week (e.g., "Monday",
1118 "Tuesday") for each datetime value in the Series/Index. The names can be
1119 localized to different languages using the locale parameter.
1120
1121 Parameters
1122 ----------
1123 locale : str, optional
1124 Locale determining the language in which to return the day name.
1125 Default is English locale (``'en_US.utf8'``). Use the command
1126 ``locale -a`` on your terminal on Unix systems to find your locale
1127 language code.
1128
1129 Returns
1130 -------
1131 Index
1132 Index of day names.
1133
1134 See Also
1135 --------
1136 DatetimeIndex.month_name : Return the month names with specified locale.
1137
1138 Examples
1139 --------
1140 >>> idx = pd.date_range(start="2018-01-01", freq="D", periods=3)
1141 >>> idx
1142 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03'],
1143 dtype='datetime64[us]', freq='D')
1144 >>> idx.day_name()
1145 Index(['Monday', 'Tuesday', 'Wednesday'], dtype='str')
1146
1147 Using the ``locale`` parameter you can set a different locale language,
1148 for example: ``idx.day_name(locale='pt_BR.utf8')`` will return day
1149 names in Brazilian Portuguese language.
1150
1151 >>> idx.day_name(locale="pt_BR.utf8") # doctest: +SKIP
1152 Index(['Segunda', 'Terça', 'Quarta'], dtype='str')
1153 """
1154 arr = self._data.day_name(locale=locale)
1155 return Index(arr, name=self.name, dtype=arr.dtype, copy=False)
1156
1157 # error: Signature of "round" incompatible with supertype "Index"
1158 # freq-based, unlike Index.round(decimals)
1159 def round( # type: ignore[override]
1160 self,
1161 freq,
1162 ambiguous: TimeAmbiguous = "raise",
1163 nonexistent: TimeNonexistent = "raise",
1164 ) -> Self:
1165 """
1166 Perform round operation on the data to the specified `freq`.
1167
1168 This method rounds each datetime value in the Series/Index to the
1169 nearest specified frequency using standard rounding rules (round half
1170 to even).
1171
1172 Parameters
1173 ----------
1174 freq : str or Offset
1175 The frequency level to round the index to. Must be a fixed
1176 frequency like 's' (second) not 'ME' (month end). See
1177 :ref:`frequency aliases <timeseries.offset_aliases>` for
1178 a list of possible `freq` values.
1179 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
1180 - 'infer' will attempt to infer fall dst-transition hours based on
1181 order. Requires that the timestamps are monotonically increasing.
1182 - bool-ndarray where True signifies a DST time, False designates
1183 a non-DST time (note that this flag is only applicable for
1184 ambiguous times)
1185 - 'NaT' will return NaT where there are ambiguous times
1186 - 'raise' will raise a ValueError if there are ambiguous
1187 times.
1188 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
1189 default 'raise'
1190 A nonexistent time does not exist in a particular timezone
1191 where clocks moved forward due to DST.
1192
1193 - 'shift_forward' will shift the nonexistent time forward to the
1194 closest existing time
1195 - 'shift_backward' will shift the nonexistent time backward to the
1196 closest existing time
1197 - 'NaT' will return NaT where there are nonexistent times
1198 - timedelta objects will shift nonexistent times by the timedelta
1199 - 'raise' will raise a ValueError if there are
1200 nonexistent times.
1201
1202 Returns
1203 -------
1204 DatetimeIndex
1205 Index with each value rounded to the specified `freq`.
1206
1207 Raises
1208 ------
1209 ValueError if the `freq` cannot be converted.
1210
1211 See Also
1212 --------
1213 DatetimeIndex.floor : Perform floor operation on the data to the
1214 specified `freq`.
1215 DatetimeIndex.ceil : Perform ceil operation on the data to the
1216 specified `freq`.
1217 DatetimeIndex.snap : Snap time stamps to nearest occurring frequency.
1218
1219 Notes
1220 -----
1221 If the timestamps have a timezone, rounding will take place relative to the
1222 local ("wall") time and re-localized to the same timezone. When rounding
1223 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to
1224 control the re-localization behavior.
1225
1226 Examples
1227 --------
1228 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min")
1229 >>> rng
1230 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00',
1231 '2018-01-01 12:01:00'],
1232 dtype='datetime64[us]', freq='min')
1233 >>> rng.round("h")
1234 DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00',
1235 '2018-01-01 12:00:00'],
1236 dtype='datetime64[us]', freq=None)
1237
1238 When rounding near a daylight savings time transition, use ``ambiguous`` or
1239 ``nonexistent`` to control how the timestamp should be re-localized.
1240
1241 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 01:30:00"], tz="Europe/Amsterdam")
1242 >>> rng_tz.round("2h", ambiguous=False)
1243 DatetimeIndex(['2021-10-31 02:00:00+01:00'],
1244 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1245 >>> rng_tz.round("2h", ambiguous=True)
1246 DatetimeIndex(['2021-10-31 02:00:00+02:00'],
1247 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1248 """
1249 arr = self._data.round(freq, ambiguous, nonexistent)
1250 return type(self)._simple_new(arr, name=self.name)
1251
1252 def floor(
1253 self,
1254 freq,
1255 ambiguous: TimeAmbiguous = "raise",
1256 nonexistent: TimeNonexistent = "raise",
1257 ) -> Self:
1258 """
1259 Perform floor operation on the data to the specified `freq`.
1260
1261 This method rounds each datetime value in the Series/Index down to
1262 the specified frequency (i.e., towards negative infinity).
1263
1264 Parameters
1265 ----------
1266 freq : str or Offset
1267 The frequency level to floor the index to. Must be a fixed
1268 frequency like 's' (second) not 'ME' (month end). See
1269 :ref:`frequency aliases <timeseries.offset_aliases>` for
1270 a list of possible `freq` values.
1271 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
1272 - 'infer' will attempt to infer fall dst-transition hours based on
1273 order. Requires that the timestamps are monotonically increasing.
1274 - bool-ndarray where True signifies a DST time, False designates
1275 a non-DST time (note that this flag is only applicable for
1276 ambiguous times)
1277 - 'NaT' will return NaT where there are ambiguous times
1278 - 'raise' will raise a ValueError if there are ambiguous
1279 times.
1280 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
1281 default 'raise'
1282 A nonexistent time does not exist in a particular timezone
1283 where clocks moved forward due to DST.
1284
1285 - 'shift_forward' will shift the nonexistent time forward to the
1286 closest existing time
1287 - 'shift_backward' will shift the nonexistent time backward to the
1288 closest existing time
1289 - 'NaT' will return NaT where there are nonexistent times
1290 - timedelta objects will shift nonexistent times by the timedelta
1291 - 'raise' will raise a ValueError if there are
1292 nonexistent times.
1293
1294 Returns
1295 -------
1296 DatetimeIndex
1297 Index with each value floored to the specified `freq`.
1298
1299 Raises
1300 ------
1301 ValueError if the `freq` cannot be converted.
1302
1303 See Also
1304 --------
1305 DatetimeIndex.round : Perform round operation on the data to the
1306 specified `freq`.
1307 DatetimeIndex.ceil : Perform ceil operation on the data to the
1308 specified `freq`.
1309 DatetimeIndex.snap : Snap time stamps to nearest occurring frequency.
1310
1311 Notes
1312 -----
1313 If the timestamps have a timezone, flooring will take place relative to the
1314 local ("wall") time and re-localized to the same timezone. When flooring
1315 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to
1316 control the re-localization behavior.
1317
1318 Examples
1319 --------
1320 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min")
1321 >>> rng
1322 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00',
1323 '2018-01-01 12:01:00'],
1324 dtype='datetime64[us]', freq='min')
1325 >>> rng.floor("h")
1326 DatetimeIndex(['2018-01-01 11:00:00', '2018-01-01 12:00:00',
1327 '2018-01-01 12:00:00'],
1328 dtype='datetime64[us]', freq=None)
1329
1330 When rounding near a daylight savings time transition, use ``ambiguous`` or
1331 ``nonexistent`` to control how the timestamp should be re-localized.
1332
1333 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 03:30:00"], tz="Europe/Amsterdam")
1334 >>> rng_tz.floor("2h", ambiguous=False)
1335 DatetimeIndex(['2021-10-31 02:00:00+01:00'],
1336 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1337 >>> rng_tz.floor("2h", ambiguous=True)
1338 DatetimeIndex(['2021-10-31 02:00:00+02:00'],
1339 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1340 """
1341 arr = self._data.floor(freq, ambiguous, nonexistent)
1342 return type(self)._simple_new(arr, name=self.name)
1343
1344 def ceil(
1345 self,
1346 freq,
1347 ambiguous: TimeAmbiguous = "raise",
1348 nonexistent: TimeNonexistent = "raise",
1349 ) -> Self:
1350 """
1351 Perform ceil operation on the data to the specified `freq`.
1352
1353 This method rounds each datetime value in the Series/Index up to
1354 the specified frequency (i.e., towards positive infinity).
1355
1356 Parameters
1357 ----------
1358 freq : str or Offset
1359 The frequency level to ceil the index to. Must be a fixed
1360 frequency like 's' (second) not 'ME' (month end). See
1361 :ref:`frequency aliases <timeseries.offset_aliases>` for
1362 a list of possible `freq` values.
1363 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
1364 - 'infer' will attempt to infer fall dst-transition hours based on
1365 order. Requires that the timestamps are monotonically increasing.
1366 - bool-ndarray where True signifies a DST time, False designates
1367 a non-DST time (note that this flag is only applicable for
1368 ambiguous times)
1369 - 'NaT' will return NaT where there are ambiguous times
1370 - 'raise' will raise a ValueError if there are ambiguous
1371 times.
1372 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
1373 default 'raise'
1374 A nonexistent time does not exist in a particular timezone
1375 where clocks moved forward due to DST.
1376
1377 - 'shift_forward' will shift the nonexistent time forward to the
1378 closest existing time
1379 - 'shift_backward' will shift the nonexistent time backward to the
1380 closest existing time
1381 - 'NaT' will return NaT where there are nonexistent times
1382 - timedelta objects will shift nonexistent times by the timedelta
1383 - 'raise' will raise a ValueError if there are
1384 nonexistent times.
1385
1386 Returns
1387 -------
1388 DatetimeIndex
1389 Index with each value ceiled to the specified `freq`.
1390
1391 Raises
1392 ------
1393 ValueError if the `freq` cannot be converted.
1394
1395 See Also
1396 --------
1397 DatetimeIndex.round : Perform round operation on the data to the
1398 specified `freq`.
1399 DatetimeIndex.floor : Perform floor operation on the data to the
1400 specified `freq`.
1401 DatetimeIndex.snap : Snap time stamps to nearest occurring frequency.
1402
1403 Notes
1404 -----
1405 If the timestamps have a timezone, ceiling will take place relative to the
1406 local ("wall") time and re-localized to the same timezone. When ceiling
1407 near daylight savings time, use ``nonexistent`` and ``ambiguous`` to
1408 control the re-localization behavior.
1409
1410 Examples
1411 --------
1412 >>> rng = pd.date_range("1/1/2018 11:59:00", periods=3, freq="min")
1413 >>> rng
1414 DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00',
1415 '2018-01-01 12:01:00'],
1416 dtype='datetime64[us]', freq='min')
1417 >>> rng.ceil("h")
1418 DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00',
1419 '2018-01-01 13:00:00'],
1420 dtype='datetime64[us]', freq=None)
1421
1422 When rounding near a daylight savings time transition, use ``ambiguous`` or
1423 ``nonexistent`` to control how the timestamp should be re-localized.
1424
1425 >>> rng_tz = pd.DatetimeIndex(["2021-10-31 01:30:00"], tz="Europe/Amsterdam")
1426 >>> rng_tz.ceil("h", ambiguous=False)
1427 DatetimeIndex(['2021-10-31 02:00:00+01:00'],
1428 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1429 >>> rng_tz.ceil("h", ambiguous=True)
1430 DatetimeIndex(['2021-10-31 02:00:00+02:00'],
1431 dtype='datetime64[us, Europe/Amsterdam]', freq=None)
1432 """
1433 arr = self._data.ceil(freq, ambiguous, nonexistent)
1434 return type(self)._simple_new(arr, name=self.name)
1435
1436 def normalize(self) -> Self:
1437 """
1438 Convert times to midnight.
1439
1440 The time component of the date-time is converted to midnight i.e.
1441 00:00:00. This is useful in cases, when the time does not matter.
1442 Length is unaltered. The timezones are unaffected.
1443
1444 This method is available on Series with datetime values under
1445 the ``.dt`` accessor, and directly on Datetime Array/Index.
1446
1447 Returns
1448 -------
1449 DatetimeArray, DatetimeIndex or Series
1450 The same type as the original data. Series will have the same
1451 name and index. DatetimeIndex will have the same name.
1452
1453 See Also
1454 --------
1455 floor : Floor the datetimes to the specified freq.
1456 ceil : Ceil the datetimes to the specified freq.
1457 round : Round the datetimes to the specified freq.
1458
1459 Examples
1460 --------
1461 >>> idx = pd.date_range(
1462 ... start="2014-08-01 10:00", freq="h", periods=3, tz="Asia/Calcutta"
1463 ... )
1464 >>> idx
1465 DatetimeIndex(['2014-08-01 10:00:00+05:30',
1466 '2014-08-01 11:00:00+05:30',
1467 '2014-08-01 12:00:00+05:30'],
1468 dtype='datetime64[us, Asia/Calcutta]', freq='h')
1469 >>> idx.normalize()
1470 DatetimeIndex(['2014-08-01 00:00:00+05:30',
1471 '2014-08-01 00:00:00+05:30',
1472 '2014-08-01 00:00:00+05:30'],
1473 dtype='datetime64[us, Asia/Calcutta]', freq=None)
1474 """
1475 data = self._data
1476 # Inferring frequency from naive normalization is significantly
1477 # cheaper than tz-aware.
1478 naive = data._normalize_naive()
1479 freq = to_offset(naive._inferred_freq_str)
1480 arr = naive.tz_localize(data.tz) if data.tz is not None else naive
1481 result = type(self)._simple_new(arr, name=self.name)
1482 result._freq = freq
1483 return result
1484
1485 def tz_convert(self, tz) -> Self:
1486 """
1487 Convert tz-aware Datetime Array/Index from one time zone to another.
1488
1489 This method converts each timestamp in the index to the target time
1490 zone while preserving the underlying UTC time. The index must already
1491 be timezone-aware.
1492
1493 Parameters
1494 ----------
1495 tz : str, zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
1496 Time zone for time. Corresponding timestamps would be converted
1497 to this time zone of the Datetime Array/Index. A `tz` of None will
1498 convert to UTC and remove the timezone information.
1499
1500 Returns
1501 -------
1502 Array or Index
1503 Datetime Array/Index with target `tz`.
1504
1505 Raises
1506 ------
1507 TypeError
1508 If Datetime Array/Index is tz-naive.
1509
1510 See Also
1511 --------
1512 DatetimeIndex.tz : A timezone that has a variable offset from UTC.
1513 DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
1514 given time zone, or remove timezone from a tz-aware DatetimeIndex.
1515
1516 Examples
1517 --------
1518 With the `tz` parameter, we can change the DatetimeIndex
1519 to other time zones:
1520
1521 >>> dti = pd.date_range(
1522 ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
1523 ... )
1524
1525 >>> dti
1526 DatetimeIndex(['2014-08-01 09:00:00+02:00',
1527 '2014-08-01 10:00:00+02:00',
1528 '2014-08-01 11:00:00+02:00'],
1529 dtype='datetime64[us, Europe/Berlin]', freq='h')
1530
1531 >>> dti.tz_convert("US/Central")
1532 DatetimeIndex(['2014-08-01 02:00:00-05:00',
1533 '2014-08-01 03:00:00-05:00',
1534 '2014-08-01 04:00:00-05:00'],
1535 dtype='datetime64[us, US/Central]', freq='h')
1536
1537 With the ``tz=None``, we can remove the timezone (after converting
1538 to UTC if necessary):
1539
1540 >>> dti = pd.date_range(
1541 ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
1542 ... )
1543
1544 >>> dti
1545 DatetimeIndex(['2014-08-01 09:00:00+02:00',
1546 '2014-08-01 10:00:00+02:00',
1547 '2014-08-01 11:00:00+02:00'],
1548 dtype='datetime64[us, Europe/Berlin]', freq='h')
1549
1550 >>> dti.tz_convert(None)
1551 DatetimeIndex(['2014-08-01 07:00:00',
1552 '2014-08-01 08:00:00',
1553 '2014-08-01 09:00:00'],
1554 dtype='datetime64[us]', freq='h')
1555 """ # noqa: E501
1556 arr = self._data.tz_convert(tz)
1557 result = type(self)._simple_new(arr, name=self.name, refs=self._references)
1558 if isinstance(self.freq, Tick):
1559 result._freq = self.freq
1560 return result
1561
1562 def tz_localize(
1563 self,
1564 tz,
1565 ambiguous: TimeAmbiguous = "raise",
1566 nonexistent: TimeNonexistent = "raise",
1567 ) -> Self:
1568 """
1569 Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.
1570
1571 This method takes a time zone (tz) naive Datetime Array/Index object
1572 and makes this time zone aware. It does not move the time to another
1573 time zone.
1574
1575 This method can also be used to do the inverse -- to create a time
1576 zone unaware object from an aware object. To that end, pass `tz=None`.
1577
1578 Parameters
1579 ----------
1580 tz : str, zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
1581 Time zone to attach to the tz-naive timestamps; the wall time is
1582 preserved. Passing ``None`` detaches the time zone from a tz-aware
1583 DatetimeIndex, returning a tz-naive DatetimeIndex with the same
1584 wall time.
1585 ambiguous : 'infer', 'NaT', bool array, default 'raise'
1586 When clocks moved backward due to DST, ambiguous times may arise.
1587 For example in Central European Time (UTC+01), when going from
1588 03:00 DST to 02:00 non-DST, 02:30:00 local time occurs both at
1589 00:30:00 UTC and at 01:30:00 UTC. In such a situation, the
1590 `ambiguous` parameter dictates how ambiguous times should be
1591 handled.
1592
1593 - 'infer' will attempt to infer fall dst-transition hours based on
1594 order. Requires that the timestamps are monotonically increasing.
1595 - bool-ndarray where True signifies a DST time, False signifies a
1596 non-DST time (note that this flag is only applicable for
1597 ambiguous times)
1598 - 'NaT' will return NaT where there are ambiguous times
1599 - 'raise' will raise a ValueError if there are ambiguous
1600 times.
1601
1602 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
1603 default 'raise'
1604 A nonexistent time does not exist in a particular timezone
1605 where clocks moved forward due to DST.
1606
1607 - 'shift_forward' will shift the nonexistent time forward to the
1608 closest existing time
1609 - 'shift_backward' will shift the nonexistent time backward to the
1610 closest existing time
1611 - 'NaT' will return NaT where there are nonexistent times
1612 - timedelta objects will shift nonexistent times by the timedelta
1613 - 'raise' will raise a ValueError if there are
1614 nonexistent times.
1615
1616 Returns
1617 -------
1618 Same type as self
1619 Array/Index converted to the specified time zone.
1620
1621 Raises
1622 ------
1623 TypeError
1624 If the Datetime Array/Index is tz-aware and tz is not None.
1625
1626 See Also
1627 --------
1628 DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
1629 one time zone to another.
1630
1631 Examples
1632 --------
1633 >>> tz_naive = pd.date_range('2018-03-01 09:00', periods=3)
1634 >>> tz_naive
1635 DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
1636 '2018-03-03 09:00:00'],
1637 dtype='datetime64[us]', freq='D')
1638
1639 Localize DatetimeIndex in US/Eastern time zone:
1640
1641 >>> tz_aware = tz_naive.tz_localize(tz='US/Eastern')
1642 >>> tz_aware
1643 DatetimeIndex(['2018-03-01 09:00:00-05:00', '2018-03-02 09:00:00-05:00',
1644 '2018-03-03 09:00:00-05:00'],
1645 dtype='datetime64[us, US/Eastern]', freq='D')
1646
1647 With ``tz=None`` we can remove the time zone information while
1648 preserving the wall time (no conversion to UTC):
1649
1650 >>> tz_aware.tz_localize(None)
1651 DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
1652 '2018-03-03 09:00:00'],
1653 dtype='datetime64[us]', freq='D')
1654
1655 Be careful with DST changes. When there is sequential data, pandas can
1656 infer the DST time:
1657
1658 >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:30:00',
1659 ... '2018-10-28 02:00:00',
1660 ... '2018-10-28 02:30:00',
1661 ... '2018-10-28 02:00:00',
1662 ... '2018-10-28 02:30:00',
1663 ... '2018-10-28 03:00:00',
1664 ... '2018-10-28 03:30:00']))
1665 >>> s.dt.tz_localize('CET', ambiguous='infer')
1666 0 2018-10-28 01:30:00+02:00
1667 1 2018-10-28 02:00:00+02:00
1668 2 2018-10-28 02:30:00+02:00
1669 3 2018-10-28 02:00:00+01:00
1670 4 2018-10-28 02:30:00+01:00
1671 5 2018-10-28 03:00:00+01:00
1672 6 2018-10-28 03:30:00+01:00
1673 dtype: datetime64[us, CET]
1674
1675 In some cases, inferring the DST is impossible. In such cases, you can
1676 pass an ndarray to the ambiguous parameter to set the DST explicitly
1677
1678 >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:20:00',
1679 ... '2018-10-28 02:36:00',
1680 ... '2018-10-28 03:46:00']))
1681 >>> s.dt.tz_localize('CET', ambiguous=np.array([True, True, False]))
1682 0 2018-10-28 01:20:00+02:00
1683 1 2018-10-28 02:36:00+02:00
1684 2 2018-10-28 03:46:00+01:00
1685 dtype: datetime64[us, CET]
1686
1687 If the DST transition causes nonexistent times, you can shift these
1688 dates forward or backwards with a timedelta object or `'shift_forward'`
1689 or `'shift_backward'`.
1690
1691 >>> s = pd.to_datetime(pd.Series(['2015-03-29 02:30:00',
1692 ... '2015-03-29 03:30:00'], dtype="M8[ns]"))
1693 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_forward')
1694 0 2015-03-29 03:00:00+02:00
1695 1 2015-03-29 03:30:00+02:00
1696 dtype: datetime64[ns, Europe/Warsaw]
1697
1698 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_backward')
1699 0 2015-03-29 01:59:59.999999999+01:00
1700 1 2015-03-29 03:30:00.000000000+02:00
1701 dtype: datetime64[ns, Europe/Warsaw]
1702
1703 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent=pd.Timedelta('1h'))
1704 0 2015-03-29 03:30:00+02:00
1705 1 2015-03-29 03:30:00+02:00
1706 dtype: datetime64[ns, Europe/Warsaw]
1707 """ # noqa: E501
1708 freq = self._freq
1709 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
1710 result = type(self)._simple_new(arr, name=self.name)
1711 if timezones.is_utc(arr.tz) or (len(arr) == 1 and arr[0] is not NaT):
1712 # we can preserve freq
1713 # TODO: Also for fixed-offsets
1714 result._freq = freq
1715 elif arr.tz is None and self._data.tz is None:
1716 # no-op
1717 result._freq = freq
1718 elif (
1719 freq is not None
1720 and not isinstance(freq, Tick)
1721 and nonexistent in ("raise", "NaT")
1722 and not arr._hasna
1723 ):
1724 # GH#36575 pandas treats non-Tick offsets as wall-time: date_range
1725 # generates them in wall time and localizes after, and
1726 # _validate_frequency validates them against wall times (GH#55499).
1727 # tz_localize leaves wall times unchanged, so freq survives unless
1728 # we shifted nonexistent times or introduced NaTs.
1729 result._freq = freq
1730 return result
1731
1732 def to_period(self, freq=None) -> PeriodIndex:
1733 """
1734 Cast to PeriodArray/PeriodIndex at a particular frequency.
1735
1736 Converts DatetimeArray/Index to PeriodArray/PeriodIndex.
1737
1738 Parameters
1739 ----------
1740 freq : str, optional
1741 One of pandas' :ref:`period aliases <timeseries.period_aliases>`.
1742 Will be inferred by default.
1743
1744 Returns
1745 -------
1746 PeriodArray/PeriodIndex
1747 Immutable ndarray holding ordinal values at a particular frequency.
1748
1749 Raises
1750 ------
1751 ValueError
1752 When converting a DatetimeArray/Index with non-regular values,
1753 so that a frequency cannot be inferred.
1754
1755 See Also
1756 --------
1757 PeriodIndex: Immutable ndarray holding ordinal values.
1758 DatetimeIndex.to_pydatetime: Return DatetimeIndex as object.
1759
1760 Examples
1761 --------
1762 >>> df = pd.DataFrame(
1763 ... {"y": [1, 2, 3]},
1764 ... index=pd.to_datetime(
1765 ... [
1766 ... "2000-03-31 00:00:00",
1767 ... "2000-05-31 00:00:00",
1768 ... "2000-08-31 00:00:00",
1769 ... ]
1770 ... ),
1771 ... )
1772 >>> df.index.to_period("M")
1773 PeriodIndex(['2000-03', '2000-05', '2000-08'],
1774 dtype='period[M]')
1775
1776 Infer the daily frequency
1777
1778 >>> idx = pd.date_range("2017-01-01", periods=2)
1779 >>> idx.to_period()
1780 PeriodIndex(['2017-01-01', '2017-01-02'],
1781 dtype='period[D]')
1782 """
1783 from pandas.core.dtypes.dtypes import PeriodDtype
1784
1785 from pandas.core.indexes.api import PeriodIndex
1786
1787 from pandas.tseries.frequencies import get_period_alias
1788
1789 if freq is None:
1790 dt_freq = self.freq
1791 freq = self.freqstr
1792 if dt_freq is not None and hasattr(dt_freq, "_period_dtype_code"):
1793 freq = PeriodDtype(dt_freq)._freqstr
1794
1795 if freq is None:
1796 freq = self._inferred_freq_str
1797
1798 if freq is not None:
1799 res = get_period_alias(freq)
1800 if res is not None:
1801 freq = res
1802 arr = self._data.to_period(freq)
1803 return PeriodIndex._simple_new(arr, name=self.name)
1804
1805 def to_julian_date(self) -> Index:
1806 """
1807 Convert Timestamp to a Julian Date.
1808
1809 This method returns the number of days as a float since noon January 1, 4713 BC.
1810
1811 https://en.wikipedia.org/wiki/Julian_day
1812
1813 Returns
1814 -------
1815 ndarray or Index
1816 Float values that represent each date in Julian Calendar.
1817
1818 See Also
1819 --------
1820 Timestamp.to_julian_date : Equivalent method on ``Timestamp`` objects.
1821
1822 Examples
1823 --------
1824 >>> idx = pd.DatetimeIndex(["2028-08-12 00:54", "2028-08-12 02:06"])
1825 >>> idx.to_julian_date()
1826 Index([2461995.5375, 2461995.5875], dtype='float64')
1827 """
1828 arr = self._data.to_julian_date()
1829 return Index._simple_new(arr, name=self.name)
1830
1831 def isocalendar(self) -> DataFrame:
1832 """
1833 Calculate year, week, and day according to the ISO 8601 standard.
1834 Returns
1835 -------
1836 DataFrame
1837 With columns year, week and day.
1838 See Also
1839 --------
1840 Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
1841 week number, and weekday for the given Timestamp object.
1842 datetime.date.isocalendar : Return a named tuple object with
1843 three components: year, week and weekday.
1844
1845 Examples
1846 --------
1847 >>> idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
1848 >>> idx.isocalendar()
1849 year week day
1850 2019-12-29 2019 52 7
1851 2019-12-30 2020 1 1
1852 2019-12-31 2020 1 2
1853 2020-01-01 2020 1 3
1854 >>> idx.isocalendar().week
1855 2019-12-29 52
1856 2019-12-30 1
1857 2019-12-31 1
1858 2020-01-01 1
1859 Freq: D, Name: week, dtype: UInt32
1860 """
1861 df = self._data.isocalendar()
1862 return df.set_index(self)
1863
1864 # --------------------------------------------------------------------
1865 # Constructors
1866
1867 def __new__(
1868 cls,
1869 data=None,
1870 freq: Frequency | lib.NoDefault = lib.no_default,
1871 tz=lib.no_default,
1872 ambiguous: TimeAmbiguous | lib.NoDefault = lib.no_default,
1873 dayfirst: bool | lib.NoDefault = lib.no_default,
1874 yearfirst: bool | lib.NoDefault = lib.no_default,
1875 dtype: Dtype | None = None,
1876 copy: bool | None = None,
1877 name: Hashable | None = None,
1878 ) -> Self:
1879 if is_scalar(data):
1880 cls._raise_scalar_data_error(data)
1881
1882 if dayfirst is lib.no_default:
1883 dayfirst = False
1884 else:
1885 warnings.warn(
1886 f"The 'dayfirst' keyword in {cls.__name__} is deprecated "
1887 "and will be removed in a future version. "
1888 "Use pd.to_datetime instead.",
1889 Pandas4Warning,
1890 stacklevel=find_stack_level(),
1891 )
1892 if yearfirst is lib.no_default:
1893 yearfirst = False
1894 else:
1895 warnings.warn(
1896 f"The 'yearfirst' keyword in {cls.__name__} is deprecated "
1897 "and will be removed in a future version. "
1898 "Use pd.to_datetime instead.",
1899 Pandas4Warning,
1900 stacklevel=find_stack_level(),
1901 )
1902 if ambiguous is lib.no_default:
1903 ambiguous = "raise"
1904 else:
1905 warnings.warn(
1906 f"The 'ambiguous' keyword in {cls.__name__} is deprecated "
1907 "and will be removed in a future version. "
1908 f"Use {cls.__name__}.tz_localize instead.",
1909 Pandas4Warning,
1910 stacklevel=find_stack_level(),
1911 )
1912
1913 # - Cases checked above all return/raise before reaching here - #
1914
1915 name = maybe_extract_name(name, data, cls)
1916
1917 # GH#63388
1918 data, copy = cls._maybe_copy_array_input(data, copy, dtype)
1919
1920 if (
1921 isinstance(data, DatetimeArray)
1922 and freq is lib.no_default
1923 and tz is lib.no_default
1924 and dtype is None
1925 ):
1926 # fastpath, similar logic in TimedeltaIndex.__new__;
1927 # Note in this particular case we retain non-nano.
1928 if copy:
1929 data = data.copy()
1930 return cls._simple_new(data, name=name)
1931
1932 inferred_freq = data.freq if isinstance(data, DatetimeIndex) else None
1933
1934 dtarr = DatetimeArray._from_sequence_not_strict(
1935 data,
1936 dtype=dtype,
1937 copy=copy,
1938 tz=tz,
1939 dayfirst=dayfirst,
1940 yearfirst=yearfirst,
1941 ambiguous=ambiguous,
1942 )
1943
1944 refs = None
1945 if not copy and isinstance(data, (Index, ABCSeries)):
1946 refs = data._references
1947
1948 subarr = cls._simple_new(dtarr, name=name, refs=refs)
1949 subarr._pin_freq(freq, inferred_freq)
1950 return subarr
1951
1952 # --------------------------------------------------------------------
1953
1954 @cache_readonly
1955 def _is_dates_only(self) -> bool:
1956 """
1957 Return a boolean if we are only dates (and don't have a timezone)
1958
1959 Returns
1960 -------
1961 bool
1962 """
1963 if isinstance(self.freq, Tick):
1964 delta = Timedelta(self.freq)
1965
1966 if delta % dt.timedelta(days=1) != dt.timedelta(days=0):
1967 return False
1968
1969 return self._values._is_dates_only
1970
1971 def __reduce__(self):
1972 d = {"data": self._data, "name": self.name, "freq": self._freq}
1973 return _new_DatetimeIndex, (type(self), d), None
1974
1975 def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
1976 """
1977 Can we compare values of the given dtype to our own?
1978 """
1979 if isinstance(dtype, ArrowDtype):
1980 # GH#62277
1981 if dtype.kind != "M":
1982 return False
1983
1984 import pyarrow as pa
1985
1986 pa_dtype = dtype.pyarrow_dtype
1987 if not pa.types.is_timestamp(pa_dtype):
1988 # GH#62051 date types (date32, date64) are not
1989 # comparable with DatetimeIndex
1990 return False
1991 if (pa_dtype.tz is None) ^ (self.tz is None):
1992 return False
1993 return True
1994
1995 if self.tz is not None:
1996 # If we have tz, we can compare to tzaware
1997 return isinstance(dtype, DatetimeTZDtype)
1998 # if we dont have tz, we can only compare to tznaive
1999 return lib.is_np_dtype(dtype, "M")
2000
2001 # --------------------------------------------------------------------
2002 # Rendering Methods
2003
2004 def _formatter_func(self, val) -> str:
2005 # Note this is equivalent to the DatetimeIndexOpsMixin method but
2006 # uses the maybe-cached self._is_dates_only instead of re-computing it.
2007 return f"'{self._date_formatter(val)}'"
2008
2009 @cache_readonly
2010 def _date_formatter(self):
2011 from pandas.io.formats.format import get_format_datetime64
2012
2013 return get_format_datetime64(is_dates_only=self._is_dates_only)
2014
2015 # --------------------------------------------------------------------
2016 # Set Operation Methods
2017
2018 def _can_range_setop(self, other) -> bool:
2019 # GH 46702: If self or other have non-UTC tzs, DST transitions prevent
2020 # range representation due to no singular step
2021 if (
2022 self.tz is not None
2023 and not timezones.is_utc(self.tz)
2024 and not timezones.is_fixed_offset(self.tz)
2025 ):
2026 return False
2027 if (
2028 other.tz is not None
2029 and not timezones.is_utc(other.tz)
2030 and not timezones.is_fixed_offset(other.tz)
2031 ):
2032 return False
2033 return super()._can_range_setop(other)
2034
2035 # --------------------------------------------------------------------
2036
2037 def _get_time_micros(self) -> npt.NDArray[np.int64]:
2038 """
2039 Return the number of microseconds since midnight.
2040
2041 Returns
2042 -------
2043 ndarray[int64_t]
2044 """
2045 values = self._data._local_timestamps()
2046
2047 ppd = periods_per_day(self._data._creso)
2048
2049 frac = values % ppd
2050 if self.unit == "ns":
2051 micros = frac // 1000
2052 elif self.unit == "us":
2053 micros = frac
2054 elif self.unit == "ms":
2055 micros = frac * 1000
2056 elif self.unit == "s":
2057 micros = frac * 1_000_000
2058 else: # pragma: no cover
2059 raise NotImplementedError(self.unit)
2060
2061 micros[self._isnan] = -1
2062 return micros
2063
2064 def snap(self, freq: Frequency = "S") -> DatetimeIndex:
2065 """
2066 Snap time stamps to nearest occurring frequency.
2067
2068 Each timestamp in the index is adjusted to the nearest occurrence of
2069 the specified frequency, snapping backward or forward as appropriate.
2070
2071 Parameters
2072 ----------
2073 freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'S'
2074 Frequency strings can have multiples, e.g. '5h'. See
2075 :ref:`here <timeseries.offset_aliases>` for a list of
2076 frequency aliases.
2077
2078 Returns
2079 -------
2080 DatetimeIndex
2081 Time stamps to nearest occurring `freq`.
2082
2083 See Also
2084 --------
2085 DatetimeIndex.round : Perform round operation on the data to the
2086 specified `freq`.
2087 DatetimeIndex.floor : Perform floor operation on the data to the
2088 specified `freq`.
2089
2090 Examples
2091 --------
2092 >>> idx = pd.DatetimeIndex(
2093 ... ["2023-01-01", "2023-01-02", "2023-02-01", "2023-02-02"],
2094 ... dtype="M8[ns]",
2095 ... )
2096 >>> idx
2097 DatetimeIndex(['2023-01-01', '2023-01-02', '2023-02-01', '2023-02-02'],
2098 dtype='datetime64[ns]', freq=None)
2099 >>> idx.snap("MS")
2100 DatetimeIndex(['2023-01-01', '2023-01-01', '2023-02-01', '2023-02-01'],
2101 dtype='datetime64[ns]', freq=None)
2102 """
2103 # Superdumb, punting on any optimizing
2104 freq = to_offset(freq)
2105
2106 dta = self._data.copy()
2107
2108 for i, v in enumerate(self):
2109 s = v
2110 if not freq.is_on_offset(s):
2111 t0 = freq.rollback(s)
2112 t1 = freq.rollforward(s)
2113 if abs(s - t0) < abs(t1 - s):
2114 s = t0
2115 else:
2116 s = t1
2117 dta[i] = s
2118
2119 return DatetimeIndex._simple_new(dta, name=self.name)
2120
2121 # --------------------------------------------------------------------
2122 # Indexing Methods
2123
2124 def _parsed_string_to_bounds(
2125 self, reso: Resolution, parsed: dt.datetime
2126 ) -> tuple[Timestamp, Timestamp]:
2127 """
2128 Calculate datetime bounds for parsed time string and its resolution.
2129
2130 Parameters
2131 ----------
2132 reso : Resolution
2133 Resolution provided by parsed string.
2134 parsed : datetime
2135 Datetime from parsed string.
2136
2137 Returns
2138 -------
2139 lower, upper: pd.Timestamp
2140 """
2141 freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso.attr_abbrev)
2142 per = Period(parsed, freq=freq)
2143 start = per.start_time
2144 # Can't use end_time here bc that will subtract a microsecond
2145 # instead of a nanosecond
2146 end = (per + 1).start_time - np.timedelta64(1, "ns")
2147 start = start.as_unit(self.unit)
2148 end = end.as_unit(self.unit)
2149
2150 # GH 24076
2151 # If an incoming date string contained a UTC offset, need to localize
2152 # the parsed date to this offset first before aligning with the index's
2153 # timezone
2154 start = start.tz_localize(parsed.tzinfo)
2155 end = end.tz_localize(parsed.tzinfo)
2156
2157 if parsed.tzinfo is not None:
2158 if self.tz is None:
2159 raise ValueError(
2160 "The index must be timezone aware when indexing "
2161 "with a date string with a UTC offset"
2162 )
2163 # The flipped case with parsed.tz is None and self.tz is not None
2164 # is ruled out bc parsed and reso are produced by _parse_with_reso,
2165 # which localizes parsed.
2166 return start, end
2167
2168 def _parse_with_reso(self, label: str) -> tuple[Timestamp, Resolution]:
2169 parsed, reso = super()._parse_with_reso(label)
2170
2171 parsed = Timestamp(parsed)
2172
2173 if self.tz is not None and parsed.tzinfo is None:
2174 # we special-case timezone-naive strings and timezone-aware
2175 # DatetimeIndex
2176 # https://github.com/pandas-dev/pandas/pull/36148#issuecomment-687883081
2177 parsed = parsed.tz_localize(self.tz)
2178
2179 return parsed, reso
2180
2181 def _disallow_mismatched_indexing(self, key) -> None:
2182 """
2183 Check for mismatched-tzawareness indexing and re-raise as KeyError.
2184 """
2185 # we get here with isinstance(key, self._data._recognized_scalars)
2186 try:
2187 # GH#36148
2188 self._data._assert_tzawareness_compat(key)
2189 except TypeError as err:
2190 raise KeyError(key) from err
2191
2192 def get_loc(self, key):
2193 """
2194 Get integer location for requested label
2195
2196 Returns
2197 -------
2198 loc : int
2199 """
2200 self._check_indexing_error(key)
2201
2202 orig_key = key
2203 if is_valid_na_for_dtype(key, self.dtype):
2204 key = NaT
2205
2206 if isinstance(key, self._data._recognized_scalars):
2207 # needed to localize naive datetimes
2208 self._disallow_mismatched_indexing(key)
2209 key = Timestamp(key)
2210
2211 elif isinstance(key, str):
2212 try:
2213 parsed, reso = self._parse_with_reso(key)
2214 except ValueError as err:
2215 raise KeyError(key) from err
2216 self._disallow_mismatched_indexing(parsed)
2217
2218 if self._can_partial_date_slice(reso):
2219 try:
2220 return self._partial_date_slice(reso, parsed)
2221 except KeyError as err:
2222 raise KeyError(key) from err
2223
2224 key = parsed
2225
2226 elif isinstance(key, dt.timedelta):
2227 # GH#20464
2228 raise TypeError(
2229 f"Cannot index {type(self).__name__} with {type(key).__name__}"
2230 )
2231
2232 elif isinstance(key, dt.time):
2233 return self.indexer_at_time(key)
2234
2235 else:
2236 # unrecognized type
2237 raise KeyError(key)
2238
2239 try:
2240 return Index.get_loc(self, key)
2241 except KeyError as err:
2242 raise KeyError(orig_key) from err
2243
2244 def _maybe_cast_slice_bound(self, label, side: str):
2245 """
2246 This function should be overloaded in subclasses that allow non-trivial
2247 casting on label-slice bounds, e.g. datetime-like indices allowing
2248 strings containing formatted datetimes.
2249
2250 Parameters
2251 ----------
2252 label : object
2253 side : {'left', 'right'}
2254
2255 Returns
2256 -------
2257 label : object
2258
2259 Notes
2260 -----
2261 Value of `side` parameter should be validated in caller.
2262 """
2263 # GH#42855 handle date here instead of get_slice_bound
2264 if isinstance(label, dt.date) and not isinstance(label, dt.datetime):
2265 # Pandas supports slicing with dates, treated as datetimes at midnight.
2266 # https://github.com/pandas-dev/pandas/issues/31501
2267 label = Timestamp(label).to_pydatetime()
2268 warnings.warn(
2269 # GH#35830 deprecate last remaining inconsistent date treatment
2270 "Slicing with a datetime.date object is deprecated. "
2271 "Explicitly cast to Timestamp instead.",
2272 Pandas4Warning,
2273 stacklevel=find_stack_level(),
2274 )
2275
2276 label = super()._maybe_cast_slice_bound(label, side)
2277 self._data._assert_tzawareness_compat(label)
2278 return Timestamp(label)
2279
2280 def slice_indexer(self, start=None, end=None, step=None):
2281 """
2282 Return indexer for specified label slice.
2283 Index.slice_indexer, customized to handle time slicing.
2284
2285 In addition to functionality provided by Index.slice_indexer, does the
2286 following:
2287
2288 - if both `start` and `end` are instances of `datetime.time`, it
2289 invokes `indexer_between_time`
2290 - if `start` and `end` are both either string or None perform
2291 value-based selection in non-monotonic cases.
2292
2293 """
2294 # For historical reasons DatetimeIndex supports slices between two
2295 # instances of datetime.time as if it were applying a slice mask to
2296 # an array of (self.hour, self.minute, self.seconds, self.microsecond).
2297 if isinstance(start, dt.time) and isinstance(end, dt.time):
2298 if step is not None and step != 1:
2299 raise ValueError("Must have step size of 1 with time slices")
2300 return self.indexer_between_time(start, end)
2301
2302 if isinstance(start, dt.time) or isinstance(end, dt.time):
2303 raise KeyError("Cannot mix time and non-time slice keys")
2304
2305 def check_str_or_none(point) -> bool:
2306 return point is not None and not isinstance(point, str)
2307
2308 # GH#33146 if start and end are combinations of str and None and Index is not
2309 # monotonic, we can not use Index.slice_indexer because it does not honor the
2310 # actual elements, is only searching for start and end
2311 if (
2312 check_str_or_none(start)
2313 or check_str_or_none(end)
2314 or self.is_monotonic_increasing
2315 ):
2316 return Index.slice_indexer(self, start, end, step)
2317
2318 mask = np.array(True)
2319 in_index = True
2320 if start is not None:
2321 start_casted = self._maybe_cast_slice_bound(start, "left")
2322 mask = start_casted <= self
2323 in_index &= (start_casted == self).any()
2324
2325 if end is not None:
2326 end_casted = self._maybe_cast_slice_bound(end, "right")
2327 mask = (self <= end_casted) & mask
2328 in_index &= (end_casted == self).any()
2329
2330 if not in_index:
2331 raise KeyError(
2332 "Value based partial slicing on non-monotonic DatetimeIndexes "
2333 "with non-existing keys is not allowed.",
2334 )
2335 indexer = mask.nonzero()[0][::step]
2336 if len(indexer) == len(self):
2337 return slice(None)
2338 else:
2339 return indexer
2340
2341 # --------------------------------------------------------------------
2342
2343 @property
2344 def inferred_type(self) -> str:
2345 # b/c datetime is represented as microseconds since the epoch, make
2346 # sure we can't have ambiguous indexing
2347 return "datetime64"
2348
2349 def indexer_at_time(self, time, asof: bool = False) -> npt.NDArray[np.intp]:
2350 """
2351 Return index locations of values at particular time of day.
2352
2353 This method returns the integer indices of the DatetimeIndex entries
2354 that match the specified time of day, regardless of the date.
2355
2356 Parameters
2357 ----------
2358 time : datetime.time or str
2359 Time passed in either as object (datetime.time) or as string in
2360 appropriate format ("%H:%M", "%H%M", "%I:%M%p", "%I%M%p",
2361 "%H:%M:%S", "%H%M%S", "%I:%M:%S%p", "%I%M%S%p").
2362 asof : bool, default False
2363 This parameter is currently not supported.
2364
2365 Returns
2366 -------
2367 np.ndarray[np.intp]
2368 Index locations of values at given `time` of day.
2369
2370 See Also
2371 --------
2372 indexer_between_time : Get index locations of values between particular
2373 times of day.
2374 DataFrame.at_time : Select values at particular time of day.
2375
2376 Examples
2377 --------
2378 >>> idx = pd.DatetimeIndex(
2379 ... ["1/1/2020 10:00", "2/1/2020 11:00", "3/1/2020 10:00"]
2380 ... )
2381 >>> idx.indexer_at_time("10:00")
2382 array([0, 2])
2383 """
2384 if asof:
2385 raise NotImplementedError("'asof' argument is not supported")
2386
2387 if isinstance(time, str):
2388 from dateutil.parser import parse
2389
2390 orig = time
2391 try:
2392 alt = to_time(time)
2393 except ValueError:
2394 warnings.warn(
2395 # GH#50839
2396 f"The string '{orig}' cannot be parsed using pd.core.tools.to_time "
2397 f"and in a future version will raise. "
2398 "Use an unambiguous time string format or explicitly cast to "
2399 "`datetime.time` before calling.",
2400 Pandas4Warning,
2401 stacklevel=find_stack_level(),
2402 )
2403 time = parse(time).time()
2404 else:
2405 try:
2406 time = parse(time).time()
2407 except ValueError:
2408 # e.g. '23550' raises dateutil.parser._parser.ParserError
2409 time = alt
2410 if alt != time:
2411 warnings.warn(
2412 # GH#50839
2413 f"The string '{orig}' is currently parsed as {time} "
2414 f"but in a future version will be parsed as {alt}, consistent"
2415 "with `between_time` behavior. To avoid this warning, "
2416 "use an unambiguous string format or explicitly cast to "
2417 "`datetime.time` before calling.",
2418 Pandas4Warning,
2419 stacklevel=find_stack_level(),
2420 )
2421
2422 if time.tzinfo:
2423 if self.tz is None:
2424 raise ValueError("Index must be timezone aware.")
2425 time_micros = self.tz_convert(time.tzinfo)._get_time_micros()
2426 else:
2427 time_micros = self._get_time_micros()
2428 micros = _time_to_micros(time)
2429 return (time_micros == micros).nonzero()[0]
2430
2431 def indexer_between_time(
2432 self, start_time, end_time, include_start: bool = True, include_end: bool = True
2433 ) -> npt.NDArray[np.intp]:
2434 """
2435 Return index locations of values between particular times of day.
2436
2437 This method returns the integer indices of the DatetimeIndex entries
2438 whose time-of-day components fall within the specified range.
2439
2440 Parameters
2441 ----------
2442 start_time, end_time : datetime.time, str
2443 Time passed either as object (datetime.time) or as string in
2444 appropriate format ("%H:%M", "%H%M", "%I:%M%p", "%I%M%p",
2445 "%H:%M:%S", "%H%M%S", "%I:%M:%S%p","%I%M%S%p").
2446 include_start : bool, default True
2447 Include boundaries; whether to set start bound as closed or open.
2448 include_end : bool, default True
2449 Include boundaries; whether to set end bound as closed or open.
2450
2451 Returns
2452 -------
2453 np.ndarray[np.intp]
2454 Index locations of values between particular times of day.
2455
2456 See Also
2457 --------
2458 indexer_at_time : Get index locations of values at particular time of day.
2459 DataFrame.between_time : Select values between particular times of day.
2460
2461 Examples
2462 --------
2463 >>> idx = pd.date_range("2023-01-01", periods=4, freq="h")
2464 >>> idx
2465 DatetimeIndex(['2023-01-01 00:00:00', '2023-01-01 01:00:00',
2466 '2023-01-01 02:00:00', '2023-01-01 03:00:00'],
2467 dtype='datetime64[us]', freq='h')
2468 >>> idx.indexer_between_time("00:00", "2:00", include_end=False)
2469 array([0, 1])
2470 """
2471 start_time = to_time(start_time)
2472 end_time = to_time(end_time)
2473 time_micros = self._get_time_micros()
2474 start_micros = _time_to_micros(start_time)
2475 end_micros = _time_to_micros(end_time)
2476
2477 if include_start and include_end:
2478 lop = rop = operator.le
2479 elif include_start:
2480 lop = operator.le
2481 rop = operator.lt
2482 elif include_end:
2483 lop = operator.lt
2484 rop = operator.le
2485 else:
2486 lop = rop = operator.lt
2487
2488 if start_time <= end_time:
2489 join_op = operator.and_
2490 else:
2491 join_op = operator.or_
2492
2493 mask = join_op(lop(start_micros, time_micros), rop(time_micros, end_micros))
2494
2495 return mask.nonzero()[0]
2496
2497
2498@set_module("pandas")
2499def date_range(
2500 start=None,
2501 end=None,
2502 periods=None,
2503 freq=None,
2504 tz=None,
2505 normalize: bool = False,
2506 name: Hashable | None = None,
2507 inclusive: IntervalClosedType = "both",
2508 *,
2509 unit: TimeUnit | None = None,
2510 ambiguous: TimeAmbiguous = "raise",
2511 nonexistent: TimeNonexistent = "raise",
2512 **kwargs,
2513) -> DatetimeIndex:
2514 """
2515 Return a fixed frequency DatetimeIndex.
2516
2517 Returns the range of equally spaced time points (where the difference between any
2518 two adjacent points is specified by the given frequency) such that they fall in the
2519 range `[start, end]` , where the first one and the last one are, resp., the first
2520 and last time points in that range that fall on the boundary of ``freq`` (if given
2521 as a frequency string) or that are valid for ``freq`` (if given as a
2522 :class:`pandas.tseries.offsets.DateOffset`). If ``freq`` is positive, the points
2523 satisfy `start <[=] x <[=] end`, and if ``freq`` is negative, the points satisfy
2524 `end <[=] x <[=] start`. (If exactly one of ``start``, ``end``, or ``freq`` is *not*
2525 specified, this missing parameter can be computed given ``periods``, the number of
2526 timesteps in the range. See the note below.)
2527
2528 Parameters
2529 ----------
2530 start : str or datetime-like, optional
2531 Left bound for generating dates.
2532 end : str or datetime-like, optional
2533 Right bound for generating dates.
2534 periods : int, optional
2535 Number of periods to generate.
2536 freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'D'
2537 Frequency strings can have multiples, e.g. '5h'. See
2538 :ref:`here <timeseries.offset_aliases>` for a list of
2539 frequency aliases.
2540 tz : str or tzinfo, optional
2541 Time zone name for returning localized DatetimeIndex, for example
2542 'Asia/Hong_Kong'. By default, the resulting DatetimeIndex is
2543 timezone-naive unless timezone-aware datetime-likes are passed.
2544 normalize : bool, default False
2545 Normalize start/end dates to midnight before generating date range.
2546 name : Hashable, default None
2547 Name of the resulting DatetimeIndex.
2548 inclusive : {"both", "neither", "left", "right"}, default "both"
2549 Include boundaries; Whether to set each bound as closed or open.
2550 unit : {'s', 'ms', 'us', 'ns', None}, default None
2551 Specify the desired resolution of the result.
2552 If not specified, this is inferred from the 'start', 'end', and 'freq'
2553 using the same inference as :class:`Timestamp` taking the highest
2554 resolution of the three that are provided.
2555
2556 .. versionadded:: 2.0.0
2557 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
2558 When clocks moved backward due to DST, ambiguous times may arise.
2559 For example in Central European Time (UTC+01), when going from 03:00
2560 DST to 02:00 non-DST, 02:30:00 local time occurs both at 00:30:00 UTC
2561 and at 01:30:00 UTC. In such a situation, the `ambiguous` parameter
2562 dictates how ambiguous times should be handled.
2563
2564 - 'infer' will attempt to infer fall dst-transition hours based on
2565 order. Requires that the timestamps are monotonically increasing.
2566 - bool-ndarray where True signifies a DST time, False signifies a
2567 non-DST time (note that this flag is only applicable for ambiguous
2568 times)
2569 - 'NaT' will return NaT where there are ambiguous times
2570 - 'raise' will raise a ValueError if there are ambiguous times.
2571 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
2572 default 'raise'
2573 A nonexistent time does not exist in a particular timezone
2574 where clocks moved forward due to DST.
2575
2576 - 'shift_forward' will shift the nonexistent time forward to the
2577 closest existing time
2578 - 'shift_backward' will shift the nonexistent time backward to the
2579 closest existing time
2580 - 'NaT' will return NaT where there are nonexistent times
2581 - timedelta objects will shift nonexistent times by the timedelta
2582 - 'raise' will raise a ValueError if there are nonexistent times.
2583 **kwargs
2584 For compatibility. Has no effect on the result.
2585
2586 Returns
2587 -------
2588 DatetimeIndex
2589 A DatetimeIndex object of the generated dates.
2590
2591 See Also
2592 --------
2593 DatetimeIndex : An immutable container for datetimes.
2594 timedelta_range : Return a fixed frequency TimedeltaIndex.
2595 period_range : Return a fixed frequency PeriodIndex.
2596 interval_range : Return a fixed frequency IntervalIndex.
2597
2598 Notes
2599 -----
2600 Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
2601 a maximum of three can be specified at once. Of the three parameters
2602 ``start``, ``end``, and ``periods``, at least two must be specified.
2603 If ``freq`` is omitted, the resulting ``DatetimeIndex`` will have
2604 ``periods`` linearly spaced elements between ``start`` and ``end``
2605 (closed on both sides).
2606
2607 To learn more about the frequency strings, please see
2608 :ref:`this link<timeseries.offset_aliases>`.
2609
2610 Examples
2611 --------
2612 **Specifying the values**
2613
2614 The next four examples generate the same `DatetimeIndex`, but vary
2615 the combination of `start`, `end` and `periods`.
2616
2617 Specify `start` and `end`, with the default daily frequency.
2618
2619 >>> pd.date_range(start="1/1/2018", end="1/08/2018")
2620 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
2621 '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08'],
2622 dtype='datetime64[us]', freq='D')
2623
2624 Specify timezone-aware `start` and `end`, with the default daily frequency.
2625
2626 >>> pd.date_range(
2627 ... start=pd.to_datetime("1/1/2018").tz_localize("Europe/Berlin"),
2628 ... end=pd.to_datetime("1/08/2018").tz_localize("Europe/Berlin"),
2629 ... )
2630 DatetimeIndex(['2018-01-01 00:00:00+01:00', '2018-01-02 00:00:00+01:00',
2631 '2018-01-03 00:00:00+01:00', '2018-01-04 00:00:00+01:00',
2632 '2018-01-05 00:00:00+01:00', '2018-01-06 00:00:00+01:00',
2633 '2018-01-07 00:00:00+01:00', '2018-01-08 00:00:00+01:00'],
2634 dtype='datetime64[us, Europe/Berlin]', freq='D')
2635
2636 Specify `start` and `periods`, the number of periods (days).
2637
2638 >>> pd.date_range(start="1/1/2018", periods=8)
2639 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
2640 '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08'],
2641 dtype='datetime64[us]', freq='D')
2642
2643 Specify `end` and `periods`, the number of periods (days).
2644
2645 >>> pd.date_range(end="1/1/2018", periods=8)
2646 DatetimeIndex(['2017-12-25', '2017-12-26', '2017-12-27', '2017-12-28',
2647 '2017-12-29', '2017-12-30', '2017-12-31', '2018-01-01'],
2648 dtype='datetime64[us]', freq='D')
2649
2650 Specify `start`, `end`, and `periods`; the frequency is generated
2651 automatically (linearly spaced).
2652
2653 >>> pd.date_range(start="2018-04-24", end="2018-04-27", periods=3)
2654 DatetimeIndex(['2018-04-24 00:00:00', '2018-04-25 12:00:00',
2655 '2018-04-27 00:00:00'],
2656 dtype='datetime64[us]', freq=None)
2657
2658 **Other Parameters**
2659
2660 Changed the `freq` (frequency) to ``'ME'`` (month end frequency).
2661
2662 >>> pd.date_range(start="1/1/2018", periods=5, freq="ME")
2663 DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31', '2018-04-30',
2664 '2018-05-31'],
2665 dtype='datetime64[us]', freq='ME')
2666
2667 Multiples are allowed
2668
2669 >>> pd.date_range(start="1/1/2018", periods=5, freq="3ME")
2670 DatetimeIndex(['2018-01-31', '2018-04-30', '2018-07-31', '2018-10-31',
2671 '2019-01-31'],
2672 dtype='datetime64[us]', freq='3ME')
2673
2674 `freq` can also be specified as an Offset object.
2675
2676 >>> pd.date_range(start="1/1/2018", periods=5, freq=pd.offsets.MonthEnd(3))
2677 DatetimeIndex(['2018-01-31', '2018-04-30', '2018-07-31', '2018-10-31',
2678 '2019-01-31'],
2679 dtype='datetime64[us]', freq='3ME')
2680
2681 Use :class:`pandas.DateOffset` for calendar steps that are not anchored to
2682 the start or end of a period.
2683
2684 >>> pd.date_range(end="2017-03-12", periods=3, freq=pd.DateOffset(years=1))
2685 DatetimeIndex(['2015-03-12', '2016-03-12', '2017-03-12'],
2686 dtype='datetime64[us]', freq='<DateOffset: years=1>')
2687
2688 Specify `tz` to set the timezone.
2689
2690 >>> pd.date_range(start="1/1/2018", periods=5, tz="Asia/Tokyo")
2691 DatetimeIndex(['2018-01-01 00:00:00+09:00', '2018-01-02 00:00:00+09:00',
2692 '2018-01-03 00:00:00+09:00', '2018-01-04 00:00:00+09:00',
2693 '2018-01-05 00:00:00+09:00'],
2694 dtype='datetime64[us, Asia/Tokyo]', freq='D')
2695
2696 `inclusive` controls whether to include `start` and `end` that are on the
2697 boundary. The default, "both", includes boundary points on either end.
2698
2699 >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="both")
2700 DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03', '2017-01-04'],
2701 dtype='datetime64[us]', freq='D')
2702
2703 Use ``inclusive='left'`` to exclude `end` if it falls on the boundary.
2704
2705 >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="left")
2706 DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03'],
2707 dtype='datetime64[us]', freq='D')
2708
2709 Use ``inclusive='right'`` to exclude `start` if it falls on the boundary, and
2710 similarly ``inclusive='neither'`` will exclude both `start` and `end`.
2711
2712 >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="right")
2713 DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'],
2714 dtype='datetime64[us]', freq='D')
2715
2716 **Specify a unit**
2717
2718 >>> pd.date_range(start="2017-01-01", periods=10, freq="100YS", unit="s")
2719 DatetimeIndex(['2017-01-01', '2117-01-01', '2217-01-01', '2317-01-01',
2720 '2417-01-01', '2517-01-01', '2617-01-01', '2717-01-01',
2721 '2817-01-01', '2917-01-01'],
2722 dtype='datetime64[s]', freq='100YS-JAN')
2723 """
2724 if freq is None and com.any_none(periods, start, end):
2725 freq = "D"
2726 if freq is not None:
2727 freq = to_offset(freq)
2728
2729 if start is NaT or end is NaT:
2730 # This check needs to come before the `unit = start.unit` line below
2731 raise ValueError("Neither `start` nor `end` can be NaT")
2732
2733 if unit is None:
2734 # Infer the unit based on the inputs
2735
2736 if start is not None and end is not None:
2737 start = Timestamp(start)
2738 end = Timestamp(end)
2739 if abbrev_to_npy_unit(start.unit) > abbrev_to_npy_unit(end.unit):
2740 unit = start.unit
2741 else:
2742 unit = end.unit
2743 elif start is not None:
2744 start = Timestamp(start)
2745 unit = start.unit
2746 elif end is not None:
2747 end = Timestamp(end)
2748 unit = end.unit
2749 else:
2750 raise ValueError(
2751 "Of the four parameters: start, end, periods, "
2752 "and freq, exactly three must be specified"
2753 )
2754
2755 # Last we need to watch out for cases where the 'freq' implies a higher
2756 # unit than either start or end
2757 if freq is not None:
2758 creso = abbrev_to_npy_unit(unit)
2759 if isinstance(freq, Tick):
2760 if freq._creso > creso:
2761 unit = freq.base.freqstr # type: ignore[assignment]
2762 elif hasattr(freq, "offset") and freq.offset is not None:
2763 # e.g. BDay with an offset
2764 td = Timedelta(freq.offset)
2765 if abbrev_to_npy_unit(td.unit) > creso:
2766 unit = td.unit
2767 elif type(freq) is DateOffset:
2768 if getattr(freq, "nanoseconds", 0) != 0:
2769 # e.g. test_freq_dateoffset_with_relateivedelta_nanos
2770 unit = "ns"
2771 elif getattr(freq, "microseconds", 0) != 0 and unit != "ns":
2772 unit = "us"
2773 elif getattr(freq, "milliseconds", 0) != 0 and unit not in ["ns", "us"]:
2774 unit = "ms"
2775
2776 dtarr = DatetimeArray._generate_range(
2777 start=start,
2778 end=end,
2779 periods=periods,
2780 freq=freq,
2781 tz=tz,
2782 normalize=normalize,
2783 ambiguous=ambiguous,
2784 nonexistent=nonexistent,
2785 inclusive=inclusive,
2786 unit=unit,
2787 **kwargs,
2788 )
2789 result = DatetimeIndex._simple_new(dtarr, name=name)
2790 result._freq = freq
2791 return result
2792
2793
2794@set_module("pandas")
2795def bdate_range(
2796 start=None,
2797 end=None,
2798 periods: int | None = None,
2799 freq: Frequency | dt.timedelta = "B",
2800 tz=None,
2801 normalize: bool = True,
2802 name: Hashable | None = None,
2803 weekmask=None,
2804 holidays=None,
2805 inclusive: IntervalClosedType = "both",
2806 **kwargs,
2807) -> DatetimeIndex:
2808 """
2809 Return a fixed frequency DatetimeIndex with business day as the default.
2810
2811 This function generates a DatetimeIndex using business day frequency by
2812 default, skipping weekends. Custom business day calendars can be
2813 specified via ``weekmask`` and ``holidays``.
2814
2815 Parameters
2816 ----------
2817 start : str or datetime-like, default None
2818 Left bound for generating dates.
2819 end : str or datetime-like, default None
2820 Right bound for generating dates.
2821 periods : int, default None
2822 Number of periods to generate.
2823 freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'B'
2824 Frequency strings can have multiples, e.g. '5h'. The default is
2825 business daily ('B').
2826 tz : str or None
2827 Time zone name for returning localized DatetimeIndex, for example
2828 Asia/Beijing.
2829 normalize : bool, default False
2830 Normalize start/end dates to midnight before generating date range.
2831 name : Hashable, default None
2832 Name of the resulting DatetimeIndex.
2833 weekmask : str or None, default None
2834 Weekmask of valid business days, passed to ``numpy.busdaycalendar``,
2835 only used when custom frequency strings are passed. The default
2836 value None is equivalent to 'Mon Tue Wed Thu Fri'.
2837 holidays : list-like or None, default None
2838 Dates to exclude from the set of valid business days, passed to
2839 ``numpy.busdaycalendar``, only used when custom frequency strings
2840 are passed.
2841 inclusive : {"both", "neither", "left", "right"}, default "both"
2842 Include boundaries; Whether to set each bound as closed or open.
2843 **kwargs
2844 For compatibility. Has no effect on the result.
2845
2846 Returns
2847 -------
2848 DatetimeIndex
2849 Fixed frequency DatetimeIndex.
2850
2851 See Also
2852 --------
2853 date_range : Return a fixed frequency DatetimeIndex.
2854 period_range : Return a fixed frequency PeriodIndex.
2855 timedelta_range : Return a fixed frequency TimedeltaIndex.
2856
2857 Notes
2858 -----
2859 Of the four parameters: ``start``, ``end``, ``periods``, and ``freq``,
2860 exactly three must be specified. Specifying ``freq`` is a requirement
2861 for ``bdate_range``. Use ``date_range`` if specifying ``freq`` is not
2862 desired.
2863
2864 To learn more about the frequency strings, please see
2865 :ref:`this link<timeseries.offset_aliases>`.
2866
2867 Examples
2868 --------
2869 Note how the two weekend days are skipped in the result.
2870
2871 >>> pd.bdate_range(start="1/1/2018", end="1/08/2018")
2872 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
2873 '2018-01-05', '2018-01-08'],
2874 dtype='datetime64[us]', freq='B')
2875 """
2876 if freq is None:
2877 msg = "freq must be specified for bdate_range; use date_range instead"
2878 raise TypeError(msg)
2879
2880 if isinstance(freq, str) and freq.upper().startswith("C"):
2881 msg = f"invalid custom frequency string: {freq}"
2882 if freq == "CBH":
2883 # GH#62849
2884 raise ValueError(f"{msg}, did you mean cbh?")
2885 try:
2886 weekmask = weekmask or "Mon Tue Wed Thu Fri"
2887 freq = prefix_mapping[freq](holidays=holidays, weekmask=weekmask)
2888 except (KeyError, TypeError) as err:
2889 raise ValueError(msg) from err
2890 elif holidays or weekmask:
2891 msg = (
2892 "a custom frequency string is required when holidays or "
2893 f"weekmask are passed, got frequency {freq}"
2894 )
2895 raise ValueError(msg)
2896
2897 return date_range(
2898 start=start,
2899 end=end,
2900 periods=periods,
2901 freq=freq,
2902 tz=tz,
2903 normalize=normalize,
2904 name=name,
2905 inclusive=inclusive,
2906 **kwargs,
2907 )
2908
2909
2910def _time_to_micros(time_obj: dt.time) -> int:
2911 seconds = time_obj.hour * 60 * 60 + 60 * time_obj.minute + time_obj.second
2912 return 1_000_000 * seconds + time_obj.microsecond