Skip to content

Commit 90cffc5

Browse files
jbrockmendeljreback
authored andcommitted
implement constructors for TimedeltaArray, DatetimeArray (pandas-dev#21803)
1 parent 5cb5880 commit 90cffc5

File tree

8 files changed

+381
-295
lines changed

8 files changed

+381
-295
lines changed

pandas/core/arrays/datetimelike.py

+115-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,37 @@
1818
from pandas.core.algorithms import checked_add_with_arr
1919

2020

21-
class DatetimeLikeArrayMixin(object):
21+
class AttributesMixin(object):
22+
23+
@property
24+
def _attributes(self):
25+
# Inheriting subclass should implement _attributes as a list of strings
26+
from pandas.errors import AbstractMethodError
27+
raise AbstractMethodError(self)
28+
29+
@classmethod
30+
def _simple_new(cls, values, **kwargs):
31+
from pandas.errors import AbstractMethodError
32+
raise AbstractMethodError(cls)
33+
34+
def _get_attributes_dict(self):
35+
"""return an attributes dict for my class"""
36+
return {k: getattr(self, k, None) for k in self._attributes}
37+
38+
def _shallow_copy(self, values=None, **kwargs):
39+
if values is None:
40+
# Note: slightly different from Index implementation which defaults
41+
# to self.values
42+
values = self._ndarray_values
43+
44+
attributes = self._get_attributes_dict()
45+
attributes.update(kwargs)
46+
if not len(values) and 'dtype' not in kwargs:
47+
attributes['dtype'] = self.dtype
48+
return self._simple_new(values, **attributes)
49+
50+
51+
class DatetimeLikeArrayMixin(AttributesMixin):
2252
"""
2353
Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray
2454
@@ -56,9 +86,61 @@ def asi8(self):
5686
# do not cache or you'll create a memory leak
5787
return self.values.view('i8')
5888

89+
# ------------------------------------------------------------------
90+
# Array-like Methods
91+
5992
def __len__(self):
6093
return len(self._data)
6194

95+
def __getitem__(self, key):
96+
"""
97+
This getitem defers to the underlying array, which by-definition can
98+
only handle list-likes, slices, and integer scalars
99+
"""
100+
101+
is_int = lib.is_integer(key)
102+
if lib.is_scalar(key) and not is_int:
103+
raise IndexError("only integers, slices (`:`), ellipsis (`...`), "
104+
"numpy.newaxis (`None`) and integer or boolean "
105+
"arrays are valid indices")
106+
107+
getitem = self._data.__getitem__
108+
if is_int:
109+
val = getitem(key)
110+
return self._box_func(val)
111+
else:
112+
if com.is_bool_indexer(key):
113+
key = np.asarray(key)
114+
if key.all():
115+
key = slice(0, None, None)
116+
else:
117+
key = lib.maybe_booleans_to_slice(key.view(np.uint8))
118+
119+
attribs = self._get_attributes_dict()
120+
121+
is_period = is_period_dtype(self)
122+
if is_period:
123+
freq = self.freq
124+
else:
125+
freq = None
126+
if isinstance(key, slice):
127+
if self.freq is not None and key.step is not None:
128+
freq = key.step * self.freq
129+
else:
130+
freq = self.freq
131+
132+
attribs['freq'] = freq
133+
134+
result = getitem(key)
135+
if result.ndim > 1:
136+
# To support MPL which performs slicing with 2 dim
137+
# even though it only has 1 dim by definition
138+
if is_period:
139+
return self._simple_new(result, **attribs)
140+
return result
141+
142+
return self._simple_new(result, **attribs)
143+
62144
# ------------------------------------------------------------------
63145
# Null Handling
64146

@@ -97,6 +179,27 @@ def _maybe_mask_results(self, result, fill_value=None, convert=None):
97179
result[self._isnan] = fill_value
98180
return result
99181

182+
def _nat_new(self, box=True):
183+
"""
184+
Return Array/Index or ndarray filled with NaT which has the same
185+
length as the caller.
186+
187+
Parameters
188+
----------
189+
box : boolean, default True
190+
- If True returns a Array/Index as the same as caller.
191+
- If False returns ndarray of np.int64.
192+
"""
193+
result = np.zeros(len(self), dtype=np.int64)
194+
result.fill(iNaT)
195+
if not box:
196+
return result
197+
198+
attribs = self._get_attributes_dict()
199+
if not is_period_dtype(self):
200+
attribs['freq'] = None
201+
return self._simple_new(result, **attribs)
202+
100203
# ------------------------------------------------------------------
101204
# Frequency Properties/Methods
102205

@@ -195,6 +298,17 @@ def _add_delta_tdi(self, other):
195298
new_values[mask] = iNaT
196299
return new_values.view('i8')
197300

301+
def _add_nat(self):
302+
"""Add pd.NaT to self"""
303+
if is_period_dtype(self):
304+
raise TypeError('Cannot add {cls} and {typ}'
305+
.format(cls=type(self).__name__,
306+
typ=type(NaT).__name__))
307+
308+
# GH#19124 pd.NaT is treated like a timedelta for both timedelta
309+
# and datetime dtypes
310+
return self._nat_new(box=True)
311+
198312
def _sub_nat(self):
199313
"""Subtract pd.NaT from self"""
200314
# GH#19124 Timedelta - datetime is not in general well-defined.

0 commit comments

Comments
 (0)