1from __future__ import annotations
2
3from datetime import (
4 datetime,
5 timedelta,
6 tzinfo,
7)
8from typing import (
9 TYPE_CHECKING,
10 Self,
11 TypeVar,
12 cast,
13 overload,
14)
15import warnings
16
17import numpy as np
18
19from pandas._config import using_string_dtype
20from pandas._config.config import get_option
21
22from pandas._libs import (
23 lib,
24 tslib,
25)
26from pandas._libs.tslibs import (
27 BaseOffset,
28 NaT,
29 NaTType,
30 Resolution,
31 Timestamp,
32 astype_overflowsafe,
33 fields,
34 get_resolution,
35 get_supported_dtype,
36 get_unit_from_dtype,
37 ints_to_pydatetime,
38 is_date_array_normalized,
39 is_supported_dtype,
40 is_unitless,
41 normalize_i8_timestamps,
42 timezones,
43 to_offset,
44 tz_convert_from_utc,
45 tzconversion,
46)
47from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
48from pandas.errors import PerformanceWarning
49from pandas.util._decorators import set_module
50from pandas.util._exceptions import find_stack_level
51from pandas.util._validators import validate_inclusive
52
53from pandas.core.dtypes.common import (
54 DT64NS_DTYPE,
55 INT64_DTYPE,
56 is_bool_dtype,
57 is_float_dtype,
58 is_string_dtype,
59 pandas_dtype,
60)
61from pandas.core.dtypes.dtypes import (
62 DatetimeTZDtype,
63 ExtensionDtype,
64 PeriodDtype,
65)
66from pandas.core.dtypes.missing import isna
67
68from pandas.core.arrays import datetimelike as dtl
69from pandas.core.arrays._ranges import generate_regular_range
70import pandas.core.common as com
71
72from pandas.tseries.frequencies import get_period_alias
73from pandas.tseries.offsets import (
74 Day,
75 Tick,
76)
77
78if TYPE_CHECKING:
79 from collections.abc import (
80 Callable,
81 Generator,
82 Iterator,
83 )
84
85 from pandas._typing import (
86 ArrayLike,
87 DateTimeErrorChoices,
88 DtypeObj,
89 IntervalClosedType,
90 TimeAmbiguous,
91 TimeNonexistent,
92 TimeUnit,
93 npt,
94 )
95
96 from pandas import (
97 DataFrame,
98 Timedelta,
99 )
100 from pandas.core.arrays import PeriodArray
101
102 _TimestampNoneT1 = TypeVar("_TimestampNoneT1", Timestamp, None)
103 _TimestampNoneT2 = TypeVar("_TimestampNoneT2", Timestamp, None)
104
105
106_ITER_CHUNKSIZE = 10_000
107
108
109@overload
110def tz_to_dtype(tz: tzinfo, unit: TimeUnit = ...) -> DatetimeTZDtype: ...
111
112
113@overload
114def tz_to_dtype(tz: None, unit: TimeUnit = ...) -> np.dtype[np.datetime64]: ...
115
116
117def tz_to_dtype(
118 tz: tzinfo | None, unit: TimeUnit = "ns"
119) -> np.dtype[np.datetime64] | DatetimeTZDtype:
120 """
121 Return a datetime64[ns] dtype appropriate for the given timezone.
122
123 Parameters
124 ----------
125 tz : tzinfo or None
126 unit : str, default "ns"
127
128 Returns
129 -------
130 np.dtype or Datetime64TZDType
131 """
132 if tz is None:
133 return np.dtype(f"M8[{unit}]")
134 else:
135 return DatetimeTZDtype(tz=tz, unit=unit)
136
137
138def _field_accessor(name: str, field: str, docstring: str | None = None):
139 def f(self):
140 values = self._local_timestamps()
141
142 if field in self._bool_ops:
143 result: np.ndarray
144
145 if field.endswith(("start", "end")):
146 freq = self.freq
147 month_kw = 12
148 if freq:
149 kwds = freq.kwds
150 month_kw = kwds.get("startingMonth", kwds.get("month", month_kw))
151
152 if freq is not None:
153 freq_name = freq.name
154 else:
155 freq_name = None
156 result = fields.get_start_end_field(
157 values, field, freq_name, month_kw, reso=self._creso
158 )
159 else:
160 result = fields.get_date_field(values, field, reso=self._creso)
161
162 # these return a boolean by-definition
163 return result
164
165 result = fields.get_date_field(values, field, reso=self._creso)
166 result = self._maybe_mask_results(result, fill_value=None, convert="float64")
167
168 return result
169
170 f.__name__ = name
171 f.__doc__ = docstring
172 return property(f)
173
174
175@set_module("pandas.arrays")
176class DatetimeArray(dtl.TimelikeOps, dtl.DatelikeOps):
177 """
178 Pandas ExtensionArray for tz-naive or tz-aware datetime data.
179
180 .. warning::
181
182 DatetimeArray is currently experimental, and its API may change
183 without warning. In particular, :attr:`DatetimeArray.dtype` is
184 expected to change to always be an instance of an ``ExtensionDtype``
185 subclass.
186
187 Parameters
188 ----------
189 data : Series, Index, DatetimeArray, ndarray
190 The datetime data.
191
192 For DatetimeArray `values` (or a Series or Index boxing one),
193 `dtype` and `freq` will be extracted from `values`.
194
195 dtype : numpy.dtype or DatetimeTZDtype
196 Note that the only NumPy dtype allowed is 'datetime64[ns]'.
197 freq : str or Offset, optional
198 The frequency.
199 copy : bool, default False
200 Whether to copy the underlying array of values.
201
202 Attributes
203 ----------
204 None
205
206 Methods
207 -------
208 None
209
210 See Also
211 --------
212 DatetimeIndex : Immutable Index for datetime-like data.
213 Series : One-dimensional labeled array capable of holding datetime-like data.
214 Timestamp : Pandas replacement for python datetime.datetime object.
215 to_datetime : Convert argument to datetime.
216 period_range : Return a fixed frequency PeriodIndex.
217
218 Examples
219 --------
220 >>> pd.arrays.DatetimeArray._from_sequence(
221 ... pd.DatetimeIndex(["2023-01-01", "2023-01-02"], freq="D")
222 ... )
223 <DatetimeArray>
224 ['2023-01-01 00:00:00', '2023-01-02 00:00:00']
225 Length: 2, dtype: datetime64[us]
226 """
227
228 _typ = "datetimearray"
229 _recognized_scalars = (datetime, np.datetime64)
230 _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: (
231 lib.is_np_dtype(x, "M") or isinstance(x, DatetimeTZDtype)
232 )
233 _infer_matches = ("datetime", "datetime64", "date")
234
235 @property
236 def _internal_fill_value(self) -> np.datetime64:
237 return np.datetime64("NaT", self.unit)
238
239 @property
240 def _scalar_type(self) -> type[Timestamp]:
241 return Timestamp
242
243 # define my properties & methods for delegation
244 _bool_ops: list[str] = [
245 "is_month_start",
246 "is_month_end",
247 "is_quarter_start",
248 "is_quarter_end",
249 "is_year_start",
250 "is_year_end",
251 "is_leap_year",
252 ]
253 _field_ops: list[str] = [
254 "year",
255 "month",
256 "day",
257 "hour",
258 "minute",
259 "second",
260 "weekday",
261 "dayofweek",
262 "day_of_week",
263 "dayofyear",
264 "day_of_year",
265 "quarter",
266 "days_in_month",
267 "daysinmonth",
268 "microsecond",
269 "nanosecond",
270 ]
271 _other_ops: list[str] = ["date", "time", "timetz"]
272 _datetimelike_ops: list[str] = (
273 _field_ops + _bool_ops + _other_ops + ["unit", "freq", "tz"]
274 )
275 _datetimelike_methods: list[str] = [
276 "to_period",
277 "tz_localize",
278 "tz_convert",
279 "normalize",
280 "strftime",
281 "round",
282 "floor",
283 "ceil",
284 "month_name",
285 "day_name",
286 "as_unit",
287 ]
288
289 # ndim is inherited from ExtensionArray, must exist to ensure
290 # Timestamp.__richcmp__(DateTimeArray) operates pointwise
291
292 # ensure that operations with numpy arrays defer to our implementation
293 __array_priority__ = 1000
294
295 # -----------------------------------------------------------------
296 # Constructors
297
298 _dtype: np.dtype[np.datetime64] | DatetimeTZDtype
299 _freq: BaseOffset | None = None
300
301 @classmethod
302 def _validate_dtype(cls, values, dtype):
303 # used in TimeLikeOps.__init__
304 dtype = _validate_dt64_dtype(dtype)
305 _validate_dt64_dtype(values.dtype)
306 if isinstance(dtype, np.dtype):
307 if values.dtype != dtype:
308 raise ValueError("Values resolution does not match dtype.")
309 else:
310 vunit = np.datetime_data(values.dtype)[0]
311 if vunit != dtype.unit:
312 raise ValueError("Values resolution does not match dtype.")
313 return dtype
314
315 # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked"
316 @classmethod
317 def _simple_new( # type: ignore[override]
318 cls,
319 values: npt.NDArray[np.datetime64],
320 freq: BaseOffset | None = None,
321 dtype: np.dtype[np.datetime64] | DatetimeTZDtype = DT64NS_DTYPE,
322 ) -> Self:
323 assert isinstance(values, np.ndarray)
324 assert dtype.kind == "M"
325 if isinstance(dtype, np.dtype):
326 assert dtype == values.dtype
327 assert not is_unitless(dtype)
328 else:
329 # DatetimeTZDtype. If we have e.g. DatetimeTZDtype[us, UTC],
330 # then values.dtype should be M8[us].
331 assert dtype._creso == get_unit_from_dtype(values.dtype)
332
333 result = super()._simple_new(values, dtype)
334 result._freq = freq
335 return result
336
337 @classmethod
338 def _from_sequence(cls, scalars, *, dtype=None, copy: bool = False) -> Self:
339 return cls._from_sequence_not_strict(scalars, dtype=dtype, copy=copy)
340
341 @classmethod
342 def _from_sequence_not_strict(
343 cls,
344 data,
345 *,
346 dtype=None,
347 copy: bool = False,
348 tz=lib.no_default,
349 freq: str | BaseOffset | lib.NoDefault | None = lib.no_default,
350 dayfirst: bool = False,
351 yearfirst: bool = False,
352 ambiguous: TimeAmbiguous = "raise",
353 ) -> Self:
354 """
355 A non-strict version of _from_sequence, called from DatetimeIndex.__new__.
356 """
357
358 # if the user either explicitly passes tz=None or a tz-naive dtype, we
359 # disallows inferring a tz.
360 explicit_tz_none = tz is None
361 if tz is lib.no_default:
362 tz = None
363 else:
364 tz = timezones.maybe_get_tz(tz)
365
366 dtype = _validate_dt64_dtype(dtype)
367 # if dtype has an embedded tz, capture it
368 tz = _validate_tz_from_dtype(dtype, tz, explicit_tz_none)
369
370 unit = None
371 if dtype is not None:
372 unit = dtl.dtype_to_unit(dtype)
373
374 data, copy = dtl.ensure_arraylike_for_datetimelike(
375 data, copy, cls_name="DatetimeArray"
376 )
377 inferred_freq = None
378 if isinstance(data, DatetimeArray):
379 inferred_freq = data.freq
380
381 subarr, tz = _sequence_to_dt64(
382 data,
383 copy=copy,
384 tz=tz,
385 dayfirst=dayfirst,
386 yearfirst=yearfirst,
387 ambiguous=ambiguous,
388 out_unit=unit,
389 )
390 # We have to call this again after possibly inferring a tz above
391 _validate_tz_from_dtype(dtype, tz, explicit_tz_none)
392 if tz is not None and explicit_tz_none:
393 raise ValueError(
394 "Passed data is timezone-aware, incompatible with 'tz=None'. "
395 "Use obj.tz_localize(None) instead."
396 )
397
398 data_unit = np.datetime_data(subarr.dtype)[0]
399 data_unit = cast("TimeUnit", data_unit)
400 data_dtype = tz_to_dtype(tz, data_unit)
401 result = cls._simple_new(subarr, freq=inferred_freq, dtype=data_dtype)
402 if unit is not None and unit != result.unit:
403 # If unit was specified in user-passed dtype, cast to it here
404 # error: Argument 1 to "as_unit" of "TimelikeOps" has
405 # incompatible type "str"; expected "Literal['s', 'ms', 'us', 'ns']"
406 # [arg-type]
407 result = result.as_unit(unit) # type: ignore[arg-type]
408
409 validate_kwds = {"ambiguous": ambiguous}
410 result._maybe_pin_freq(freq, validate_kwds)
411 return result
412
413 @classmethod
414 def _generate_range(
415 cls,
416 start,
417 end,
418 periods: int | None,
419 freq,
420 tz=None,
421 normalize: bool = False,
422 ambiguous: TimeAmbiguous = "raise",
423 nonexistent: TimeNonexistent = "raise",
424 inclusive: IntervalClosedType = "both",
425 *,
426 unit: TimeUnit = "ns",
427 ) -> Self:
428 periods = dtl.validate_periods(periods)
429 if freq is None and any(x is None for x in [periods, start, end]):
430 raise ValueError("Must provide freq argument if no data is supplied")
431
432 if com.count_not_none(start, end, periods, freq) != 3:
433 raise ValueError(
434 "Of the four parameters: start, end, periods, "
435 "and freq, exactly three must be specified"
436 )
437 freq = to_offset(freq)
438
439 if start is not None:
440 start = Timestamp(start)
441
442 if end is not None:
443 end = Timestamp(end)
444
445 if start is NaT or end is NaT:
446 raise ValueError("Neither `start` nor `end` can be NaT")
447
448 if unit is not None:
449 if unit not in ["s", "ms", "us", "ns"]:
450 raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'")
451 else:
452 unit = "ns"
453
454 if start is not None:
455 start = start.as_unit(unit, round_ok=False)
456 if end is not None:
457 end = end.as_unit(unit, round_ok=False)
458
459 left_inclusive, right_inclusive = validate_inclusive(inclusive)
460 start, end = _maybe_normalize_endpoints(start, end, normalize)
461 tz = _infer_tz_from_endpoints(start, end, tz)
462
463 if tz is not None:
464 # Localize the start and end arguments
465 start = _maybe_localize_point(start, freq, tz, ambiguous, nonexistent)
466 end = _maybe_localize_point(end, freq, tz, ambiguous, nonexistent)
467
468 if freq is not None:
469 # Offset handling:
470 # Ticks (fixed-duration like hours/minutes): keep tz; do absolute-time math.
471 # Other calendar offsets: drop tz; do naive wall time; localize once later
472 # so `ambiguous`/`nonexistent` are applied correctly.
473 if not isinstance(freq, Tick):
474 if start is not None and start.tz is not None:
475 start = start.tz_localize(None)
476 if end is not None and end.tz is not None:
477 end = end.tz_localize(None)
478
479 if isinstance(freq, (Tick, Day)):
480 i8values = generate_regular_range(start, end, periods, freq, unit=unit)
481 else:
482 xdr = _generate_range(
483 start=start, end=end, periods=periods, offset=freq, unit=unit
484 )
485 i8values = np.array([x._value for x in xdr], dtype=np.int64)
486
487 endpoint_tz = start.tz if start is not None else end.tz
488
489 if tz is not None and endpoint_tz is None:
490 if not timezones.is_utc(tz):
491 # short-circuit tz_localize_to_utc which would make
492 # an unnecessary copy with UTC but be a no-op.
493 creso = abbrev_to_npy_unit(unit)
494 i8values = tzconversion.tz_localize_to_utc(
495 i8values,
496 tz,
497 ambiguous=ambiguous,
498 nonexistent=nonexistent,
499 creso=creso,
500 )
501
502 # i8values is localized datetime64 array -> have to convert
503 # start/end as well to compare
504 if start is not None:
505 start = start.tz_localize(tz, ambiguous, nonexistent)
506 if end is not None:
507 end = end.tz_localize(tz, ambiguous, nonexistent)
508 else:
509 # Create a linearly spaced date_range in local time
510 # Nanosecond-granularity timestamps aren't always correctly
511 # representable with doubles, so we limit the range that we
512 # pass to np.linspace as much as possible
513 periods = cast(int, periods)
514 i8values = (
515 np.linspace(0, end._value - start._value, periods, dtype="int64")
516 + start._value
517 )
518 if i8values.dtype != "i8":
519 # 2022-01-09 I (brock) am not sure if it is possible for this
520 # to overflow and cast to e.g. f8, but if it does we need to cast
521 i8values = i8values.astype("i8")
522
523 if start == end:
524 if not left_inclusive and not right_inclusive:
525 i8values = i8values[1:-1]
526 else:
527 start_i8 = Timestamp(start)._value
528 end_i8 = Timestamp(end)._value
529 if not left_inclusive or not right_inclusive:
530 if not left_inclusive and len(i8values) and i8values[0] == start_i8:
531 i8values = i8values[1:]
532 if not right_inclusive and len(i8values) and i8values[-1] == end_i8:
533 i8values = i8values[:-1]
534
535 dt64_values = i8values.view(f"datetime64[{unit}]")
536 dtype = tz_to_dtype(tz, unit=unit)
537 return cls._simple_new(dt64_values, freq=freq, dtype=dtype)
538
539 # -----------------------------------------------------------------
540 # DatetimeLike Interface
541
542 def _unbox_scalar(self, value) -> np.datetime64:
543 if not isinstance(value, self._scalar_type) and value is not NaT:
544 raise ValueError("'value' should be a Timestamp.")
545 self._check_compatible_with(value)
546 if value is NaT:
547 return np.datetime64(value._value, self.unit)
548 else:
549 return value.as_unit(self.unit, round_ok=False).asm8
550
551 def _scalar_from_string(self, value) -> Timestamp | NaTType:
552 return Timestamp(value, tz=self.tz)
553
554 def _check_compatible_with(self, other) -> None:
555 if other is NaT:
556 return
557 self._assert_tzawareness_compat(other)
558
559 # -----------------------------------------------------------------
560 # Descriptive Properties
561
562 def _box_func(self, x: np.datetime64) -> Timestamp | NaTType:
563 # GH#42228
564 value = x.view("i8")
565 ts = Timestamp._from_value_and_reso(value, reso=self._creso, tz=self.tz)
566 return ts
567
568 @property
569 # error: Return type "Union[dtype, DatetimeTZDtype]" of "dtype"
570 # incompatible with return type "ExtensionDtype" in supertype
571 # "ExtensionArray"
572 def dtype(self) -> np.dtype[np.datetime64] | DatetimeTZDtype: # type: ignore[override]
573 """
574 The dtype for the DatetimeArray.
575
576 .. warning::
577
578 A future version of pandas will change dtype to never be a
579 ``numpy.dtype``. Instead, :attr:`DatetimeArray.dtype` will
580 always be an instance of an ``ExtensionDtype`` subclass.
581
582 Returns
583 -------
584 numpy.dtype or DatetimeTZDtype
585 If the values are tz-naive, then ``np.dtype('datetime64[ns]')``
586 is returned.
587
588 If the values are tz-aware, then the ``DatetimeTZDtype``
589 is returned.
590 """
591 return self._dtype
592
593 @property
594 def tz(self) -> tzinfo | None:
595 """
596 Return the timezone.
597
598 Returns
599 -------
600 zoneinfo.ZoneInfo,, datetime.tzinfo, pytz.tzinfo.BaseTZInfo, dateutil.tz.tz.tzfile, or None
601 Returns None when the array is tz-naive.
602
603 See Also
604 --------
605 DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
606 given time zone, or remove timezone from a tz-aware DatetimeIndex.
607 DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
608 one time zone to another.
609
610 Examples
611 --------
612 For Series:
613
614 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
615 >>> s = pd.to_datetime(s)
616 >>> s
617 0 2020-01-01 10:00:00+00:00
618 1 2020-02-01 11:00:00+00:00
619 dtype: datetime64[us, UTC]
620 >>> s.dt.tz
621 datetime.timezone.utc
622
623 For DatetimeIndex:
624
625 >>> idx = pd.DatetimeIndex(
626 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
627 ... )
628 >>> idx.tz
629 datetime.timezone.utc
630 """ # noqa: E501
631 # GH 18595
632 return getattr(self.dtype, "tz", None)
633
634 @tz.setter
635 def tz(self, value):
636 # GH 3746: Prevent localizing or converting the index by setting tz
637 raise AttributeError(
638 "Cannot directly set timezone. Use tz_localize() "
639 "or tz_convert() as appropriate"
640 )
641
642 @property
643 def tzinfo(self) -> tzinfo | None:
644 """
645 Alias for tz attribute
646 """
647 return self.tz
648
649 @property # NB: override with cache_readonly in immutable subclasses
650 def is_normalized(self) -> bool:
651 """
652 Returns True if all of the dates are at midnight ("no time")
653 """
654 return is_date_array_normalized(self.asi8, self.tz, reso=self._creso)
655
656 @property # NB: override with cache_readonly in immutable subclasses
657 def _resolution_obj(self) -> Resolution:
658 return get_resolution(self.asi8, self.tz, reso=self._creso)
659
660 # ----------------------------------------------------------------
661 # Array-Like / EA-Interface Methods
662
663 def __array__(self, dtype=None, copy=None) -> np.ndarray:
664 if dtype is None and self.tz:
665 # The default for tz-aware is object, to preserve tz info
666 dtype = object
667
668 return super().__array__(dtype=dtype, copy=copy)
669
670 def __iter__(self) -> Iterator:
671 """
672 Return an iterator over the boxed values
673
674 Yields
675 ------
676 tstamp : Timestamp
677 """
678 if self.ndim > 1:
679 for i in range(len(self)):
680 yield self[i]
681 else:
682 # convert in chunks of 10k for efficiency
683 data = self.asi8
684 length = len(self)
685 chunksize = _ITER_CHUNKSIZE
686 chunks = (length // chunksize) + 1
687
688 for i in range(chunks):
689 start_i = i * chunksize
690 end_i = min((i + 1) * chunksize, length)
691 converted = ints_to_pydatetime(
692 data[start_i:end_i],
693 tz=self.tz,
694 box="timestamp",
695 reso=self._creso,
696 )
697 yield from converted
698
699 def astype(self, dtype, copy: bool = True):
700 # We handle
701 # --> datetime
702 # --> period
703 # DatetimeLikeArrayMixin Super handles the rest.
704 dtype = pandas_dtype(dtype)
705
706 if dtype == self.dtype:
707 if copy:
708 return self.copy()
709 return self
710
711 elif isinstance(dtype, ExtensionDtype):
712 if not isinstance(dtype, DatetimeTZDtype):
713 # e.g. Sparse[datetime64[ns]]
714 return super().astype(dtype, copy=copy)
715 elif self.tz is None:
716 # pre-2.0 this did self.tz_localize(dtype.tz), which did not match
717 # the Series behavior which did
718 # values.tz_localize("UTC").tz_convert(dtype.tz)
719 raise TypeError(
720 "Cannot use .astype to convert from timezone-naive dtype to "
721 "timezone-aware dtype. Use obj.tz_localize instead or "
722 "series.dt.tz_localize instead"
723 )
724 else:
725 # tzaware unit conversion e.g. datetime64[s, UTC]
726 np_dtype = np.dtype(dtype.str)
727 res_values = astype_overflowsafe(self._ndarray, np_dtype, copy=copy)
728 return type(self)._simple_new(res_values, dtype=dtype, freq=self.freq)
729
730 elif (
731 self.tz is None
732 and lib.is_np_dtype(dtype, "M")
733 and not is_unitless(dtype)
734 and is_supported_dtype(dtype)
735 ):
736 # unit conversion e.g. datetime64[s]
737 res_values = astype_overflowsafe(self._ndarray, dtype, copy=True)
738 return type(self)._simple_new(res_values, dtype=res_values.dtype)
739 # TODO: preserve freq?
740
741 elif self.tz is not None and lib.is_np_dtype(dtype, "M"):
742 # pre-2.0 behavior for DTA/DTI was
743 # values.tz_convert("UTC").tz_localize(None), which did not match
744 # the Series behavior
745 raise TypeError(
746 "Cannot use .astype to convert from timezone-aware dtype to "
747 "timezone-naive dtype. Use obj.tz_localize(None) or "
748 "obj.tz_convert('UTC').tz_localize(None) instead."
749 )
750
751 elif (
752 self.tz is None
753 and lib.is_np_dtype(dtype, "M")
754 and dtype != self.dtype
755 and is_unitless(dtype)
756 ):
757 raise TypeError(
758 "Casting to unit-less dtype 'datetime64' is not supported. "
759 "Pass e.g. 'datetime64[ns]' instead."
760 )
761
762 elif isinstance(dtype, PeriodDtype):
763 return self.to_period(freq=dtype.freq)
764 return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy)
765
766 # -----------------------------------------------------------------
767 # Rendering Methods
768
769 def _format_native_types(
770 self, *, na_rep: str | float = "NaT", date_format=None, **kwargs
771 ) -> npt.NDArray[np.object_]:
772 if date_format is None and self._is_dates_only:
773 # Only dates and no timezone: provide a default format
774 date_format = "%Y-%m-%d"
775
776 return tslib.format_array_from_datetime(
777 self.asi8, tz=self.tz, format=date_format, na_rep=na_rep, reso=self._creso
778 )
779
780 # -----------------------------------------------------------------
781 # Comparison Methods
782
783 def _assert_tzawareness_compat(self, other) -> None:
784 # adapted from _Timestamp._assert_tzawareness_compat
785 other_tz = getattr(other, "tzinfo", None)
786 other_dtype = getattr(other, "dtype", None)
787
788 if isinstance(other_dtype, DatetimeTZDtype):
789 # Get tzinfo from Series dtype
790 other_tz = other.dtype.tz
791 if other is NaT:
792 # pd.NaT quacks both aware and naive
793 pass
794 elif self.tz is None:
795 if other_tz is not None:
796 raise TypeError(
797 "Cannot compare tz-naive and tz-aware datetime-like objects."
798 )
799 elif other_tz is None:
800 raise TypeError(
801 "Cannot compare tz-naive and tz-aware datetime-like objects"
802 )
803
804 # -----------------------------------------------------------------
805 # Arithmetic Methods
806
807 def _add_offset(self, offset: BaseOffset) -> Self:
808 assert not isinstance(offset, Tick)
809
810 if self.tz is not None:
811 values = self.tz_localize(None)
812 else:
813 values = self
814
815 try:
816 res_values = offset._apply_array(values._ndarray)
817 if res_values.dtype.kind == "i":
818 # error: Argument 1 to "view" of "ndarray" has
819 # incompatible type
820 # "dtype[datetime64[date | int | None]] | DatetimeTZDtype";
821 # expected "dtype[Any] | _HasDType[dtype[Any]]" [arg-type]
822 res_values = res_values.view(values.dtype) # type: ignore[arg-type]
823 except NotImplementedError:
824 if get_option("performance_warnings"):
825 warnings.warn(
826 "Non-vectorized DateOffset being applied to Series or "
827 "DatetimeIndex.",
828 PerformanceWarning,
829 stacklevel=find_stack_level(),
830 )
831 res_values = self.astype("O") + offset
832 result = type(self)._from_sequence(res_values, dtype=self.dtype)
833
834 else:
835 result = type(self)._simple_new(res_values, dtype=res_values.dtype)
836 if offset.normalize:
837 result = result.normalize()
838 result._freq = None
839
840 if self.tz is not None:
841 result = result.tz_localize(self.tz)
842
843 return result
844
845 # -----------------------------------------------------------------
846 # Timezone Conversion and Localization Methods
847
848 def _local_timestamps(self) -> npt.NDArray[np.int64]:
849 """
850 Convert to an i8 (unix-like nanosecond timestamp) representation
851 while keeping the local timezone and not using UTC.
852 This is used to calculate time-of-day information as if the timestamps
853 were timezone-naive.
854 """
855 if self.tz is None or timezones.is_utc(self.tz):
856 # Avoid the copy that would be made in tzconversion
857 return self.asi8
858 return tz_convert_from_utc(self.asi8, self.tz, reso=self._creso)
859
860 def tz_convert(self, tz) -> Self:
861 """
862 Convert tz-aware Datetime Array/Index from one time zone to another.
863
864 Parameters
865 ----------
866 tz : str, zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
867 Time zone for time. Corresponding timestamps would be converted
868 to this time zone of the Datetime Array/Index. A `tz` of None will
869 convert to UTC and remove the timezone information.
870
871 Returns
872 -------
873 Array or Index
874 Datetme Array/Index with target `tz`.
875
876 Raises
877 ------
878 TypeError
879 If Datetime Array/Index is tz-naive.
880
881 See Also
882 --------
883 DatetimeIndex.tz : A timezone that has a variable offset from UTC.
884 DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
885 given time zone, or remove timezone from a tz-aware DatetimeIndex.
886
887 Examples
888 --------
889 With the `tz` parameter, we can change the DatetimeIndex
890 to other time zones:
891
892 >>> dti = pd.date_range(
893 ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
894 ... )
895
896 >>> dti
897 DatetimeIndex(['2014-08-01 09:00:00+02:00',
898 '2014-08-01 10:00:00+02:00',
899 '2014-08-01 11:00:00+02:00'],
900 dtype='datetime64[us, Europe/Berlin]', freq='h')
901
902 >>> dti.tz_convert("US/Central")
903 DatetimeIndex(['2014-08-01 02:00:00-05:00',
904 '2014-08-01 03:00:00-05:00',
905 '2014-08-01 04:00:00-05:00'],
906 dtype='datetime64[us, US/Central]', freq='h')
907
908 With the ``tz=None``, we can remove the timezone (after converting
909 to UTC if necessary):
910
911 >>> dti = pd.date_range(
912 ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
913 ... )
914
915 >>> dti
916 DatetimeIndex(['2014-08-01 09:00:00+02:00',
917 '2014-08-01 10:00:00+02:00',
918 '2014-08-01 11:00:00+02:00'],
919 dtype='datetime64[us, Europe/Berlin]', freq='h')
920
921 >>> dti.tz_convert(None)
922 DatetimeIndex(['2014-08-01 07:00:00',
923 '2014-08-01 08:00:00',
924 '2014-08-01 09:00:00'],
925 dtype='datetime64[us]', freq='h')
926 """ # noqa: E501
927 tz = timezones.maybe_get_tz(tz)
928
929 if self.tz is None:
930 # tz naive, use tz_localize
931 raise TypeError(
932 "Cannot convert tz-naive timestamps, use tz_localize to localize"
933 )
934
935 # No conversion since timestamps are all UTC to begin with
936 dtype = tz_to_dtype(tz, unit=self.unit)
937 new_freq = None
938 if isinstance(self.freq, Tick):
939 new_freq = self.freq
940 return self._simple_new(self._ndarray, dtype=dtype, freq=new_freq)
941
942 @dtl.ravel_compat
943 def tz_localize(
944 self,
945 tz,
946 ambiguous: TimeAmbiguous = "raise",
947 nonexistent: TimeNonexistent = "raise",
948 ) -> Self:
949 """
950 Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.
951
952 This method takes a time zone (tz) naive Datetime Array/Index object
953 and makes this time zone aware. It does not move the time to another
954 time zone.
955
956 This method can also be used to do the inverse -- to create a time
957 zone unaware object from an aware object. To that end, pass `tz=None`.
958
959 Parameters
960 ----------
961 tz : str, zoneinfo.ZoneInfo,, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
962 Time zone to convert timestamps to. Passing ``None`` will
963 remove the time zone information preserving local time.
964 ambiguous : 'infer', 'NaT', bool array, default 'raise'
965 When clocks moved backward due to DST, ambiguous times may arise.
966 For example in Central European Time (UTC+01), when going from
967 03:00 DST to 02:00 non-DST, 02:30:00 local time occurs both at
968 00:30:00 UTC and at 01:30:00 UTC. In such a situation, the
969 `ambiguous` parameter dictates how ambiguous times should be
970 handled.
971
972 - 'infer' will attempt to infer fall dst-transition hours based on
973 order
974 - bool-ndarray where True signifies a DST time, False signifies a
975 non-DST time (note that this flag is only applicable for
976 ambiguous times)
977 - 'NaT' will return NaT where there are ambiguous times
978 - 'raise' will raise a ValueError if there are ambiguous
979 times.
980
981 nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
982default 'raise'
983 A nonexistent time does not exist in a particular timezone
984 where clocks moved forward due to DST.
985
986 - 'shift_forward' will shift the nonexistent time forward to the
987 closest existing time
988 - 'shift_backward' will shift the nonexistent time backward to the
989 closest existing time
990 - 'NaT' will return NaT where there are nonexistent times
991 - timedelta objects will shift nonexistent times by the timedelta
992 - 'raise' will raise a ValueError if there are
993 nonexistent times.
994
995 Returns
996 -------
997 Same type as self
998 Array/Index converted to the specified time zone.
999
1000 Raises
1001 ------
1002 TypeError
1003 If the Datetime Array/Index is tz-aware and tz is not None.
1004
1005 See Also
1006 --------
1007 DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
1008 one time zone to another.
1009
1010 Examples
1011 --------
1012 >>> tz_naive = pd.date_range('2018-03-01 09:00', periods=3)
1013 >>> tz_naive
1014 DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
1015 '2018-03-03 09:00:00'],
1016 dtype='datetime64[us]', freq='D')
1017
1018 Localize DatetimeIndex in US/Eastern time zone:
1019
1020 >>> tz_aware = tz_naive.tz_localize(tz='US/Eastern')
1021 >>> tz_aware
1022 DatetimeIndex(['2018-03-01 09:00:00-05:00',
1023 '2018-03-02 09:00:00-05:00',
1024 '2018-03-03 09:00:00-05:00'],
1025 dtype='datetime64[us, US/Eastern]', freq=None)
1026
1027 With the ``tz=None``, we can remove the time zone information
1028 while keeping the local time (not converted to UTC):
1029
1030 >>> tz_aware.tz_localize(None)
1031 DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
1032 '2018-03-03 09:00:00'],
1033 dtype='datetime64[us]', freq=None)
1034
1035 Be careful with DST changes. When there is sequential data, pandas can
1036 infer the DST time:
1037
1038 >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:30:00',
1039 ... '2018-10-28 02:00:00',
1040 ... '2018-10-28 02:30:00',
1041 ... '2018-10-28 02:00:00',
1042 ... '2018-10-28 02:30:00',
1043 ... '2018-10-28 03:00:00',
1044 ... '2018-10-28 03:30:00']))
1045 >>> s.dt.tz_localize('CET', ambiguous='infer')
1046 0 2018-10-28 01:30:00+02:00
1047 1 2018-10-28 02:00:00+02:00
1048 2 2018-10-28 02:30:00+02:00
1049 3 2018-10-28 02:00:00+01:00
1050 4 2018-10-28 02:30:00+01:00
1051 5 2018-10-28 03:00:00+01:00
1052 6 2018-10-28 03:30:00+01:00
1053 dtype: datetime64[us, CET]
1054
1055 In some cases, inferring the DST is impossible. In such cases, you can
1056 pass an ndarray to the ambiguous parameter to set the DST explicitly
1057
1058 >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:20:00',
1059 ... '2018-10-28 02:36:00',
1060 ... '2018-10-28 03:46:00']))
1061 >>> s.dt.tz_localize('CET', ambiguous=np.array([True, True, False]))
1062 0 2018-10-28 01:20:00+02:00
1063 1 2018-10-28 02:36:00+02:00
1064 2 2018-10-28 03:46:00+01:00
1065 dtype: datetime64[us, CET]
1066
1067 If the DST transition causes nonexistent times, you can shift these
1068 dates forward or backwards with a timedelta object or `'shift_forward'`
1069 or `'shift_backwards'`.
1070
1071 >>> s = pd.to_datetime(pd.Series(['2015-03-29 02:30:00',
1072 ... '2015-03-29 03:30:00'], dtype="M8[ns]"))
1073 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_forward')
1074 0 2015-03-29 03:00:00+02:00
1075 1 2015-03-29 03:30:00+02:00
1076 dtype: datetime64[ns, Europe/Warsaw]
1077
1078 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_backward')
1079 0 2015-03-29 01:59:59.999999999+01:00
1080 1 2015-03-29 03:30:00+02:00
1081 dtype: datetime64[ns, Europe/Warsaw]
1082
1083 >>> s.dt.tz_localize('Europe/Warsaw', nonexistent=pd.Timedelta('1h'))
1084 0 2015-03-29 03:30:00+02:00
1085 1 2015-03-29 03:30:00+02:00
1086 dtype: datetime64[ns, Europe/Warsaw]
1087 """ # noqa: E501
1088 nonexistent_options = ("raise", "NaT", "shift_forward", "shift_backward")
1089 if nonexistent not in nonexistent_options and not isinstance(
1090 nonexistent, timedelta
1091 ):
1092 raise ValueError(
1093 "The nonexistent argument must be one of 'raise', "
1094 "'NaT', 'shift_forward', 'shift_backward' or "
1095 "a timedelta object"
1096 )
1097
1098 if self.tz is not None:
1099 if tz is None:
1100 new_dates = tz_convert_from_utc(self.asi8, self.tz, reso=self._creso)
1101 else:
1102 raise TypeError("Already tz-aware, use tz_convert to convert.")
1103 else:
1104 tz = timezones.maybe_get_tz(tz)
1105 # Convert to UTC
1106
1107 new_dates = tzconversion.tz_localize_to_utc(
1108 self.asi8,
1109 tz,
1110 ambiguous=ambiguous,
1111 nonexistent=nonexistent,
1112 creso=self._creso,
1113 )
1114 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1115 dtype = tz_to_dtype(tz, unit=self.unit)
1116
1117 freq = None
1118 if timezones.is_utc(tz) or (len(self) == 1 and not isna(new_dates_dt64[0])):
1119 # we can preserve freq
1120 # TODO: Also for fixed-offsets
1121 freq = self.freq
1122 elif tz is None and self.tz is None:
1123 # no-op
1124 freq = self.freq
1125 return self._simple_new(new_dates_dt64, dtype=dtype, freq=freq)
1126
1127 # ----------------------------------------------------------------
1128 # Conversion Methods - Vectorized analogues of Timestamp methods
1129
1130 def to_pydatetime(self) -> npt.NDArray[np.object_]:
1131 """
1132 Return an ndarray of ``datetime.datetime`` objects.
1133
1134 Returns
1135 -------
1136 numpy.ndarray
1137 An ndarray of ``datetime.datetime`` objects.
1138
1139 See Also
1140 --------
1141 DatetimeIndex.to_julian_date : Converts Datetime Array to float64 ndarray
1142 of Julian Dates.
1143
1144 Examples
1145 --------
1146 >>> idx = pd.date_range("2018-02-27", periods=3)
1147 >>> idx.to_pydatetime()
1148 array([datetime.datetime(2018, 2, 27, 0, 0),
1149 datetime.datetime(2018, 2, 28, 0, 0),
1150 datetime.datetime(2018, 3, 1, 0, 0)], dtype=object)
1151 """
1152 return ints_to_pydatetime(self.asi8, tz=self.tz, reso=self._creso)
1153
1154 def normalize(self) -> Self:
1155 """
1156 Convert times to midnight.
1157
1158 The time component of the date-time is converted to midnight i.e.
1159 00:00:00. This is useful in cases, when the time does not matter.
1160 Length is unaltered. The timezones are unaffected.
1161
1162 This method is available on Series with datetime values under
1163 the ``.dt`` accessor, and directly on Datetime Array/Index.
1164
1165 Returns
1166 -------
1167 DatetimeArray, DatetimeIndex or Series
1168 The same type as the original data. Series will have the same
1169 name and index. DatetimeIndex will have the same name.
1170
1171 See Also
1172 --------
1173 floor : Floor the datetimes to the specified freq.
1174 ceil : Ceil the datetimes to the specified freq.
1175 round : Round the datetimes to the specified freq.
1176
1177 Examples
1178 --------
1179 >>> idx = pd.date_range(
1180 ... start="2014-08-01 10:00", freq="h", periods=3, tz="Asia/Calcutta"
1181 ... )
1182 >>> idx
1183 DatetimeIndex(['2014-08-01 10:00:00+05:30',
1184 '2014-08-01 11:00:00+05:30',
1185 '2014-08-01 12:00:00+05:30'],
1186 dtype='datetime64[us, Asia/Calcutta]', freq='h')
1187 >>> idx.normalize()
1188 DatetimeIndex(['2014-08-01 00:00:00+05:30',
1189 '2014-08-01 00:00:00+05:30',
1190 '2014-08-01 00:00:00+05:30'],
1191 dtype='datetime64[us, Asia/Calcutta]', freq=None)
1192 """
1193 new_values = normalize_i8_timestamps(self.asi8, self.tz, reso=self._creso)
1194 dt64_values = new_values.view(self._ndarray.dtype)
1195
1196 dta = type(self)._simple_new(dt64_values, dtype=dt64_values.dtype)
1197 dta = dta._with_freq("infer")
1198 if self.tz is not None:
1199 dta = dta.tz_localize(self.tz)
1200 return dta
1201
1202 def to_period(self, freq=None) -> PeriodArray:
1203 """
1204 Cast to PeriodArray/PeriodIndex at a particular frequency.
1205
1206 Converts DatetimeArray/Index to PeriodArray/PeriodIndex.
1207
1208 Parameters
1209 ----------
1210 freq : str or Period, optional
1211 One of pandas' :ref:`period aliases <timeseries.period_aliases>`
1212 or a Period object. Will be inferred by default.
1213
1214 Returns
1215 -------
1216 PeriodArray/PeriodIndex
1217 Immutable ndarray holding ordinal values at a particular frequency.
1218
1219 Raises
1220 ------
1221 ValueError
1222 When converting a DatetimeArray/Index with non-regular values,
1223 so that a frequency cannot be inferred.
1224
1225 See Also
1226 --------
1227 PeriodIndex: Immutable ndarray holding ordinal values.
1228 DatetimeIndex.to_pydatetime: Return DatetimeIndex as object.
1229
1230 Examples
1231 --------
1232 >>> df = pd.DataFrame(
1233 ... {"y": [1, 2, 3]},
1234 ... index=pd.to_datetime(
1235 ... [
1236 ... "2000-03-31 00:00:00",
1237 ... "2000-05-31 00:00:00",
1238 ... "2000-08-31 00:00:00",
1239 ... ]
1240 ... ),
1241 ... )
1242 >>> df.index.to_period("M")
1243 PeriodIndex(['2000-03', '2000-05', '2000-08'],
1244 dtype='period[M]')
1245
1246 Infer the daily frequency
1247
1248 >>> idx = pd.date_range("2017-01-01", periods=2)
1249 >>> idx.to_period()
1250 PeriodIndex(['2017-01-01', '2017-01-02'],
1251 dtype='period[D]')
1252 """
1253 from pandas.core.arrays import PeriodArray
1254
1255 if self.tz is not None:
1256 warnings.warn(
1257 "Converting to PeriodArray/Index representation "
1258 "will drop timezone information.",
1259 UserWarning,
1260 stacklevel=find_stack_level(),
1261 )
1262
1263 if freq is None:
1264 freq = self.freqstr or self.inferred_freq
1265 if isinstance(self.freq, BaseOffset) and hasattr(
1266 self.freq, "_period_dtype_code"
1267 ):
1268 freq = PeriodDtype(self.freq)._freqstr
1269
1270 if freq is None:
1271 raise ValueError(
1272 "You must pass a freq argument as current index has none."
1273 )
1274
1275 res = get_period_alias(freq)
1276
1277 # https://github.com/pandas-dev/pandas/issues/33358
1278 if res is None:
1279 res = freq
1280
1281 freq = res
1282 return PeriodArray._from_datetime64(self._ndarray, freq, tz=self.tz)
1283
1284 # -----------------------------------------------------------------
1285 # Properties - Vectorized Timestamp Properties/Methods
1286
1287 def month_name(self, locale=None) -> npt.NDArray[np.object_]:
1288 """
1289 Return the month names with specified locale.
1290
1291 Parameters
1292 ----------
1293 locale : str, optional
1294 Locale determining the language in which to return the month name.
1295 Default is English locale (``'en_US.utf8'``). Use the command
1296 ``locale -a`` on your terminal on Unix systems to find your locale
1297 language code.
1298
1299 Returns
1300 -------
1301 Series or Index
1302 Series or Index of month names.
1303
1304 See Also
1305 --------
1306 DatetimeIndex.day_name : Return the day names with specified locale.
1307
1308 Examples
1309 --------
1310 >>> s = pd.Series(pd.date_range(start="2018-01", freq="ME", periods=3))
1311 >>> s
1312 0 2018-01-31
1313 1 2018-02-28
1314 2 2018-03-31
1315 dtype: datetime64[us]
1316 >>> s.dt.month_name()
1317 0 January
1318 1 February
1319 2 March
1320 dtype: str
1321
1322 >>> idx = pd.date_range(start="2018-01", freq="ME", periods=3)
1323 >>> idx
1324 DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31'],
1325 dtype='datetime64[us]', freq='ME')
1326 >>> idx.month_name()
1327 Index(['January', 'February', 'March'], dtype='str')
1328
1329 Using the ``locale`` parameter you can set a different locale language,
1330 for example: ``idx.month_name(locale='pt_BR.utf8')`` will return month
1331 names in Brazilian Portuguese language.
1332
1333 >>> idx = pd.date_range(start="2018-01", freq="ME", periods=3)
1334 >>> idx
1335 DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31'],
1336 dtype='datetime64[us]', freq='ME')
1337 >>> idx.month_name(locale="pt_BR.utf8") # doctest: +SKIP
1338 Index(['Janeiro', 'Fevereiro', 'Março'], dtype='str')
1339 """
1340 values = self._local_timestamps()
1341
1342 result = fields.get_date_name_field(
1343 values, "month_name", locale=locale, reso=self._creso
1344 )
1345 result = self._maybe_mask_results(result, fill_value=None)
1346 if using_string_dtype():
1347 from pandas import (
1348 StringDtype,
1349 array as pd_array,
1350 )
1351
1352 return pd_array(result, dtype=StringDtype(na_value=np.nan)) # type: ignore[return-value]
1353 return result
1354
1355 def day_name(self, locale=None) -> npt.NDArray[np.object_]:
1356 """
1357 Return the day names with specified locale.
1358
1359 Parameters
1360 ----------
1361 locale : str, optional
1362 Locale determining the language in which to return the day name.
1363 Default is English locale (``'en_US.utf8'``). Use the command
1364 ``locale -a`` on your terminal on Unix systems to find your locale
1365 language code.
1366
1367 Returns
1368 -------
1369 Series or Index
1370 Series or Index of day names.
1371
1372 See Also
1373 --------
1374 DatetimeIndex.month_name : Return the month names with specified locale.
1375
1376 Examples
1377 --------
1378 >>> s = pd.Series(pd.date_range(start="2018-01-01", freq="D", periods=3))
1379 >>> s
1380 0 2018-01-01
1381 1 2018-01-02
1382 2 2018-01-03
1383 dtype: datetime64[us]
1384 >>> s.dt.day_name()
1385 0 Monday
1386 1 Tuesday
1387 2 Wednesday
1388 dtype: str
1389
1390 >>> idx = pd.date_range(start="2018-01-01", freq="D", periods=3)
1391 >>> idx
1392 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03'],
1393 dtype='datetime64[us]', freq='D')
1394 >>> idx.day_name()
1395 Index(['Monday', 'Tuesday', 'Wednesday'], dtype='str')
1396
1397 Using the ``locale`` parameter you can set a different locale language,
1398 for example: ``idx.day_name(locale='pt_BR.utf8')`` will return day
1399 names in Brazilian Portuguese language.
1400
1401 >>> idx = pd.date_range(start="2018-01-01", freq="D", periods=3)
1402 >>> idx
1403 DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03'],
1404 dtype='datetime64[us]', freq='D')
1405 >>> idx.day_name(locale="pt_BR.utf8") # doctest: +SKIP
1406 Index(['Segunda', 'Terça', 'Quarta'], dtype='str')
1407 """
1408 values = self._local_timestamps()
1409
1410 result = fields.get_date_name_field(
1411 values, "day_name", locale=locale, reso=self._creso
1412 )
1413 result = self._maybe_mask_results(result, fill_value=None)
1414 if using_string_dtype():
1415 # TODO: no tests that check for dtype of result as of 2024-08-15
1416 from pandas import (
1417 StringDtype,
1418 array as pd_array,
1419 )
1420
1421 return pd_array(result, dtype=StringDtype(na_value=np.nan)) # type: ignore[return-value]
1422 return result
1423
1424 @property
1425 def time(self) -> npt.NDArray[np.object_]:
1426 """
1427 Returns numpy array of :class:`datetime.time` objects.
1428
1429 The time part of the Timestamps.
1430
1431 See Also
1432 --------
1433 DatetimeIndex.timetz : Returns numpy array of :class:`datetime.time`
1434 objects with timezones. The time part of the Timestamps.
1435 DatetimeIndex.date : Returns numpy array of python :class:`datetime.date`
1436 objects. Namely, the date part of Timestamps without time and timezone
1437 information.
1438
1439 Examples
1440 --------
1441 For Series:
1442
1443 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
1444 >>> s = pd.to_datetime(s)
1445 >>> s
1446 0 2020-01-01 10:00:00+00:00
1447 1 2020-02-01 11:00:00+00:00
1448 dtype: datetime64[us, UTC]
1449 >>> s.dt.time
1450 0 10:00:00
1451 1 11:00:00
1452 dtype: object
1453
1454 For DatetimeIndex:
1455
1456 >>> idx = pd.DatetimeIndex(
1457 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
1458 ... )
1459 >>> idx.time
1460 array([datetime.time(10, 0), datetime.time(11, 0)], dtype=object)
1461 """
1462 # If the Timestamps have a timezone that is not UTC,
1463 # convert them into their i8 representation while
1464 # keeping their timezone and not using UTC
1465 timestamps = self._local_timestamps()
1466
1467 return ints_to_pydatetime(timestamps, box="time", reso=self._creso)
1468
1469 @property
1470 def timetz(self) -> npt.NDArray[np.object_]:
1471 """
1472 Returns numpy array of :class:`datetime.time` objects with timezones.
1473
1474 The time part of the Timestamps.
1475
1476 See Also
1477 --------
1478 DatetimeIndex.time : Returns numpy array of :class:`datetime.time` objects.
1479 The time part of the Timestamps.
1480 DatetimeIndex.tz : Return the timezone.
1481
1482 Examples
1483 --------
1484 For Series:
1485
1486 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
1487 >>> s = pd.to_datetime(s)
1488 >>> s
1489 0 2020-01-01 10:00:00+00:00
1490 1 2020-02-01 11:00:00+00:00
1491 dtype: datetime64[us, UTC]
1492 >>> s.dt.timetz
1493 0 10:00:00+00:00
1494 1 11:00:00+00:00
1495 dtype: object
1496
1497 For DatetimeIndex:
1498
1499 >>> idx = pd.DatetimeIndex(
1500 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
1501 ... )
1502 >>> idx.timetz
1503 array([datetime.time(10, 0, tzinfo=datetime.timezone.utc),
1504 datetime.time(11, 0, tzinfo=datetime.timezone.utc)], dtype=object)
1505 """
1506 return ints_to_pydatetime(self.asi8, self.tz, box="time", reso=self._creso)
1507
1508 @property
1509 def date(self) -> npt.NDArray[np.object_]:
1510 """
1511 Returns numpy array of python :class:`datetime.date` objects.
1512
1513 Namely, the date part of Timestamps without time and
1514 timezone information.
1515
1516 See Also
1517 --------
1518 DatetimeIndex.time : Returns numpy array of :class:`datetime.time` objects.
1519 The time part of the Timestamps.
1520 DatetimeIndex.year : The year of the datetime.
1521 DatetimeIndex.month : The month as January=1, December=12.
1522 DatetimeIndex.day : The day of the datetime.
1523
1524 Examples
1525 --------
1526 For Series:
1527
1528 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
1529 >>> s = pd.to_datetime(s)
1530 >>> s
1531 0 2020-01-01 10:00:00+00:00
1532 1 2020-02-01 11:00:00+00:00
1533 dtype: datetime64[us, UTC]
1534 >>> s.dt.date
1535 0 2020-01-01
1536 1 2020-02-01
1537 dtype: object
1538
1539 For DatetimeIndex:
1540
1541 >>> idx = pd.DatetimeIndex(
1542 ... ["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]
1543 ... )
1544 >>> idx.date
1545 array([datetime.date(2020, 1, 1), datetime.date(2020, 2, 1)], dtype=object)
1546 """
1547 # If the Timestamps have a timezone that is not UTC,
1548 # convert them into their i8 representation while
1549 # keeping their timezone and not using UTC
1550 timestamps = self._local_timestamps()
1551
1552 return ints_to_pydatetime(timestamps, box="date", reso=self._creso)
1553
1554 def isocalendar(self) -> DataFrame:
1555 """
1556 Calculate year, week, and day according to the ISO 8601 standard.
1557
1558 Returns
1559 -------
1560 DataFrame
1561 With columns year, week and day.
1562
1563 See Also
1564 --------
1565 Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
1566 week number, and weekday for the given Timestamp object.
1567 datetime.date.isocalendar : Return a named tuple object with
1568 three components: year, week and weekday.
1569
1570 Examples
1571 --------
1572 >>> idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
1573 >>> idx.isocalendar()
1574 year week day
1575 2019-12-29 2019 52 7
1576 2019-12-30 2020 1 1
1577 2019-12-31 2020 1 2
1578 2020-01-01 2020 1 3
1579 >>> idx.isocalendar().week
1580 2019-12-29 52
1581 2019-12-30 1
1582 2019-12-31 1
1583 2020-01-01 1
1584 Freq: D, Name: week, dtype: UInt32
1585 """
1586 from pandas import DataFrame
1587
1588 values = self._local_timestamps()
1589 sarray = fields.build_isocalendar_sarray(values, reso=self._creso)
1590 iso_calendar_df = DataFrame(
1591 sarray, columns=["year", "week", "day"], dtype="UInt32"
1592 )
1593 if self._hasna:
1594 iso_calendar_df.iloc[self._isnan] = None
1595 return iso_calendar_df
1596
1597 year = _field_accessor(
1598 "year",
1599 "Y",
1600 """
1601 The year of the datetime.
1602
1603 See Also
1604 --------
1605 DatetimeIndex.month: The month as January=1, December=12.
1606 DatetimeIndex.day: The day of the datetime.
1607
1608 Examples
1609 --------
1610 >>> datetime_series = pd.Series(
1611 ... pd.date_range("2000-01-01", periods=3, freq="YE")
1612 ... )
1613 >>> datetime_series
1614 0 2000-12-31
1615 1 2001-12-31
1616 2 2002-12-31
1617 dtype: datetime64[us]
1618 >>> datetime_series.dt.year
1619 0 2000
1620 1 2001
1621 2 2002
1622 dtype: int32
1623 """,
1624 )
1625 month = _field_accessor(
1626 "month",
1627 "M",
1628 """
1629 The month as January=1, December=12.
1630
1631 See Also
1632 --------
1633 DatetimeIndex.year: The year of the datetime.
1634 DatetimeIndex.day: The day of the datetime.
1635
1636 Examples
1637 --------
1638 >>> datetime_series = pd.Series(
1639 ... pd.date_range("2000-01-01", periods=3, freq="ME")
1640 ... )
1641 >>> datetime_series
1642 0 2000-01-31
1643 1 2000-02-29
1644 2 2000-03-31
1645 dtype: datetime64[us]
1646 >>> datetime_series.dt.month
1647 0 1
1648 1 2
1649 2 3
1650 dtype: int32
1651 """,
1652 )
1653 day = _field_accessor(
1654 "day",
1655 "D",
1656 """
1657 The day of the datetime.
1658
1659 See Also
1660 --------
1661 DatetimeIndex.year: The year of the datetime.
1662 DatetimeIndex.month: The month as January=1, December=12.
1663 DatetimeIndex.hour: The hours of the datetime.
1664
1665 Examples
1666 --------
1667 >>> datetime_series = pd.Series(
1668 ... pd.date_range("2000-01-01", periods=3, freq="D")
1669 ... )
1670 >>> datetime_series
1671 0 2000-01-01
1672 1 2000-01-02
1673 2 2000-01-03
1674 dtype: datetime64[us]
1675 >>> datetime_series.dt.day
1676 0 1
1677 1 2
1678 2 3
1679 dtype: int32
1680 """,
1681 )
1682 hour = _field_accessor(
1683 "hour",
1684 "h",
1685 """
1686 The hours of the datetime.
1687
1688 See Also
1689 --------
1690 DatetimeIndex.day: The day of the datetime.
1691 DatetimeIndex.minute: The minutes of the datetime.
1692 DatetimeIndex.second: The seconds of the datetime.
1693
1694 Examples
1695 --------
1696 >>> datetime_series = pd.Series(
1697 ... pd.date_range("2000-01-01", periods=3, freq="h")
1698 ... )
1699 >>> datetime_series
1700 0 2000-01-01 00:00:00
1701 1 2000-01-01 01:00:00
1702 2 2000-01-01 02:00:00
1703 dtype: datetime64[us]
1704 >>> datetime_series.dt.hour
1705 0 0
1706 1 1
1707 2 2
1708 dtype: int32
1709 """,
1710 )
1711 minute = _field_accessor(
1712 "minute",
1713 "m",
1714 """
1715 The minutes of the datetime.
1716
1717 See Also
1718 --------
1719 DatetimeIndex.hour: The hours of the datetime.
1720 DatetimeIndex.second: The seconds of the datetime.
1721
1722 Examples
1723 --------
1724 >>> datetime_series = pd.Series(
1725 ... pd.date_range("2000-01-01", periods=3, freq="min")
1726 ... )
1727 >>> datetime_series
1728 0 2000-01-01 00:00:00
1729 1 2000-01-01 00:01:00
1730 2 2000-01-01 00:02:00
1731 dtype: datetime64[us]
1732 >>> datetime_series.dt.minute
1733 0 0
1734 1 1
1735 2 2
1736 dtype: int32
1737 """,
1738 )
1739 second = _field_accessor(
1740 "second",
1741 "s",
1742 """
1743 The seconds of the datetime.
1744
1745 See Also
1746 --------
1747 DatetimeIndex.minute: The minutes of the datetime.
1748 DatetimeIndex.microsecond: The microseconds of the datetime.
1749 DatetimeIndex.nanosecond: The nanoseconds of the datetime.
1750
1751 Examples
1752 --------
1753 >>> datetime_series = pd.Series(
1754 ... pd.date_range("2000-01-01", periods=3, freq="s")
1755 ... )
1756 >>> datetime_series
1757 0 2000-01-01 00:00:00
1758 1 2000-01-01 00:00:01
1759 2 2000-01-01 00:00:02
1760 dtype: datetime64[us]
1761 >>> datetime_series.dt.second
1762 0 0
1763 1 1
1764 2 2
1765 dtype: int32
1766 """,
1767 )
1768 microsecond = _field_accessor(
1769 "microsecond",
1770 "us",
1771 """
1772 The microseconds of the datetime.
1773
1774 See Also
1775 --------
1776 DatetimeIndex.second: The seconds of the datetime.
1777 DatetimeIndex.nanosecond: The nanoseconds of the datetime.
1778
1779 Examples
1780 --------
1781 >>> datetime_series = pd.Series(
1782 ... pd.date_range("2000-01-01", periods=3, freq="us")
1783 ... )
1784 >>> datetime_series
1785 0 2000-01-01 00:00:00.000000
1786 1 2000-01-01 00:00:00.000001
1787 2 2000-01-01 00:00:00.000002
1788 dtype: datetime64[us]
1789 >>> datetime_series.dt.microsecond
1790 0 0
1791 1 1
1792 2 2
1793 dtype: int32
1794 """,
1795 )
1796 nanosecond = _field_accessor(
1797 "nanosecond",
1798 "ns",
1799 """
1800 The nanoseconds of the datetime.
1801
1802 See Also
1803 --------
1804 DatetimeIndex.second: The seconds of the datetime.
1805 DatetimeIndex.microsecond: The microseconds of the datetime.
1806
1807 Examples
1808 --------
1809 >>> datetime_series = pd.Series(
1810 ... pd.date_range("2000-01-01", periods=3, freq="ns")
1811 ... )
1812 >>> datetime_series
1813 0 2000-01-01 00:00:00.000000000
1814 1 2000-01-01 00:00:00.000000001
1815 2 2000-01-01 00:00:00.000000002
1816 dtype: datetime64[ns]
1817 >>> datetime_series.dt.nanosecond
1818 0 0
1819 1 1
1820 2 2
1821 dtype: int32
1822 """,
1823 )
1824 _dayofweek_doc = """
1825 The day of the week with Monday=0, Sunday=6.
1826
1827 Return the day of the week. It is assumed the week starts on
1828 Monday, which is denoted by 0 and ends on Sunday which is denoted
1829 by 6. This method is available on both Series with datetime
1830 values (using the `dt` accessor) or DatetimeIndex.
1831
1832 Returns
1833 -------
1834 Series or Index
1835 Containing integers indicating the day number.
1836
1837 See Also
1838 --------
1839 Series.dt.dayofweek : Alias.
1840 Series.dt.weekday : Alias.
1841 Series.dt.day_name : Returns the name of the day of the week.
1842
1843 Examples
1844 --------
1845 >>> s = pd.date_range('2016-12-31', '2017-01-08', freq='D').to_series()
1846 >>> s.dt.dayofweek
1847 2016-12-31 5
1848 2017-01-01 6
1849 2017-01-02 0
1850 2017-01-03 1
1851 2017-01-04 2
1852 2017-01-05 3
1853 2017-01-06 4
1854 2017-01-07 5
1855 2017-01-08 6
1856 Freq: D, dtype: int32
1857 """
1858 day_of_week = _field_accessor("day_of_week", "dow", _dayofweek_doc)
1859 dayofweek = day_of_week
1860 weekday = day_of_week
1861
1862 day_of_year = _field_accessor(
1863 "dayofyear",
1864 "doy",
1865 """
1866 The ordinal day of the year.
1867
1868 See Also
1869 --------
1870 DatetimeIndex.dayofweek : The day of the week with Monday=0, Sunday=6.
1871 DatetimeIndex.day : The day of the datetime.
1872
1873 Examples
1874 --------
1875 For Series:
1876
1877 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
1878 >>> s = pd.to_datetime(s)
1879 >>> s
1880 0 2020-01-01 10:00:00+00:00
1881 1 2020-02-01 11:00:00+00:00
1882 dtype: datetime64[us, UTC]
1883 >>> s.dt.dayofyear
1884 0 1
1885 1 32
1886 dtype: int32
1887
1888 For DatetimeIndex:
1889
1890 >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00",
1891 ... "2/1/2020 11:00:00+00:00"])
1892 >>> idx.dayofyear
1893 Index([1, 32], dtype='int32')
1894 """,
1895 )
1896 dayofyear = day_of_year
1897 quarter = _field_accessor(
1898 "quarter",
1899 "q",
1900 """
1901 The quarter of the date.
1902
1903 See Also
1904 --------
1905 DatetimeIndex.snap : Snap time stamps to nearest occurring frequency.
1906 DatetimeIndex.time : Returns numpy array of datetime.time objects.
1907 The time part of the Timestamps.
1908
1909 Examples
1910 --------
1911 For Series:
1912
1913 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "4/1/2020 11:00:00+00:00"])
1914 >>> s = pd.to_datetime(s)
1915 >>> s
1916 0 2020-01-01 10:00:00+00:00
1917 1 2020-04-01 11:00:00+00:00
1918 dtype: datetime64[us, UTC]
1919 >>> s.dt.quarter
1920 0 1
1921 1 2
1922 dtype: int32
1923
1924 For DatetimeIndex:
1925
1926 >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00",
1927 ... "2/1/2020 11:00:00+00:00"])
1928 >>> idx.quarter
1929 Index([1, 1], dtype='int32')
1930 """,
1931 )
1932 days_in_month = _field_accessor(
1933 "days_in_month",
1934 "dim",
1935 """
1936 The number of days in the month.
1937
1938 See Also
1939 --------
1940 Series.dt.day : Return the day of the month.
1941 Series.dt.is_month_end : Return a boolean indicating if the
1942 date is the last day of the month.
1943 Series.dt.is_month_start : Return a boolean indicating if the
1944 date is the first day of the month.
1945 Series.dt.month : Return the month as January=1 through December=12.
1946
1947 Examples
1948 --------
1949 >>> s = pd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
1950 >>> s = pd.to_datetime(s)
1951 >>> s
1952 0 2020-01-01 10:00:00+00:00
1953 1 2020-02-01 11:00:00+00:00
1954 dtype: datetime64[us, UTC]
1955 >>> s.dt.daysinmonth
1956 0 31
1957 1 29
1958 dtype: int32
1959 """,
1960 )
1961 daysinmonth = days_in_month
1962 _is_month_doc = """
1963 Indicates whether the date is the {first_or_last} day of the month.
1964
1965 Returns
1966 -------
1967 Series or array
1968 For Series, returns a Series with boolean values.
1969 For DatetimeIndex, returns a boolean array.
1970
1971 See Also
1972 --------
1973 is_month_start : Return a boolean indicating whether the date
1974 is the first day of the month.
1975 is_month_end : Return a boolean indicating whether the date
1976 is the last day of the month.
1977
1978 Examples
1979 --------
1980 This method is available on Series with datetime values under
1981 the ``.dt`` accessor, and directly on DatetimeIndex.
1982
1983 >>> s = pd.Series(pd.date_range("2018-02-27", periods=3))
1984 >>> s
1985 0 2018-02-27
1986 1 2018-02-28
1987 2 2018-03-01
1988 dtype: datetime64[us]
1989 >>> s.dt.is_month_start
1990 0 False
1991 1 False
1992 2 True
1993 dtype: bool
1994 >>> s.dt.is_month_end
1995 0 False
1996 1 True
1997 2 False
1998 dtype: bool
1999
2000 >>> idx = pd.date_range("2018-02-27", periods=3)
2001 >>> idx.is_month_start
2002 array([False, False, True])
2003 >>> idx.is_month_end
2004 array([False, True, False])
2005 """
2006 is_month_start = _field_accessor(
2007 "is_month_start", "is_month_start", _is_month_doc.format(first_or_last="first")
2008 )
2009
2010 is_month_end = _field_accessor(
2011 "is_month_end", "is_month_end", _is_month_doc.format(first_or_last="last")
2012 )
2013
2014 is_quarter_start = _field_accessor(
2015 "is_quarter_start",
2016 "is_quarter_start",
2017 """
2018 Indicator for whether the date is the first day of a quarter.
2019
2020 Returns
2021 -------
2022 is_quarter_start : Series or DatetimeIndex
2023 The same type as the original data with boolean values. Series will
2024 have the same name and index. DatetimeIndex will have the same
2025 name.
2026
2027 See Also
2028 --------
2029 quarter : Return the quarter of the date.
2030 is_quarter_end : Similar property for indicating the quarter end.
2031
2032 Examples
2033 --------
2034 This method is available on Series with datetime values under
2035 the ``.dt`` accessor, and directly on DatetimeIndex.
2036
2037 >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30",
2038 ... periods=4)})
2039 >>> df.assign(quarter=df.dates.dt.quarter,
2040 ... is_quarter_start=df.dates.dt.is_quarter_start)
2041 dates quarter is_quarter_start
2042 0 2017-03-30 1 False
2043 1 2017-03-31 1 False
2044 2 2017-04-01 2 True
2045 3 2017-04-02 2 False
2046
2047 >>> idx = pd.date_range('2017-03-30', periods=4)
2048 >>> idx
2049 DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'],
2050 dtype='datetime64[us]', freq='D')
2051
2052 >>> idx.is_quarter_start
2053 array([False, False, True, False])
2054 """,
2055 )
2056 is_quarter_end = _field_accessor(
2057 "is_quarter_end",
2058 "is_quarter_end",
2059 """
2060 Indicator for whether the date is the last day of a quarter.
2061
2062 Returns
2063 -------
2064 is_quarter_end : Series or DatetimeIndex
2065 The same type as the original data with boolean values. Series will
2066 have the same name and index. DatetimeIndex will have the same
2067 name.
2068
2069 See Also
2070 --------
2071 quarter : Return the quarter of the date.
2072 is_quarter_start : Similar property indicating the quarter start.
2073
2074 Examples
2075 --------
2076 This method is available on Series with datetime values under
2077 the ``.dt`` accessor, and directly on DatetimeIndex.
2078
2079 >>> df = pd.DataFrame({'dates': pd.date_range("2017-03-30",
2080 ... periods=4)})
2081 >>> df.assign(quarter=df.dates.dt.quarter,
2082 ... is_quarter_end=df.dates.dt.is_quarter_end)
2083 dates quarter is_quarter_end
2084 0 2017-03-30 1 False
2085 1 2017-03-31 1 True
2086 2 2017-04-01 2 False
2087 3 2017-04-02 2 False
2088
2089 >>> idx = pd.date_range('2017-03-30', periods=4)
2090 >>> idx
2091 DatetimeIndex(['2017-03-30', '2017-03-31', '2017-04-01', '2017-04-02'],
2092 dtype='datetime64[us]', freq='D')
2093
2094 >>> idx.is_quarter_end
2095 array([False, True, False, False])
2096 """,
2097 )
2098 is_year_start = _field_accessor(
2099 "is_year_start",
2100 "is_year_start",
2101 """
2102 Indicate whether the date is the first day of a year.
2103
2104 Returns
2105 -------
2106 Series or DatetimeIndex
2107 The same type as the original data with boolean values. Series will
2108 have the same name and index. DatetimeIndex will have the same
2109 name.
2110
2111 See Also
2112 --------
2113 is_year_end : Similar property indicating the last day of the year.
2114
2115 Examples
2116 --------
2117 This method is available on Series with datetime values under
2118 the ``.dt`` accessor, and directly on DatetimeIndex.
2119
2120 >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3))
2121 >>> dates
2122 0 2017-12-30
2123 1 2017-12-31
2124 2 2018-01-01
2125 dtype: datetime64[us]
2126
2127 >>> dates.dt.is_year_start
2128 0 False
2129 1 False
2130 2 True
2131 dtype: bool
2132
2133 >>> idx = pd.date_range("2017-12-30", periods=3)
2134 >>> idx
2135 DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'],
2136 dtype='datetime64[us]', freq='D')
2137
2138 >>> idx.is_year_start
2139 array([False, False, True])
2140
2141 This method, when applied to Series with datetime values under
2142 the ``.dt`` accessor, will lose information about Business offsets.
2143
2144 >>> dates = pd.Series(pd.date_range("2020-10-30", periods=4, freq="BYS"))
2145 >>> dates
2146 0 2021-01-01
2147 1 2022-01-03
2148 2 2023-01-02
2149 3 2024-01-01
2150 dtype: datetime64[us]
2151
2152 >>> dates.dt.is_year_start
2153 0 True
2154 1 False
2155 2 False
2156 3 True
2157 dtype: bool
2158
2159 >>> idx = pd.date_range("2020-10-30", periods=4, freq="BYS")
2160 >>> idx
2161 DatetimeIndex(['2021-01-01', '2022-01-03', '2023-01-02', '2024-01-01'],
2162 dtype='datetime64[us]', freq='BYS-JAN')
2163
2164 >>> idx.is_year_start
2165 array([ True, True, True, True])
2166 """,
2167 )
2168 is_year_end = _field_accessor(
2169 "is_year_end",
2170 "is_year_end",
2171 """
2172 Indicate whether the date is the last day of the year.
2173
2174 Returns
2175 -------
2176 Series or DatetimeIndex
2177 The same type as the original data with boolean values. Series will
2178 have the same name and index. DatetimeIndex will have the same
2179 name.
2180
2181 See Also
2182 --------
2183 is_year_start : Similar property indicating the start of the year.
2184
2185 Examples
2186 --------
2187 This method is available on Series with datetime values under
2188 the ``.dt`` accessor, and directly on DatetimeIndex.
2189
2190 >>> dates = pd.Series(pd.date_range("2017-12-30", periods=3))
2191 >>> dates
2192 0 2017-12-30
2193 1 2017-12-31
2194 2 2018-01-01
2195 dtype: datetime64[us]
2196
2197 >>> dates.dt.is_year_end
2198 0 False
2199 1 True
2200 2 False
2201 dtype: bool
2202
2203 >>> idx = pd.date_range("2017-12-30", periods=3)
2204 >>> idx
2205 DatetimeIndex(['2017-12-30', '2017-12-31', '2018-01-01'],
2206 dtype='datetime64[us]', freq='D')
2207
2208 >>> idx.is_year_end
2209 array([False, True, False])
2210 """,
2211 )
2212 is_leap_year = _field_accessor(
2213 "is_leap_year",
2214 "is_leap_year",
2215 """
2216 Boolean indicator if the date belongs to a leap year.
2217
2218 A leap year is a year, which has 366 days (instead of 365) including
2219 29th of February as an intercalary day.
2220 Leap years are years which are multiples of four with the exception
2221 of years divisible by 100 but not by 400.
2222
2223 Returns
2224 -------
2225 Series or ndarray
2226 Booleans indicating if dates belong to a leap year.
2227
2228 See Also
2229 --------
2230 DatetimeIndex.is_year_end : Indicate whether the date is the
2231 last day of the year.
2232 DatetimeIndex.is_year_start : Indicate whether the date is the first
2233 day of a year.
2234
2235 Examples
2236 --------
2237 This method is available on Series with datetime values under
2238 the ``.dt`` accessor, and directly on DatetimeIndex.
2239
2240 >>> idx = pd.date_range("2012-01-01", "2015-01-01", freq="YE")
2241 >>> idx
2242 DatetimeIndex(['2012-12-31', '2013-12-31', '2014-12-31'],
2243 dtype='datetime64[us]', freq='YE-DEC')
2244 >>> idx.is_leap_year
2245 array([ True, False, False])
2246
2247 >>> dates_series = pd.Series(idx)
2248 >>> dates_series
2249 0 2012-12-31
2250 1 2013-12-31
2251 2 2014-12-31
2252 dtype: datetime64[us]
2253 >>> dates_series.dt.is_leap_year
2254 0 True
2255 1 False
2256 2 False
2257 dtype: bool
2258 """,
2259 )
2260
2261 def to_julian_date(self) -> npt.NDArray[np.float64]:
2262 """
2263 Convert TimeStamp to a Julian Date.
2264
2265 This method returns the number of days as a float since noon January 1, 4713 BC.
2266
2267 https://en.wikipedia.org/wiki/Julian_day
2268
2269 Returns
2270 -------
2271 ndarray or Index
2272 Float values that represent each date in Julian Calendar.
2273
2274 See Also
2275 --------
2276 Timestamp.to_julian_date : Equivalent method on ``Timestamp`` objects.
2277
2278 Examples
2279 --------
2280 >>> idx = pd.DatetimeIndex(["2028-08-12 00:54", "2028-08-12 02:06"])
2281 >>> idx.to_julian_date()
2282 Index([2461995.5375, 2461995.5875], dtype='float64')
2283 """
2284
2285 # http://mysite.verizon.net/aesir_research/date/jdalg2.htm
2286 year = np.asarray(self.year)
2287 month = np.asarray(self.month)
2288 day = np.asarray(self.day)
2289 testarr = month < 3
2290 year[testarr] -= 1
2291 month[testarr] += 12
2292 return (
2293 day
2294 + np.trunc((153 * month - 457) / 5)
2295 + 365 * year
2296 + np.floor(year / 4)
2297 - np.floor(year / 100)
2298 + np.floor(year / 400)
2299 + 1_721_118.5
2300 + (
2301 self.hour
2302 + self.minute / 60
2303 + self.second / 3600
2304 + self.microsecond / 3600 / 10**6
2305 + self.nanosecond / 3600 / 10**9
2306 )
2307 / 24
2308 )
2309
2310 # -----------------------------------------------------------------
2311 # Reductions
2312
2313 def _reduce(
2314 self, name: str, *, skipna: bool = True, keepdims: bool = False, **kwargs
2315 ):
2316 result = super()._reduce(name, skipna=skipna, keepdims=keepdims, **kwargs)
2317 if keepdims and isinstance(result, np.ndarray):
2318 if name == "std":
2319 from pandas.core.arrays import TimedeltaArray
2320
2321 return TimedeltaArray._from_sequence(result)
2322 else:
2323 return self._from_sequence(result, dtype=self.dtype)
2324 return result
2325
2326 def std(
2327 self,
2328 axis=None,
2329 dtype=None,
2330 out=None,
2331 ddof: int = 1,
2332 keepdims: bool = False,
2333 skipna: bool = True,
2334 ) -> Timedelta:
2335 """
2336 Return sample standard deviation over requested axis.
2337
2338 Normalized by `N-1` by default. This can be changed using ``ddof``.
2339
2340 Parameters
2341 ----------
2342 axis : int, optional
2343 Axis for the function to be applied on. For :class:`pandas.Series`
2344 this parameter is unused and defaults to ``None``.
2345 dtype : dtype, optional, default None
2346 Type to use in computing the standard deviation. For arrays of
2347 integer type the default is float64, for arrays of float types
2348 it is the same as the array type.
2349 out : ndarray, optional, default None
2350 Alternative output array in which to place the result. It must have
2351 the same shape as the expected output but the type (of the
2352 calculated values) will be cast if necessary.
2353 ddof : int, default 1
2354 Degrees of Freedom. The divisor used in calculations is `N - ddof`,
2355 where `N` represents the number of elements.
2356 keepdims : bool, optional
2357 If this is set to True, the axes which are reduced are left in the
2358 result as dimensions with size one. With this option, the result
2359 will broadcast correctly against the input array. If the default
2360 value is passed, then keepdims will not be passed through to the
2361 std method of sub-classes of ndarray, however any non-default value
2362 will be. If the sub-class method does not implement keepdims any
2363 exceptions will be raised.
2364 skipna : bool, default True
2365 Exclude NA/null values. If an entire row/column is ``NA``, the result
2366 will be ``NA``.
2367
2368 Returns
2369 -------
2370 Timedelta
2371 Standard deviation over requested axis.
2372
2373 See Also
2374 --------
2375 numpy.ndarray.std : Returns the standard deviation of the array elements
2376 along given axis.
2377 Series.std : Return sample standard deviation over requested axis.
2378
2379 Examples
2380 --------
2381 For :class:`pandas.DatetimeIndex`:
2382
2383 >>> idx = pd.date_range("2001-01-01 00:00", periods=3)
2384 >>> idx
2385 DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'],
2386 dtype='datetime64[us]', freq='D')
2387 >>> idx.std()
2388 Timedelta('1 days 00:00:00')
2389 """
2390 # Because std is translation-invariant, we can get self.std
2391 # by calculating (self - Timestamp(0)).std, and we can do it
2392 # without creating a copy by using a view on self._ndarray
2393 from pandas.core.arrays import TimedeltaArray
2394
2395 # Find the td64 dtype with the same resolution as our dt64 dtype
2396 dtype_str = self._ndarray.dtype.name.replace("datetime64", "timedelta64")
2397 dtype = np.dtype(dtype_str)
2398
2399 tda = TimedeltaArray._simple_new(self._ndarray.view(dtype), dtype=dtype)
2400
2401 return tda.std(axis=axis, out=out, ddof=ddof, keepdims=keepdims, skipna=skipna)
2402
2403
2404# -------------------------------------------------------------------
2405# Constructor Helpers
2406
2407
2408def _sequence_to_dt64(
2409 data: ArrayLike,
2410 *,
2411 copy: bool = False,
2412 tz: tzinfo | None = None,
2413 dayfirst: bool = False,
2414 yearfirst: bool = False,
2415 ambiguous: TimeAmbiguous = "raise",
2416 out_unit: str | None = None,
2417) -> tuple[np.ndarray, tzinfo | None]:
2418 """
2419 Parameters
2420 ----------
2421 data : np.ndarray or ExtensionArray
2422 dtl.ensure_arraylike_for_datetimelike has already been called.
2423 copy : bool, default False
2424 tz : tzinfo or None, default None
2425 dayfirst : bool, default False
2426 yearfirst : bool, default False
2427 ambiguous : str, bool, or arraylike, default 'raise'
2428 See pandas._libs.tslibs.tzconversion.tz_localize_to_utc.
2429 out_unit : str or None, default None
2430 Desired output resolution.
2431
2432 Returns
2433 -------
2434 result : numpy.ndarray
2435 The sequence converted to a numpy array with dtype ``datetime64[unit]``.
2436 Where `unit` is "ns" unless specified otherwise by `out_unit`.
2437 tz : tzinfo or None
2438 Either the user-provided tzinfo or one inferred from the data.
2439
2440 Raises
2441 ------
2442 TypeError : PeriodDType data is passed
2443 """
2444
2445 # By this point we are assured to have either a numpy array or Index
2446 data, copy = maybe_convert_dtype(data, copy, tz=tz)
2447 data_dtype = getattr(data, "dtype", None)
2448
2449 out_dtype = DT64NS_DTYPE
2450 if out_unit is not None:
2451 out_dtype = np.dtype(f"M8[{out_unit}]")
2452
2453 if data_dtype == object or is_string_dtype(data_dtype):
2454 # TODO: We do not have tests specific to string-dtypes,
2455 # also complex or categorical or other extension
2456 data = cast(np.ndarray, data)
2457 copy = False
2458 if lib.infer_dtype(data, skipna=False) == "integer":
2459 # Much more performant than going through array_to_datetime
2460 data = data.astype(np.int64)
2461 elif tz is not None and ambiguous == "raise":
2462 obj_data = np.asarray(data, dtype=object)
2463 result = tslib.array_to_datetime_with_tz(
2464 obj_data,
2465 tz=tz,
2466 dayfirst=dayfirst,
2467 yearfirst=yearfirst,
2468 creso=abbrev_to_npy_unit(out_unit),
2469 )
2470 return result, tz
2471 else:
2472 converted, inferred_tz = objects_to_datetime64(
2473 data,
2474 dayfirst=dayfirst,
2475 yearfirst=yearfirst,
2476 allow_object=False,
2477 out_unit=out_unit,
2478 )
2479 copy = False
2480 if tz and inferred_tz:
2481 # two timezones: convert to intended from base UTC repr
2482 # GH#42505 by convention, these are _already_ UTC
2483 result = converted
2484
2485 elif inferred_tz:
2486 tz = inferred_tz
2487 result = converted
2488
2489 else:
2490 result, _ = _construct_from_dt64_naive(
2491 converted, tz=tz, copy=copy, ambiguous=ambiguous
2492 )
2493 return result, tz
2494
2495 data_dtype = data.dtype
2496
2497 # `data` may have originally been a Categorical[datetime64[ns, tz]],
2498 # so we need to handle these types.
2499 if isinstance(data_dtype, DatetimeTZDtype):
2500 # DatetimeArray -> ndarray
2501 data = cast(DatetimeArray, data)
2502 tz = _maybe_infer_tz(tz, data.tz)
2503 result = data._ndarray
2504
2505 elif lib.is_np_dtype(data_dtype, "M"):
2506 # tz-naive DatetimeArray or ndarray[datetime64]
2507 if isinstance(data, DatetimeArray):
2508 data = data._ndarray
2509
2510 data = cast(np.ndarray, data)
2511 result, copy = _construct_from_dt64_naive(
2512 data, tz=tz, copy=copy, ambiguous=ambiguous
2513 )
2514
2515 else:
2516 # must be integer dtype otherwise
2517 # assume this data are epoch timestamps
2518 if data.dtype != INT64_DTYPE:
2519 data = data.astype(np.int64, copy=False)
2520 copy = False
2521 data = cast(np.ndarray, data)
2522 result = data.view(out_dtype)
2523
2524 if copy:
2525 result = result.copy()
2526
2527 assert isinstance(result, np.ndarray), type(result)
2528 assert result.dtype.kind == "M"
2529 assert result.dtype != "M8"
2530 assert is_supported_dtype(result.dtype)
2531 return result, tz
2532
2533
2534def _construct_from_dt64_naive(
2535 data: np.ndarray, *, tz: tzinfo | None, copy: bool, ambiguous: TimeAmbiguous
2536) -> tuple[np.ndarray, bool]:
2537 """
2538 Convert datetime64 data to a supported dtype, localizing if necessary.
2539 """
2540 # Caller is responsible for ensuring
2541 # lib.is_np_dtype(data.dtype)
2542
2543 new_dtype = data.dtype
2544 if not is_supported_dtype(new_dtype):
2545 # Cast to the nearest supported unit, generally "s"
2546 new_dtype = get_supported_dtype(new_dtype)
2547 data = astype_overflowsafe(data, dtype=new_dtype, copy=False)
2548 copy = False
2549
2550 if data.dtype.byteorder == ">":
2551 # TODO: better way to handle this? non-copying alternative?
2552 # without this, test_constructor_datetime64_bigendian fails
2553 data = data.astype(data.dtype.newbyteorder("<"))
2554 new_dtype = data.dtype
2555 copy = False
2556
2557 if tz is not None:
2558 # Convert tz-naive to UTC
2559 # TODO: if tz is UTC, are there situations where we *don't* want a
2560 # copy? tz_localize_to_utc always makes one.
2561 shape = data.shape
2562 if data.ndim > 1:
2563 data = data.ravel()
2564
2565 data_unit = get_unit_from_dtype(new_dtype)
2566 data = tzconversion.tz_localize_to_utc(
2567 data.view("i8"), tz, ambiguous=ambiguous, creso=data_unit
2568 )
2569 data = data.view(new_dtype)
2570 data = data.reshape(shape)
2571
2572 assert data.dtype == new_dtype, data.dtype
2573 result = data
2574
2575 return result, copy
2576
2577
2578def objects_to_datetime64(
2579 data: np.ndarray,
2580 dayfirst,
2581 yearfirst,
2582 utc: bool = False,
2583 errors: DateTimeErrorChoices = "raise",
2584 allow_object: bool = False,
2585 out_unit: str | None = None,
2586) -> tuple[np.ndarray, tzinfo | None]:
2587 """
2588 Convert data to array of timestamps.
2589
2590 Parameters
2591 ----------
2592 data : np.ndarray[object]
2593 dayfirst : bool
2594 yearfirst : bool
2595 utc : bool, default False
2596 Whether to convert/localize timestamps to UTC.
2597 errors : {'raise', 'coerce'}
2598 allow_object : bool
2599 Whether to return an object-dtype ndarray instead of raising if the
2600 data contains more than one timezone.
2601 out_unit : str or None, default None
2602 None indicates we should do resolution inference.
2603
2604 Returns
2605 -------
2606 result : ndarray
2607 np.datetime64[out_unit] if returned values represent wall times or UTC
2608 timestamps.
2609 object if mixed timezones
2610 inferred_tz : tzinfo or None
2611 If not None, then the datetime64 values in `result` denote UTC timestamps.
2612
2613 Raises
2614 ------
2615 ValueError : if data cannot be converted to datetimes
2616 TypeError : When a type cannot be converted to datetime
2617 """
2618 assert errors in ["raise", "coerce"]
2619
2620 # if str-dtype, convert
2621 data = np.asarray(data, dtype=np.object_)
2622
2623 result, tz_parsed = tslib.array_to_datetime(
2624 data,
2625 errors=errors,
2626 utc=utc,
2627 dayfirst=dayfirst,
2628 yearfirst=yearfirst,
2629 creso=abbrev_to_npy_unit(out_unit),
2630 )
2631
2632 if tz_parsed is not None:
2633 # We can take a shortcut since the datetime64 numpy array
2634 # is in UTC
2635 return result, tz_parsed
2636 elif result.dtype.kind == "M":
2637 return result, tz_parsed
2638 elif result.dtype == object:
2639 # GH#23675 when called via `pd.to_datetime`, returning an object-dtype
2640 # array is allowed. When called via `pd.DatetimeIndex`, we can
2641 # only accept datetime64 dtype, so raise TypeError if object-dtype
2642 # is returned, as that indicates the values can be recognized as
2643 # datetimes but they have conflicting timezones/awareness
2644 if allow_object:
2645 return result, tz_parsed
2646 raise TypeError("DatetimeIndex has mixed timezones")
2647 else: # pragma: no cover
2648 # GH#23675 this TypeError should never be hit, whereas the TypeError
2649 # in the object-dtype branch above is reachable.
2650 raise TypeError(result)
2651
2652
2653def maybe_convert_dtype(data, copy: bool, tz: tzinfo | None = None):
2654 """
2655 Convert data based on dtype conventions, issuing
2656 errors where appropriate.
2657
2658 Parameters
2659 ----------
2660 data : np.ndarray or pd.Index
2661 copy : bool
2662 tz : tzinfo or None, default None
2663
2664 Returns
2665 -------
2666 data : np.ndarray or pd.Index
2667 copy : bool
2668
2669 Raises
2670 ------
2671 TypeError : PeriodDType data is passed
2672 """
2673 if not hasattr(data, "dtype"):
2674 # e.g. collections.deque
2675 return data, copy
2676
2677 if is_float_dtype(data.dtype):
2678 # pre-2.0 we treated these as wall-times, inconsistent with ints
2679 # GH#23675, GH#45573 deprecated to treat symmetrically with integer dtypes.
2680 # Note: data.astype(np.int64) fails ARM tests, see
2681 # https://github.com/pandas-dev/pandas/issues/49468.
2682 data = data.astype(DT64NS_DTYPE).view("i8")
2683 copy = False
2684
2685 elif lib.is_np_dtype(data.dtype, "m") or is_bool_dtype(data.dtype):
2686 # GH#29794 enforcing deprecation introduced in GH#23539
2687 raise TypeError(f"dtype {data.dtype} cannot be converted to datetime64[ns]")
2688 elif isinstance(data.dtype, PeriodDtype):
2689 # Note: without explicitly raising here, PeriodIndex
2690 # test_setops.test_join_does_not_recur fails
2691 raise TypeError(
2692 "Passing PeriodDtype data is invalid. Use `data.to_timestamp()` instead"
2693 )
2694
2695 elif isinstance(data.dtype, ExtensionDtype) and not isinstance(
2696 data.dtype, DatetimeTZDtype
2697 ):
2698 # TODO: We have no tests for these
2699 data = np.array(data, dtype=np.object_)
2700 copy = False
2701
2702 return data, copy
2703
2704
2705# -------------------------------------------------------------------
2706# Validation and Inference
2707
2708
2709def _maybe_infer_tz(tz: tzinfo | None, inferred_tz: tzinfo | None) -> tzinfo | None:
2710 """
2711 If a timezone is inferred from data, check that it is compatible with
2712 the user-provided timezone, if any.
2713
2714 Parameters
2715 ----------
2716 tz : tzinfo or None
2717 inferred_tz : tzinfo or None
2718
2719 Returns
2720 -------
2721 tz : tzinfo or None
2722
2723 Raises
2724 ------
2725 TypeError : if both timezones are present but do not match
2726 """
2727 if tz is None:
2728 tz = inferred_tz
2729 elif inferred_tz is None:
2730 pass
2731 elif not timezones.tz_compare(tz, inferred_tz):
2732 raise TypeError(
2733 f"data is already tz-aware {inferred_tz}, unable to set specified tz: {tz}"
2734 )
2735 return tz
2736
2737
2738def _validate_dt64_dtype(dtype):
2739 """
2740 Check that a dtype, if passed, represents either a numpy datetime64[ns]
2741 dtype or a pandas DatetimeTZDtype.
2742
2743 Parameters
2744 ----------
2745 dtype : object
2746
2747 Returns
2748 -------
2749 dtype : None, numpy.dtype, or DatetimeTZDtype
2750
2751 Raises
2752 ------
2753 ValueError : invalid dtype
2754
2755 Notes
2756 -----
2757 Unlike _validate_tz_from_dtype, this does _not_ allow non-existent
2758 tz errors to go through
2759 """
2760 if dtype is not None:
2761 dtype = pandas_dtype(dtype)
2762 if dtype == np.dtype("M8"):
2763 # no precision, disallowed GH#24806
2764 msg = (
2765 "Passing in 'datetime64' dtype with no precision is not allowed. "
2766 "Please pass in 'datetime64[ns]' instead."
2767 )
2768 raise ValueError(msg)
2769
2770 if (
2771 isinstance(dtype, np.dtype)
2772 and (dtype.kind != "M" or not is_supported_dtype(dtype))
2773 ) or not isinstance(dtype, (np.dtype, DatetimeTZDtype)):
2774 raise ValueError(
2775 f"Unexpected value for 'dtype': '{dtype}'. "
2776 "Must be 'datetime64[s]', 'datetime64[ms]', 'datetime64[us]', "
2777 "'datetime64[ns]' or DatetimeTZDtype'."
2778 )
2779
2780 if getattr(dtype, "tz", None):
2781 # https://github.com/pandas-dev/pandas/issues/18595
2782 # Ensure that we have a standard timezone for pytz objects.
2783 # Without this, things like adding an array of timedeltas and
2784 # a tz-aware Timestamp (with a tz specific to its datetime) will
2785 # be incorrect(ish?) for the array as a whole
2786 dtype = cast(DatetimeTZDtype, dtype)
2787 dtype = DatetimeTZDtype(
2788 unit=dtype.unit, tz=timezones.tz_standardize(dtype.tz)
2789 )
2790
2791 return dtype
2792
2793
2794def _validate_tz_from_dtype(
2795 dtype, tz: tzinfo | None, explicit_tz_none: bool = False
2796) -> tzinfo | None:
2797 """
2798 If the given dtype is a DatetimeTZDtype, extract the implied
2799 tzinfo object from it and check that it does not conflict with the given
2800 tz.
2801
2802 Parameters
2803 ----------
2804 dtype : dtype, str
2805 tz : None, tzinfo
2806 explicit_tz_none : bool, default False
2807 Whether tz=None was passed explicitly, as opposed to lib.no_default.
2808
2809 Returns
2810 -------
2811 tz : consensus tzinfo
2812
2813 Raises
2814 ------
2815 ValueError : on tzinfo mismatch
2816 """
2817 if dtype is not None:
2818 if isinstance(dtype, str):
2819 try:
2820 dtype = DatetimeTZDtype.construct_from_string(dtype)
2821 except TypeError:
2822 # Things like `datetime64[ns]`, which is OK for the
2823 # constructors, but also nonsense, which should be validated
2824 # but not by us. We *do* allow non-existent tz errors to
2825 # go through
2826 pass
2827 dtz = getattr(dtype, "tz", None)
2828 if dtz is not None:
2829 if tz is not None and not timezones.tz_compare(tz, dtz):
2830 raise ValueError("cannot supply both a tz and a dtype with a tz")
2831 if explicit_tz_none:
2832 raise ValueError("Cannot pass both a timezone-aware dtype and tz=None")
2833 tz = dtz
2834
2835 if tz is not None and lib.is_np_dtype(dtype, "M"):
2836 # We also need to check for the case where the user passed a
2837 # tz-naive dtype (i.e. datetime64[ns])
2838 if tz is not None and not timezones.tz_compare(tz, dtz):
2839 raise ValueError(
2840 "cannot supply both a tz and a "
2841 "timezone-naive dtype (i.e. datetime64[ns])"
2842 )
2843
2844 return tz
2845
2846
2847def _infer_tz_from_endpoints(
2848 start: Timestamp, end: Timestamp, tz: tzinfo | None
2849) -> tzinfo | None:
2850 """
2851 If a timezone is not explicitly given via `tz`, see if one can
2852 be inferred from the `start` and `end` endpoints. If more than one
2853 of these inputs provides a timezone, require that they all agree.
2854
2855 Parameters
2856 ----------
2857 start : Timestamp
2858 end : Timestamp
2859 tz : tzinfo or None
2860
2861 Returns
2862 -------
2863 tz : tzinfo or None
2864
2865 Raises
2866 ------
2867 TypeError : if start and end timezones do not agree
2868 """
2869 try:
2870 inferred_tz = timezones.infer_tzinfo(start, end)
2871 except AssertionError as err:
2872 # infer_tzinfo raises AssertionError if passed mismatched timezones
2873 raise TypeError(
2874 "Start and end cannot both be tz-aware with different timezones"
2875 ) from err
2876
2877 inferred_tz = timezones.maybe_get_tz(inferred_tz)
2878 tz = timezones.maybe_get_tz(tz)
2879
2880 if tz is not None and inferred_tz is not None:
2881 if not timezones.tz_compare(inferred_tz, tz):
2882 raise AssertionError("Inferred time zone not equal to passed time zone")
2883
2884 elif inferred_tz is not None:
2885 tz = inferred_tz
2886
2887 return tz
2888
2889
2890def _maybe_normalize_endpoints(
2891 start: _TimestampNoneT1, end: _TimestampNoneT2, normalize: bool
2892) -> tuple[_TimestampNoneT1, _TimestampNoneT2]:
2893 if normalize:
2894 if start is not None:
2895 start = start.normalize()
2896
2897 if end is not None:
2898 end = end.normalize()
2899
2900 return start, end
2901
2902
2903def _maybe_localize_point(
2904 ts: Timestamp | None, freq, tz, ambiguous, nonexistent
2905) -> Timestamp | None:
2906 """
2907 Localize a start or end Timestamp to the timezone of the corresponding
2908 start or end Timestamp
2909
2910 Parameters
2911 ----------
2912 ts : start or end Timestamp to potentially localize
2913 freq : Tick, DateOffset, or None
2914 tz : str, timezone object or None
2915 ambiguous: str, localization behavior for ambiguous times
2916 nonexistent: str, localization behavior for nonexistent times
2917
2918 Returns
2919 -------
2920 ts : Timestamp
2921 """
2922 # Make sure start and end are timezone localized if:
2923 # 1) freq = a Timedelta-like frequency (Tick)
2924 # 2) freq = None i.e. generating a linspaced range
2925 if ts is not None and ts.tzinfo is None:
2926 # Note: We can't ambiguous='infer' a singular ambiguous time; however,
2927 # we have historically defaulted ambiguous=False
2928 ambiguous = ambiguous if ambiguous != "infer" else False
2929 localize_args = {"ambiguous": ambiguous, "nonexistent": nonexistent, "tz": None}
2930 if isinstance(freq, Tick) or freq is None:
2931 localize_args["tz"] = tz
2932 ts = ts.tz_localize(**localize_args)
2933 return ts
2934
2935
2936def _generate_range(
2937 start: Timestamp | None,
2938 end: Timestamp | None,
2939 periods: int | None,
2940 offset: BaseOffset,
2941 *,
2942 unit: TimeUnit,
2943) -> Generator[Timestamp]:
2944 """
2945 Generates a sequence of dates corresponding to the specified time
2946 offset. Similar to dateutil.rrule except uses pandas DateOffset
2947 objects to represent time increments.
2948
2949 Parameters
2950 ----------
2951 start : Timestamp or None
2952 end : Timestamp or None
2953 periods : int or None
2954 offset : DateOffset
2955 unit : str
2956
2957 Notes
2958 -----
2959 * This method is faster for generating weekdays than dateutil.rrule
2960 * At least two of (start, end, periods) must be specified.
2961 * If both start and end are specified, the returned dates will
2962 satisfy start <= date <= end.
2963
2964 Returns
2965 -------
2966 dates : generator object
2967 """
2968 offset = to_offset(offset)
2969
2970 # Argument 1 to "Timestamp" has incompatible type "Optional[Timestamp]";
2971 # expected "Union[integer[Any], float, str, date, datetime64]"
2972 start = Timestamp(start) # type: ignore[arg-type]
2973 if start is not NaT:
2974 start = start.as_unit(unit)
2975 else:
2976 start = None
2977
2978 # Argument 1 to "Timestamp" has incompatible type "Optional[Timestamp]";
2979 # expected "Union[integer[Any], float, str, date, datetime64]"
2980 end = Timestamp(end) # type: ignore[arg-type]
2981 if end is not NaT:
2982 end = end.as_unit(unit)
2983 else:
2984 end = None
2985
2986 # GH #64834 FIX for bdate_range regression
2987 if end is not None and periods is not None and not offset.is_on_offset(end):
2988 if offset.n >= 0:
2989 end = offset.rollback(end) # type: ignore[assignment]
2990 else:
2991 end = offset.rollforward(end) # type: ignore[assignment]
2992
2993 if start and not offset.is_on_offset(start):
2994 # Incompatible types in assignment (expression has type "datetime",
2995 # variable has type "Optional[Timestamp]")
2996
2997 # GH #56147 account for negative direction and range bounds
2998 if offset.n >= 0:
2999 start = offset.rollforward(start) # type: ignore[assignment]
3000 else:
3001 start = offset.rollback(start) # type: ignore[assignment]
3002
3003 # Unsupported operand types for < ("Timestamp" and "None")
3004 if periods is None and end < start and offset.n >= 0: # type: ignore[operator]
3005 end = None
3006 periods = 0
3007
3008 if end is None:
3009 # error: No overload variant of "__radd__" of "BaseOffset" matches
3010 # argument type "None"
3011 end = start + (periods - 1) * offset # type: ignore[operator]
3012
3013 if start is None:
3014 # error: No overload variant of "__radd__" of "BaseOffset" matches
3015 # argument type "None"
3016 start = end - (periods - 1) * offset # type: ignore[operator]
3017
3018 start = cast(Timestamp, start)
3019 end = cast(Timestamp, end)
3020
3021 cur = start
3022 if offset.n >= 0:
3023 while cur <= end:
3024 yield cur
3025
3026 if cur == end:
3027 # GH#24252 avoid overflows by not performing the addition
3028 # in offset.apply unless we have to
3029 break
3030
3031 # faster than cur + offset
3032 next_date = offset._apply(cur)
3033 next_date = next_date.as_unit(unit)
3034 if next_date <= cur:
3035 raise ValueError(f"Offset {offset} did not increment date")
3036 cur = next_date
3037 else:
3038 while cur >= end:
3039 yield cur
3040
3041 if cur == end:
3042 # GH#24252 avoid overflows by not performing the addition
3043 # in offset.apply unless we have to
3044 break
3045
3046 # faster than cur + offset
3047 next_date = offset._apply(cur)
3048 next_date = next_date.as_unit(unit)
3049 if next_date >= cur:
3050 raise ValueError(f"Offset {offset} did not decrement date")
3051 cur = next_date