Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/indexes/datetimelike.py : 25%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Base and utility classes for tseries type pandas objects.
3"""
4import operator
5from typing import List, Optional, Set
7import numpy as np
9from pandas._libs import NaT, iNaT, join as libjoin, lib
10from pandas._libs.algos import unique_deltas
11from pandas._libs.tslibs import timezones
12from pandas.compat.numpy import function as nv
13from pandas.errors import AbstractMethodError
14from pandas.util._decorators import Appender, cache_readonly
16from pandas.core.dtypes.common import (
17 ensure_int64,
18 is_bool_dtype,
19 is_categorical_dtype,
20 is_dtype_equal,
21 is_float,
22 is_integer,
23 is_list_like,
24 is_period_dtype,
25 is_scalar,
26 needs_i8_conversion,
27)
28from pandas.core.dtypes.concat import concat_compat
29from pandas.core.dtypes.generic import ABCIndex, ABCIndexClass, ABCSeries
30from pandas.core.dtypes.missing import isna
32from pandas.core import algorithms
33from pandas.core.accessor import PandasDelegate
34from pandas.core.arrays import DatetimeArray, ExtensionArray, TimedeltaArray
35from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin
36import pandas.core.indexes.base as ibase
37from pandas.core.indexes.base import Index, _index_shared_docs
38from pandas.core.indexes.extension import (
39 ExtensionIndex,
40 inherit_names,
41 make_wrapped_arith_op,
42)
43from pandas.core.indexes.numeric import Int64Index
44from pandas.core.ops import get_op_result_name
45from pandas.core.tools.timedeltas import to_timedelta
47from pandas.tseries.frequencies import DateOffset, to_offset
49_index_doc_kwargs = dict(ibase._index_doc_kwargs)
52def _join_i8_wrapper(joinf, with_indexers: bool = True):
53 """
54 Create the join wrapper methods.
55 """
57 @staticmethod # type: ignore
58 def wrapper(left, right):
59 if isinstance(left, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)):
60 left = left.view("i8")
61 if isinstance(right, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)):
62 right = right.view("i8")
64 results = joinf(left, right)
65 if with_indexers:
66 # dtype should be timedelta64[ns] for TimedeltaIndex
67 # and datetime64[ns] for DatetimeIndex
68 dtype = left.dtype.base
70 join_index, left_indexer, right_indexer = results
71 join_index = join_index.view(dtype)
72 return join_index, left_indexer, right_indexer
73 return results
75 return wrapper
78@inherit_names(
79 ["inferred_freq", "_isnan", "_resolution", "resolution"],
80 DatetimeLikeArrayMixin,
81 cache=True,
82)
83@inherit_names(
84 ["__iter__", "mean", "freq", "freqstr", "_ndarray_values", "asi8", "_box_values"],
85 DatetimeLikeArrayMixin,
86)
87class DatetimeIndexOpsMixin(ExtensionIndex):
88 """
89 Common ops mixin to support a unified interface datetimelike Index.
90 """
92 _data: ExtensionArray
93 freq: Optional[DateOffset]
94 freqstr: Optional[str]
95 _resolution: int
96 _bool_ops: List[str] = []
97 _field_ops: List[str] = []
99 hasnans = cache_readonly(DatetimeLikeArrayMixin._hasnans.fget) # type: ignore
100 _hasnans = hasnans # for index / array -agnostic code
102 @property
103 def is_all_dates(self) -> bool:
104 return True
106 # ------------------------------------------------------------------------
107 # Abstract data attributes
109 @property
110 def values(self):
111 # Note: PeriodArray overrides this to return an ndarray of objects.
112 return self._data._data
114 def __array_wrap__(self, result, context=None):
115 """
116 Gets called after a ufunc.
117 """
118 result = lib.item_from_zerodim(result)
119 if is_bool_dtype(result) or lib.is_scalar(result):
120 return result
122 attrs = self._get_attributes_dict()
123 if not is_period_dtype(self) and attrs["freq"]:
124 # no need to infer if freq is None
125 attrs["freq"] = "infer"
126 return Index(result, **attrs)
128 # ------------------------------------------------------------------------
130 def equals(self, other) -> bool:
131 """
132 Determines if two Index objects contain the same elements.
133 """
134 if self.is_(other):
135 return True
137 if not isinstance(other, ABCIndexClass):
138 return False
139 elif not isinstance(other, type(self)):
140 try:
141 other = type(self)(other)
142 except (ValueError, TypeError, OverflowError):
143 # e.g.
144 # ValueError -> cannot parse str entry, or OutOfBoundsDatetime
145 # TypeError -> trying to convert IntervalIndex to DatetimeIndex
146 # OverflowError -> Index([very_large_timedeltas])
147 return False
149 if not is_dtype_equal(self.dtype, other.dtype):
150 # have different timezone
151 return False
153 return np.array_equal(self.asi8, other.asi8)
155 @Appender(_index_shared_docs["contains"] % _index_doc_kwargs)
156 def __contains__(self, key):
157 try:
158 res = self.get_loc(key)
159 return (
160 is_scalar(res)
161 or isinstance(res, slice)
162 or (is_list_like(res) and len(res))
163 )
164 except (KeyError, TypeError, ValueError):
165 return False
167 def sort_values(self, return_indexer=False, ascending=True):
168 """
169 Return sorted copy of Index.
170 """
171 if return_indexer:
172 _as = self.argsort()
173 if not ascending:
174 _as = _as[::-1]
175 sorted_index = self.take(_as)
176 return sorted_index, _as
177 else:
178 # NB: using asi8 instead of _ndarray_values matters in numpy 1.18
179 # because the treatment of NaT has been changed to put NaT last
180 # instead of first.
181 sorted_values = np.sort(self.asi8)
182 attribs = self._get_attributes_dict()
183 freq = attribs["freq"]
185 if freq is not None and not is_period_dtype(self):
186 if freq.n > 0 and not ascending:
187 freq = freq * -1
188 elif freq.n < 0 and ascending:
189 freq = freq * -1
190 attribs["freq"] = freq
192 if not ascending:
193 sorted_values = sorted_values[::-1]
195 return self._simple_new(sorted_values, **attribs)
197 @Appender(_index_shared_docs["take"] % _index_doc_kwargs)
198 def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs):
199 nv.validate_take(tuple(), kwargs)
200 indices = ensure_int64(indices)
202 maybe_slice = lib.maybe_indices_to_slice(indices, len(self))
203 if isinstance(maybe_slice, slice):
204 return self[maybe_slice]
206 return ExtensionIndex.take(
207 self, indices, axis, allow_fill, fill_value, **kwargs
208 )
210 _can_hold_na = True
212 _na_value = NaT
213 """The expected NA value to use with this index."""
215 def _convert_tolerance(self, tolerance, target):
216 tolerance = np.asarray(to_timedelta(tolerance).to_numpy())
218 if target.size != tolerance.size and tolerance.size > 1:
219 raise ValueError("list-like tolerance size must match target index size")
220 return tolerance
222 def tolist(self) -> List:
223 """
224 Return a list of the underlying data.
225 """
226 return list(self.astype(object))
228 def min(self, axis=None, skipna=True, *args, **kwargs):
229 """
230 Return the minimum value of the Index or minimum along
231 an axis.
233 See Also
234 --------
235 numpy.ndarray.min
236 Series.min : Return the minimum value in a Series.
237 """
238 nv.validate_min(args, kwargs)
239 nv.validate_minmax_axis(axis)
241 if not len(self):
242 return self._na_value
244 i8 = self.asi8
245 try:
246 # quick check
247 if len(i8) and self.is_monotonic:
248 if i8[0] != iNaT:
249 return self._box_func(i8[0])
251 if self.hasnans:
252 if skipna:
253 min_stamp = self[~self._isnan].asi8.min()
254 else:
255 return self._na_value
256 else:
257 min_stamp = i8.min()
258 return self._box_func(min_stamp)
259 except ValueError:
260 return self._na_value
262 def argmin(self, axis=None, skipna=True, *args, **kwargs):
263 """
264 Returns the indices of the minimum values along an axis.
266 See `numpy.ndarray.argmin` for more information on the
267 `axis` parameter.
269 See Also
270 --------
271 numpy.ndarray.argmin
272 """
273 nv.validate_argmin(args, kwargs)
274 nv.validate_minmax_axis(axis)
276 i8 = self.asi8
277 if self.hasnans:
278 mask = self._isnan
279 if mask.all() or not skipna:
280 return -1
281 i8 = i8.copy()
282 i8[mask] = np.iinfo("int64").max
283 return i8.argmin()
285 def max(self, axis=None, skipna=True, *args, **kwargs):
286 """
287 Return the maximum value of the Index or maximum along
288 an axis.
290 See Also
291 --------
292 numpy.ndarray.max
293 Series.max : Return the maximum value in a Series.
294 """
295 nv.validate_max(args, kwargs)
296 nv.validate_minmax_axis(axis)
298 if not len(self):
299 return self._na_value
301 i8 = self.asi8
302 try:
303 # quick check
304 if len(i8) and self.is_monotonic:
305 if i8[-1] != iNaT:
306 return self._box_func(i8[-1])
308 if self.hasnans:
309 if skipna:
310 max_stamp = self[~self._isnan].asi8.max()
311 else:
312 return self._na_value
313 else:
314 max_stamp = i8.max()
315 return self._box_func(max_stamp)
316 except ValueError:
317 return self._na_value
319 def argmax(self, axis=None, skipna=True, *args, **kwargs):
320 """
321 Returns the indices of the maximum values along an axis.
323 See `numpy.ndarray.argmax` for more information on the
324 `axis` parameter.
326 See Also
327 --------
328 numpy.ndarray.argmax
329 """
330 nv.validate_argmax(args, kwargs)
331 nv.validate_minmax_axis(axis)
333 i8 = self.asi8
334 if self.hasnans:
335 mask = self._isnan
336 if mask.all() or not skipna:
337 return -1
338 i8 = i8.copy()
339 i8[mask] = 0
340 return i8.argmax()
342 # --------------------------------------------------------------------
343 # Rendering Methods
345 def _format_with_header(self, header, na_rep="NaT", **kwargs):
346 return header + list(self._format_native_types(na_rep, **kwargs))
348 @property
349 def _formatter_func(self):
350 raise AbstractMethodError(self)
352 def _format_attrs(self):
353 """
354 Return a list of tuples of the (attr,formatted_value).
355 """
356 attrs = super()._format_attrs()
357 for attrib in self._attributes:
358 if attrib == "freq":
359 freq = self.freqstr
360 if freq is not None:
361 freq = repr(freq)
362 attrs.append(("freq", freq))
363 return attrs
365 # --------------------------------------------------------------------
367 def _convert_scalar_indexer(self, key, kind=None):
368 """
369 We don't allow integer or float indexing on datetime-like when using
370 loc.
372 Parameters
373 ----------
374 key : label of the slice bound
375 kind : {'ix', 'loc', 'getitem', 'iloc'} or None
376 """
378 assert kind in ["ix", "loc", "getitem", "iloc", None]
380 # we don't allow integer/float indexing for loc
381 # we don't allow float indexing for ix/getitem
382 if is_scalar(key):
383 is_int = is_integer(key)
384 is_flt = is_float(key)
385 if kind in ["loc"] and (is_int or is_flt):
386 self._invalid_indexer("index", key)
387 elif kind in ["ix", "getitem"] and is_flt:
388 self._invalid_indexer("index", key)
390 return super()._convert_scalar_indexer(key, kind=kind)
392 __add__ = make_wrapped_arith_op("__add__")
393 __radd__ = make_wrapped_arith_op("__radd__")
394 __sub__ = make_wrapped_arith_op("__sub__")
395 __rsub__ = make_wrapped_arith_op("__rsub__")
396 __pow__ = make_wrapped_arith_op("__pow__")
397 __rpow__ = make_wrapped_arith_op("__rpow__")
398 __mul__ = make_wrapped_arith_op("__mul__")
399 __rmul__ = make_wrapped_arith_op("__rmul__")
400 __floordiv__ = make_wrapped_arith_op("__floordiv__")
401 __rfloordiv__ = make_wrapped_arith_op("__rfloordiv__")
402 __mod__ = make_wrapped_arith_op("__mod__")
403 __rmod__ = make_wrapped_arith_op("__rmod__")
404 __divmod__ = make_wrapped_arith_op("__divmod__")
405 __rdivmod__ = make_wrapped_arith_op("__rdivmod__")
406 __truediv__ = make_wrapped_arith_op("__truediv__")
407 __rtruediv__ = make_wrapped_arith_op("__rtruediv__")
409 def isin(self, values, level=None):
410 """
411 Compute boolean array of whether each index value is found in the
412 passed set of values.
414 Parameters
415 ----------
416 values : set or sequence of values
418 Returns
419 -------
420 is_contained : ndarray (boolean dtype)
421 """
422 if level is not None:
423 self._validate_index_level(level)
425 if not isinstance(values, type(self)):
426 try:
427 values = type(self)(values)
428 except ValueError:
429 return self.astype(object).isin(values)
431 return algorithms.isin(self.asi8, values.asi8)
433 @Appender(_index_shared_docs["where"] % _index_doc_kwargs)
434 def where(self, cond, other=None):
435 values = self.view("i8")
437 if is_scalar(other) and isna(other):
438 other = NaT.value
440 else:
441 # Do type inference if necessary up front
442 # e.g. we passed PeriodIndex.values and got an ndarray of Periods
443 other = Index(other)
445 if is_categorical_dtype(other):
446 # e.g. we have a Categorical holding self.dtype
447 if needs_i8_conversion(other.categories):
448 other = other._internal_get_values()
450 if not is_dtype_equal(self.dtype, other.dtype):
451 raise TypeError(f"Where requires matching dtype, not {other.dtype}")
453 other = other.view("i8")
455 result = np.where(cond, values, other).astype("i8")
456 return self._shallow_copy(result)
458 def _summary(self, name=None):
459 """
460 Return a summarized representation.
462 Parameters
463 ----------
464 name : str
465 Name to use in the summary representation.
467 Returns
468 -------
469 str
470 Summarized representation of the index.
471 """
472 formatter = self._formatter_func
473 if len(self) > 0:
474 index_summary = f", {formatter(self[0])} to {formatter(self[-1])}"
475 else:
476 index_summary = ""
478 if name is None:
479 name = type(self).__name__
480 result = f"{name}: {len(self)} entries{index_summary}"
481 if self.freq:
482 result += f"\nFreq: {self.freqstr}"
484 # display as values, not quoted
485 result = result.replace("'", "")
486 return result
488 def _concat_same_dtype(self, to_concat, name):
489 """
490 Concatenate to_concat which has the same class.
491 """
492 attribs = self._get_attributes_dict()
493 attribs["name"] = name
494 # do not pass tz to set because tzlocal cannot be hashed
495 if len({str(x.dtype) for x in to_concat}) != 1:
496 raise ValueError("to_concat must have the same tz")
498 new_data = type(self._values)._concat_same_type(to_concat).asi8
500 # GH 3232: If the concat result is evenly spaced, we can retain the
501 # original frequency
502 is_diff_evenly_spaced = len(unique_deltas(new_data)) == 1
503 if not is_period_dtype(self) and not is_diff_evenly_spaced:
504 # reset freq
505 attribs["freq"] = None
507 return self._simple_new(new_data, **attribs)
509 def shift(self, periods=1, freq=None):
510 """
511 Shift index by desired number of time frequency increments.
513 This method is for shifting the values of datetime-like indexes
514 by a specified time increment a given number of times.
516 Parameters
517 ----------
518 periods : int, default 1
519 Number of periods (or increments) to shift by,
520 can be positive or negative.
522 .. versionchanged:: 0.24.0
524 freq : pandas.DateOffset, pandas.Timedelta or string, optional
525 Frequency increment to shift by.
526 If None, the index is shifted by its own `freq` attribute.
527 Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.
529 Returns
530 -------
531 pandas.DatetimeIndex
532 Shifted index.
534 See Also
535 --------
536 Index.shift : Shift values of Index.
537 PeriodIndex.shift : Shift values of PeriodIndex.
538 """
539 result = self._data._time_shift(periods, freq=freq)
540 return type(self)(result, name=self.name)
542 # --------------------------------------------------------------------
543 # List-like Methods
545 def delete(self, loc):
546 new_i8s = np.delete(self.asi8, loc)
548 freq = None
549 if is_period_dtype(self):
550 freq = self.freq
551 elif is_integer(loc):
552 if loc in (0, -len(self), -1, len(self) - 1):
553 freq = self.freq
554 else:
555 if is_list_like(loc):
556 loc = lib.maybe_indices_to_slice(ensure_int64(np.array(loc)), len(self))
557 if isinstance(loc, slice) and loc.step in (1, None):
558 if loc.start in (0, None) or loc.stop in (len(self), None):
559 freq = self.freq
561 return self._shallow_copy(new_i8s, freq=freq)
564class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, Int64Index):
565 """
566 Mixin class for methods shared by DatetimeIndex and TimedeltaIndex,
567 but not PeriodIndex
568 """
570 # Compat for frequency inference, see GH#23789
571 _is_monotonic_increasing = Index.is_monotonic_increasing
572 _is_monotonic_decreasing = Index.is_monotonic_decreasing
573 _is_unique = Index.is_unique
575 def _set_freq(self, freq):
576 """
577 Set the _freq attribute on our underlying DatetimeArray.
579 Parameters
580 ----------
581 freq : DateOffset, None, or "infer"
582 """
583 # GH#29843
584 if freq is None:
585 # Always valid
586 pass
587 elif len(self) == 0 and isinstance(freq, DateOffset):
588 # Always valid. In the TimedeltaIndex case, we assume this
589 # is a Tick offset.
590 pass
591 else:
592 # As an internal method, we can ensure this assertion always holds
593 assert freq == "infer"
594 freq = to_offset(self.inferred_freq)
596 self._data._freq = freq
598 def _shallow_copy(self, values=None, **kwargs):
599 if values is None:
600 values = self._data
601 if isinstance(values, type(self)):
602 values = values._data
604 attributes = self._get_attributes_dict()
606 if "freq" not in kwargs and self.freq is not None:
607 if isinstance(values, (DatetimeArray, TimedeltaArray)):
608 if values.freq is None:
609 del attributes["freq"]
611 attributes.update(kwargs)
612 return self._simple_new(values, **attributes)
614 # --------------------------------------------------------------------
615 # Set Operation Methods
617 @Appender(Index.difference.__doc__)
618 def difference(self, other, sort=None):
619 new_idx = super().difference(other, sort=sort)
620 new_idx._set_freq(None)
621 return new_idx
623 def intersection(self, other, sort=False):
624 """
625 Specialized intersection for DatetimeIndex/TimedeltaIndex.
627 May be much faster than Index.intersection
629 Parameters
630 ----------
631 other : Same type as self or array-like
632 sort : False or None, default False
633 Sort the resulting index if possible.
635 .. versionadded:: 0.24.0
637 .. versionchanged:: 0.24.1
639 Changed the default to ``False`` to match the behaviour
640 from before 0.24.0.
642 .. versionchanged:: 0.25.0
644 The `sort` keyword is added
646 Returns
647 -------
648 y : Index or same type as self
649 """
650 self._validate_sort_keyword(sort)
651 self._assert_can_do_setop(other)
653 if self.equals(other):
654 return self._get_reconciled_name_object(other)
656 if len(self) == 0:
657 return self.copy()
658 if len(other) == 0:
659 return other.copy()
661 if not isinstance(other, type(self)):
662 result = Index.intersection(self, other, sort=sort)
663 if isinstance(result, type(self)):
664 if result.freq is None:
665 result._set_freq("infer")
666 return result
668 elif (
669 other.freq is None
670 or self.freq is None
671 or other.freq != self.freq
672 or not other.freq.is_anchored()
673 or (not self.is_monotonic or not other.is_monotonic)
674 ):
675 result = Index.intersection(self, other, sort=sort)
677 # Invalidate the freq of `result`, which may not be correct at
678 # this point, depending on the values.
680 result._set_freq(None)
681 result = self._shallow_copy(
682 result._data, name=result.name, dtype=result.dtype, freq=None
683 )
684 if result.freq is None:
685 result._set_freq("infer")
686 return result
688 # to make our life easier, "sort" the two ranges
689 if self[0] <= other[0]:
690 left, right = self, other
691 else:
692 left, right = other, self
694 # after sorting, the intersection always starts with the right index
695 # and ends with the index of which the last elements is smallest
696 end = min(left[-1], right[-1])
697 start = right[0]
699 if end < start:
700 return type(self)(data=[])
701 else:
702 lslice = slice(*left.slice_locs(start, end))
703 left_chunk = left.values[lslice]
704 return self._shallow_copy(left_chunk)
706 def _can_fast_union(self, other) -> bool:
707 if not isinstance(other, type(self)):
708 return False
710 freq = self.freq
712 if freq is None or freq != other.freq:
713 return False
715 if not self.is_monotonic or not other.is_monotonic:
716 return False
718 if len(self) == 0 or len(other) == 0:
719 return True
721 # to make our life easier, "sort" the two ranges
722 if self[0] <= other[0]:
723 left, right = self, other
724 else:
725 left, right = other, self
727 right_start = right[0]
728 left_end = left[-1]
730 # Only need to "adjoin", not overlap
731 try:
732 return (right_start == left_end + freq) or right_start in left
733 except ValueError:
734 # if we are comparing a freq that does not propagate timezones
735 # this will raise
736 return False
738 def _fast_union(self, other, sort=None):
739 if len(other) == 0:
740 return self.view(type(self))
742 if len(self) == 0:
743 return other.view(type(self))
745 # to make our life easier, "sort" the two ranges
746 if self[0] <= other[0]:
747 left, right = self, other
748 elif sort is False:
749 # TDIs are not in the "correct" order and we don't want
750 # to sort but want to remove overlaps
751 left, right = self, other
752 left_start = left[0]
753 loc = right.searchsorted(left_start, side="left")
754 right_chunk = right.values[:loc]
755 dates = concat_compat((left.values, right_chunk))
756 return self._shallow_copy(dates)
757 else:
758 left, right = other, self
760 left_end = left[-1]
761 right_end = right[-1]
763 # concatenate
764 if left_end < right_end:
765 loc = right.searchsorted(left_end, side="right")
766 right_chunk = right.values[loc:]
767 dates = concat_compat((left.values, right_chunk))
768 return self._shallow_copy(dates)
769 else:
770 return left
772 def _union(self, other, sort):
773 if not len(other) or self.equals(other) or not len(self):
774 return super()._union(other, sort=sort)
776 # We are called by `union`, which is responsible for this validation
777 assert isinstance(other, type(self))
779 this, other = self._maybe_utc_convert(other)
781 if this._can_fast_union(other):
782 return this._fast_union(other, sort=sort)
783 else:
784 result = Index._union(this, other, sort=sort)
785 if isinstance(result, type(self)):
786 assert result._data.dtype == this.dtype
787 if result.freq is None:
788 result._set_freq("infer")
789 return result
791 # --------------------------------------------------------------------
792 # Join Methods
793 _join_precedence = 10
795 _inner_indexer = _join_i8_wrapper(libjoin.inner_join_indexer)
796 _outer_indexer = _join_i8_wrapper(libjoin.outer_join_indexer)
797 _left_indexer = _join_i8_wrapper(libjoin.left_join_indexer)
798 _left_indexer_unique = _join_i8_wrapper(
799 libjoin.left_join_indexer_unique, with_indexers=False
800 )
802 def join(
803 self, other, how: str = "left", level=None, return_indexers=False, sort=False
804 ):
805 """
806 See Index.join
807 """
808 if self._is_convertible_to_index_for_join(other):
809 try:
810 other = type(self)(other)
811 except (TypeError, ValueError):
812 pass
814 this, other = self._maybe_utc_convert(other)
815 return Index.join(
816 this,
817 other,
818 how=how,
819 level=level,
820 return_indexers=return_indexers,
821 sort=sort,
822 )
824 def _maybe_utc_convert(self, other):
825 this = self
826 if not hasattr(self, "tz"):
827 return this, other
829 if isinstance(other, type(self)):
830 if self.tz is not None:
831 if other.tz is None:
832 raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
833 elif other.tz is not None:
834 raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
836 if not timezones.tz_compare(self.tz, other.tz):
837 this = self.tz_convert("UTC")
838 other = other.tz_convert("UTC")
839 return this, other
841 @classmethod
842 def _is_convertible_to_index_for_join(cls, other: Index) -> bool:
843 """
844 return a boolean whether I can attempt conversion to a
845 DatetimeIndex/TimedeltaIndex
846 """
847 if isinstance(other, cls):
848 return False
849 elif len(other) > 0 and other.inferred_type not in (
850 "floating",
851 "mixed-integer",
852 "integer",
853 "integer-na",
854 "mixed-integer-float",
855 "mixed",
856 ):
857 return True
858 return False
860 def _wrap_joined_index(self, joined: np.ndarray, other):
861 assert other.dtype == self.dtype, (other.dtype, self.dtype)
862 name = get_op_result_name(self, other)
864 freq = self.freq if self._can_fast_union(other) else None
865 new_data = type(self._data)._simple_new( # type: ignore
866 joined, dtype=self.dtype, freq=freq
867 )
869 return type(self)._simple_new(new_data, name=name)
872class DatetimelikeDelegateMixin(PandasDelegate):
873 """
874 Delegation mechanism, specific for Datetime, Timedelta, and Period types.
876 Functionality is delegated from the Index class to an Array class. A
877 few things can be customized
879 * _delegated_methods, delegated_properties : List
880 The list of property / method names being delagated.
881 * raw_methods : Set
882 The set of methods whose results should should *not* be
883 boxed in an index, after being returned from the array
884 * raw_properties : Set
885 The set of properties whose results should should *not* be
886 boxed in an index, after being returned from the array
887 """
889 # raw_methods : dispatch methods that shouldn't be boxed in an Index
890 _raw_methods: Set[str] = set()
891 # raw_properties : dispatch properties that shouldn't be boxed in an Index
892 _raw_properties: Set[str] = set()
893 _data: ExtensionArray
895 def _delegate_property_get(self, name, *args, **kwargs):
896 result = getattr(self._data, name)
897 if name not in self._raw_properties:
898 result = Index(result, name=self.name)
899 return result
901 def _delegate_property_set(self, name, value, *args, **kwargs):
902 setattr(self._data, name, value)
904 def _delegate_method(self, name, *args, **kwargs):
905 result = operator.methodcaller(name, *args, **kwargs)(self._data)
906 if name not in self._raw_methods:
907 result = Index(result, name=self.name)
908 return result