Skip to content

Commit 695010c

Browse files
committed
Merge remote-tracking branch 'upstream/master' into disown-tz-only-rebased
2 parents 23fd9bb + 41b2b18 commit 695010c

File tree

6 files changed

+194
-83
lines changed

6 files changed

+194
-83
lines changed

pandas/core/arrays/datetimes.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ def wrapper(self, other):
134134
return ops.invalid_comparison(self, other, op)
135135

136136
if is_object_dtype(other):
137-
# We use ops._comp_method_OBJECT_ARRAY to ensure that
138-
# we raise when comparing tz and non-tz arrays
137+
# We have to use _comp_method_OBJECT_ARRAY instead of numpy
138+
# comparison otherwise it would fail to raise when
139+
# comparing tz-aware and tz-naive
139140
with np.errstate(all='ignore'):
140141
result = ops._comp_method_OBJECT_ARRAY(op,
141142
self.astype(object),

pandas/core/indexes/datetimelike.py

+45-48
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,31 @@
3131
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
3232

3333

34+
def ea_passthrough(name):
35+
"""
36+
Make an alias for a method of the underlying ExtensionArray.
37+
38+
Parameters
39+
----------
40+
name : str
41+
42+
Returns
43+
-------
44+
method
45+
"""
46+
def method(self, *args, **kwargs):
47+
return getattr(self._data, name)(*args, **kwargs)
48+
49+
method.__name__ = name
50+
# TODO: docstrings
51+
return method
52+
53+
3454
class DatetimeIndexOpsMixin(ExtensionOpsMixin):
3555
"""
3656
common ops mixin to support a unified interface datetimelike Index
3757
"""
38-
39-
# The underlying Array (DatetimeArray, PeriodArray, TimedeltaArray)
4058
_data = None # type: ExtensionArray
41-
# override DatetimeLikeArrayMixin method
42-
copy = Index.copy
43-
view = Index.view
44-
__setitem__ = Index.__setitem__
4559

4660
# DatetimeLikeArrayMixin assumes subclasses are mutable, so these are
4761
# properties there. They can be made into cache_readonly for Index
@@ -53,11 +67,19 @@ class DatetimeIndexOpsMixin(ExtensionOpsMixin):
5367
_resolution = cache_readonly(DatetimeLikeArrayMixin._resolution.fget)
5468
resolution = cache_readonly(DatetimeLikeArrayMixin.resolution.fget)
5569

70+
_box_values = ea_passthrough("_box_values")
71+
_maybe_mask_results = ea_passthrough("_maybe_mask_results")
72+
__iter__ = ea_passthrough("__iter__")
73+
74+
@property
75+
def freqstr(self):
76+
return self._data.freqstr
77+
5678
def unique(self, level=None):
5779
if level is not None:
5880
self._validate_index_level(level)
5981

60-
result = self._eadata.unique()
82+
result = self._data.unique()
6183

6284
# Note: if `self` is already unique, then self.unique() should share
6385
# a `freq` with self. If not already unique, then self.freq must be
@@ -70,16 +92,13 @@ def _create_comparison_method(cls, op):
7092
Create a comparison method that dispatches to ``cls.values``.
7193
"""
7294
def wrapper(self, other):
73-
result = op(self._eadata, maybe_unwrap_index(other))
95+
result = op(self._data, maybe_unwrap_index(other))
7496
return result
7597

7698
wrapper.__doc__ = op.__doc__
7799
wrapper.__name__ = '__{}__'.format(op.__name__)
78100
return wrapper
79101

80-
# A few methods that are shared
81-
_maybe_mask_results = DatetimeLikeArrayMixin._maybe_mask_results
82-
83102
# ------------------------------------------------------------------------
84103
# Abstract data attributes
85104

@@ -172,7 +191,7 @@ def wrapper(left, right):
172191

173192
@Appender(DatetimeLikeArrayMixin._evaluate_compare.__doc__)
174193
def _evaluate_compare(self, other, op):
175-
result = self._eadata._evaluate_compare(other, op)
194+
result = self._data._evaluate_compare(other, op)
176195
if is_bool_dtype(result):
177196
return result
178197
try:
@@ -499,7 +518,7 @@ def _add_datetimelike_methods(cls):
499518

500519
def __add__(self, other):
501520
# dispatch to ExtensionArray implementation
502-
result = self._eadata.__add__(maybe_unwrap_index(other))
521+
result = self._data.__add__(maybe_unwrap_index(other))
503522
return wrap_arithmetic_op(self, other, result)
504523

505524
cls.__add__ = __add__
@@ -511,13 +530,13 @@ def __radd__(self, other):
511530

512531
def __sub__(self, other):
513532
# dispatch to ExtensionArray implementation
514-
result = self._eadata.__sub__(maybe_unwrap_index(other))
533+
result = self._data.__sub__(maybe_unwrap_index(other))
515534
return wrap_arithmetic_op(self, other, result)
516535

517536
cls.__sub__ = __sub__
518537

519538
def __rsub__(self, other):
520-
result = self._eadata.__rsub__(maybe_unwrap_index(other))
539+
result = self._data.__rsub__(maybe_unwrap_index(other))
521540
return wrap_arithmetic_op(self, other, result)
522541

523542
cls.__rsub__ = __rsub__
@@ -548,7 +567,7 @@ def repeat(self, repeats, axis=None):
548567
nv.validate_repeat(tuple(), dict(axis=axis))
549568
freq = self.freq if is_period_dtype(self) else None
550569
return self._shallow_copy(self.asi8.repeat(repeats), freq=freq)
551-
# TODO: dispatch to _eadata
570+
# TODO: dispatch to _data
552571

553572
@Appender(_index_shared_docs['where'] % _index_doc_kwargs)
554573
def where(self, cond, other=None):
@@ -604,7 +623,8 @@ def _concat_same_dtype(self, to_concat, name):
604623
# reset freq
605624
attribs['freq'] = None
606625

607-
new_data = type(self._values)._concat_same_type(to_concat)
626+
# TODO: Verify that asi8 is what we want.
627+
new_data = type(self._values)._concat_same_type(to_concat).asi8
608628
return self._simple_new(new_data, **attribs)
609629

610630
def _maybe_box_as_values(self, values, **attribs):
@@ -639,40 +659,17 @@ def view(self, dtype=None, type=None):
639659
return result
640660
return self._ndarray_values.view(dtype=dtype)
641661

642-
@deprecate_kwarg(old_arg_name='n', new_arg_name='periods')
643-
def shift(self, periods, freq=None):
644-
"""
645-
Shift index by desired number of increments.
646-
647-
This method is for shifting the values of period indexes
648-
by a specified time increment.
649-
650-
Parameters
651-
----------
652-
periods : int, default 1
653-
Number of periods (or increments) to shift by,
654-
can be positive or negative.
655-
656-
.. versionchanged:: 0.24.0
657-
freq :
658-
659-
Returns
660-
-------
661-
pandas.PeriodIndex
662-
Shifted index.
663-
664-
See Also
665-
--------
666-
DatetimeIndex.shift : Shift values of DatetimeIndex.
667-
"""
668-
new_values = self._data._time_shift(periods, freq=freq)
669-
return self._simple_new(new_values, name=self.name, freq=self.freq)
670-
671662
@Appender(DatetimeLikeArrayMixin._time_shift.__doc__)
672663
def _time_shift(self, periods, freq=None):
673-
result = self._eadata._time_shift(periods, freq=freq)
664+
result = self._data._time_shift(periods, freq=freq)
674665
return type(self)(result, name=self.name)
675666

667+
@deprecate_kwarg(old_arg_name='n', new_arg_name='periods')
668+
@Appender(DatetimeLikeArrayMixin.shift.__doc__)
669+
def shift(self, periods, freq=None):
670+
new_values = self._data.shift(periods, freq=freq)
671+
return self._simple_new(new_values, name=self.name, freq=self.freq)
672+
676673

677674
def wrap_arithmetic_op(self, other, result):
678675
if result is NotImplemented:
@@ -710,7 +707,7 @@ def maybe_unwrap_index(obj):
710707
if isinstance(obj, ABCIndexClass):
711708
if isinstance(obj, DatetimeIndexOpsMixin):
712709
# i.e. PeriodIndex/DatetimeIndex/TimedeltaIndex
713-
return obj._eadata
710+
return obj._data
714711
return obj._data
715712
return obj
716713

pandas/core/indexes/datetimes.py

+93-13
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
import pandas.core.common as com
2828
from pandas.core.indexes.base import Index
2929
from pandas.core.indexes.datetimelike import (
30-
DatelikeIndexMixin, DatetimeIndexOpsMixin, DatetimelikeDelegateMixin)
30+
DatelikeIndexMixin, DatetimeIndexOpsMixin, DatetimelikeDelegateMixin,
31+
ea_passthrough)
3132
from pandas.core.indexes.numeric import Int64Index
3233
from pandas.core.ops import get_op_result_name
3334
import pandas.core.tools.datetimes as tools
@@ -91,11 +92,6 @@ class DatetimeDelegateMixin(DatetimelikeDelegateMixin):
9192
_delegate_class = DatetimeArray
9293

9394

94-
@delegate_names(DatetimeArray, ["to_period", "tz_localize", "tz_convert",
95-
"day_name", "month_name"],
96-
typ="method", overwrite=True)
97-
@delegate_names(DatetimeArray,
98-
DatetimeArray._field_ops, typ="property", overwrite=True)
9995
@delegate_names(DatetimeArray,
10096
DatetimeDelegateMixin._delegated_properties,
10197
typ="property")
@@ -265,6 +261,7 @@ def _join_i8_wrapper(joinf, **kwargs):
265261
_object_ops = DatetimeArray._object_ops
266262
_field_ops = DatetimeArray._field_ops
267263
_datetimelike_ops = DatetimeArray._datetimelike_ops
264+
_datetimelike_methods = DatetimeArray._datetimelike_methods
268265

269266
# --------------------------------------------------------------------
270267
# Constructors
@@ -283,16 +280,16 @@ def __new__(cls, data=None,
283280
verify_integrity = True
284281

285282
if data is None:
286-
result = DatetimeArray._generate_range(
283+
dtarr = DatetimeArray._generate_range(
287284
start, end, periods,
288285
freq=freq, tz=tz, normalize=normalize,
289286
closed=closed, ambiguous=ambiguous)
290287
warnings.warn("Creating a DatetimeIndex by passing range "
291288
"endpoints is deprecated. Use "
292289
"`pandas.date_range` instead.",
293290
FutureWarning, stacklevel=2)
294-
295-
return cls(result, name=name)
291+
return cls._simple_new(
292+
dtarr._data, freq=dtarr.freq, tz=dtarr.tz, name=name)
296293

297294
if is_scalar(data):
298295
raise TypeError("{cls}() must be called with a "
@@ -320,7 +317,11 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
320317
# DatetimeArray._simple_new will accept either i8 or M8[ns] dtypes
321318
values = DatetimeArray._simple_new(values, freq=freq, tz=tz)
322319

323-
result = super(DatetimeIndex, cls)._simple_new(values, freq, tz)
320+
dtarr = DatetimeArray._simple_new(values, freq=freq, tz=tz)
321+
result = object.__new__(cls)
322+
result._data = dtarr._data
323+
result._freq = dtarr.freq
324+
result._tz = dtarr.tz
324325
result.name = name
325326
# For groupby perf. See note in indexes/base about _index_data
326327
result._index_data = values._data
@@ -329,6 +330,32 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
329330

330331
# --------------------------------------------------------------------
331332

333+
@property
334+
def dtype(self):
335+
return self._eadata.dtype
336+
337+
@property
338+
def _values(self):
339+
# tz-naive -> ndarray
340+
# tz-aware -> DatetimeIndex
341+
if self.tz is not None:
342+
return self
343+
else:
344+
return self.values
345+
346+
@property
347+
def tz(self):
348+
# GH 18595
349+
return self._tz
350+
351+
@tz.setter
352+
def tz(self, value):
353+
# GH 3746: Prevent localizing or converting the index by setting tz
354+
raise AttributeError("Cannot directly set timezone. Use tz_localize() "
355+
"or tz_convert() as appropriate")
356+
357+
tzinfo = tz
358+
332359
@property
333360
def size(self):
334361
# TODO: Remove this when we have a DatetimeTZArray
@@ -646,7 +673,7 @@ def intersection(self, other):
646673
def _get_time_micros(self):
647674
values = self.asi8
648675
if self.tz is not None and not timezones.is_utc(self.tz):
649-
values = self._local_timestamps()
676+
values = self._eadata._local_timestamps()
650677
return fields.get_time_micros(values)
651678

652679
def to_series(self, keep_tz=None, index=None, name=None):
@@ -1115,12 +1142,64 @@ def _eadata(self):
11151142
_is_monotonic_increasing = Index.is_monotonic_increasing
11161143
_is_monotonic_decreasing = Index.is_monotonic_decreasing
11171144
_is_unique = Index.is_unique
1118-
astype = DatetimeIndexOpsMixin.astype
11191145

11201146
_timezone = cache_readonly(DatetimeArray._timezone.fget)
11211147
is_normalized = cache_readonly(DatetimeArray.is_normalized.fget)
11221148
_resolution = cache_readonly(DatetimeArray._resolution.fget)
11231149

1150+
strftime = ea_passthrough("strftime")
1151+
_has_same_tz = ea_passthrough("_has_same_tz")
1152+
__array__ = ea_passthrough("__array__")
1153+
1154+
@property
1155+
def offset(self):
1156+
"""
1157+
get/set the frequency of the instance
1158+
"""
1159+
msg = ('{cls}.offset has been deprecated and will be removed '
1160+
'in a future version; use {cls}.freq instead.'
1161+
.format(cls=type(self).__name__))
1162+
warnings.warn(msg, FutureWarning, stacklevel=2)
1163+
return self.freq
1164+
1165+
@offset.setter
1166+
def offset(self, value):
1167+
"""
1168+
get/set the frequency of the instance
1169+
"""
1170+
msg = ('{cls}.offset has been deprecated and will be removed '
1171+
'in a future version; use {cls}.freq instead.'
1172+
.format(cls=type(self).__name__))
1173+
warnings.warn(msg, FutureWarning, stacklevel=2)
1174+
self.freq = value
1175+
1176+
@property
1177+
def freq(self):
1178+
return self._freq
1179+
1180+
@freq.setter
1181+
def freq(self, value):
1182+
if value is not None:
1183+
# let DatetimeArray to validation
1184+
self._eadata.freq = value
1185+
1186+
self._freq = to_offset(value)
1187+
1188+
def __getitem__(self, key):
1189+
result = self._eadata.__getitem__(key)
1190+
if is_scalar(result):
1191+
return result
1192+
elif result.ndim > 1:
1193+
# To support MPL which performs slicing with 2 dim
1194+
# even though it only has 1 dim by definition
1195+
assert isinstance(result, np.ndarray), result
1196+
return result
1197+
return type(self)(result, name=self.name)
1198+
1199+
@property
1200+
def _box_func(self):
1201+
return lambda x: Timestamp(x, tz=self.tz)
1202+
11241203
# --------------------------------------------------------------------
11251204

11261205
@Substitution(klass='DatetimeIndex')
@@ -1462,7 +1541,8 @@ def date_range(start=None, end=None, periods=None, freq=None, tz=None,
14621541
start=start, end=end, periods=periods,
14631542
freq=freq, tz=tz, normalize=normalize,
14641543
closed=closed, **kwargs)
1465-
return DatetimeIndex(dtarr, name=name)
1544+
return DatetimeIndex._simple_new(
1545+
dtarr._data, tz=dtarr.tz, freq=dtarr.freq, name=name)
14661546

14671547

14681548
def bdate_range(start=None, end=None, periods=None, freq='B', tz=None,

0 commit comments

Comments
 (0)