Skip to content

Commit 9d94a95

Browse files
jbrockmendeljreback
authored andcommitted
Move constructor helpers to EAMixins (#21845)
1 parent 712a085 commit 9d94a95

File tree

6 files changed

+370
-350
lines changed

6 files changed

+370
-350
lines changed

pandas/core/arrays/datetimes.py

+77-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
from datetime import timedelta
2+
from datetime import datetime, timedelta
33
import warnings
44

55
import numpy as np
@@ -22,6 +22,8 @@
2222
_ensure_int64)
2323
from pandas.core.dtypes.dtypes import DatetimeTZDtype
2424

25+
from pandas.core.algorithms import checked_add_with_arr
26+
2527
from pandas.tseries.frequencies import to_offset, DateOffset
2628
from pandas.tseries.offsets import Tick
2729

@@ -281,6 +283,39 @@ def _add_offset(self, offset):
281283

282284
return type(self)(result, freq='infer')
283285

286+
def _sub_datelike(self, other):
287+
# subtract a datetime from myself, yielding a ndarray[timedelta64[ns]]
288+
if isinstance(other, (DatetimeArrayMixin, np.ndarray)):
289+
if isinstance(other, np.ndarray):
290+
# if other is an ndarray, we assume it is datetime64-dtype
291+
other = type(self)(other)
292+
if not self._has_same_tz(other):
293+
# require tz compat
294+
raise TypeError("{cls} subtraction must have the same "
295+
"timezones or no timezones"
296+
.format(cls=type(self).__name__))
297+
result = self._sub_datelike_dti(other)
298+
elif isinstance(other, (datetime, np.datetime64)):
299+
assert other is not NaT
300+
other = Timestamp(other)
301+
if other is NaT:
302+
return self - NaT
303+
# require tz compat
304+
elif not self._has_same_tz(other):
305+
raise TypeError("Timestamp subtraction must have the same "
306+
"timezones or no timezones")
307+
else:
308+
i8 = self.asi8
309+
result = checked_add_with_arr(i8, -other.value,
310+
arr_mask=self._isnan)
311+
result = self._maybe_mask_results(result,
312+
fill_value=iNaT)
313+
else:
314+
raise TypeError("cannot subtract {cls} and {typ}"
315+
.format(cls=type(self).__name__,
316+
typ=type(other).__name__))
317+
return result.view('timedelta64[ns]')
318+
284319
def _add_delta(self, delta):
285320
"""
286321
Add a timedelta-like, DateOffset, or TimedeltaIndex-like object
@@ -517,6 +552,47 @@ def to_pydatetime(self):
517552
"""
518553
return tslib.ints_to_pydatetime(self.asi8, tz=self.tz)
519554

555+
def normalize(self):
556+
"""
557+
Convert times to midnight.
558+
559+
The time component of the date-time is converted to midnight i.e.
560+
00:00:00. This is useful in cases, when the time does not matter.
561+
Length is unaltered. The timezones are unaffected.
562+
563+
This method is available on Series with datetime values under
564+
the ``.dt`` accessor, and directly on Datetime Array/Index.
565+
566+
Returns
567+
-------
568+
DatetimeArray, DatetimeIndex or Series
569+
The same type as the original data. Series will have the same
570+
name and index. DatetimeIndex will have the same name.
571+
572+
See Also
573+
--------
574+
floor : Floor the datetimes to the specified freq.
575+
ceil : Ceil the datetimes to the specified freq.
576+
round : Round the datetimes to the specified freq.
577+
578+
Examples
579+
--------
580+
>>> idx = pd.DatetimeIndex(start='2014-08-01 10:00', freq='H',
581+
... periods=3, tz='Asia/Calcutta')
582+
>>> idx
583+
DatetimeIndex(['2014-08-01 10:00:00+05:30',
584+
'2014-08-01 11:00:00+05:30',
585+
'2014-08-01 12:00:00+05:30'],
586+
dtype='datetime64[ns, Asia/Calcutta]', freq='H')
587+
>>> idx.normalize()
588+
DatetimeIndex(['2014-08-01 00:00:00+05:30',
589+
'2014-08-01 00:00:00+05:30',
590+
'2014-08-01 00:00:00+05:30'],
591+
dtype='datetime64[ns, Asia/Calcutta]', freq=None)
592+
"""
593+
new_values = conversion.normalize_i8_timestamps(self.asi8, self.tz)
594+
return type(self)(new_values, freq='infer').tz_localize(self.tz)
595+
520596
# -----------------------------------------------------------------
521597
# Properties - Vectorized Timestamp Properties/Methods
522598

pandas/core/arrays/period.py

+122-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pandas._libs.tslib import NaT, iNaT
99
from pandas._libs.tslibs.period import (
1010
Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX,
11-
get_period_field_arr, period_asfreq_arr)
11+
get_period_field_arr, period_asfreq_arr, _quarter_to_myear)
1212
from pandas._libs.tslibs import period as libperiod
1313
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
1414
from pandas._libs.tslibs.fields import isleapyear_arr
@@ -19,6 +19,9 @@
1919
from pandas.core.dtypes.common import (
2020
is_integer_dtype, is_float_dtype, is_period_dtype)
2121
from pandas.core.dtypes.dtypes import PeriodDtype
22+
from pandas.core.dtypes.generic import ABCSeries
23+
24+
import pandas.core.common as com
2225

2326
from pandas.tseries import frequencies
2427
from pandas.tseries.offsets import Tick, DateOffset
@@ -157,6 +160,25 @@ def _from_ordinals(cls, values, freq=None):
157160
result._freq = Period._maybe_convert_freq(freq)
158161
return result
159162

163+
@classmethod
164+
def _generate_range(cls, start, end, periods, freq, fields):
165+
if freq is not None:
166+
freq = Period._maybe_convert_freq(freq)
167+
168+
field_count = len(fields)
169+
if com._count_not_none(start, end) > 0:
170+
if field_count > 0:
171+
raise ValueError('Can either instantiate from fields '
172+
'or endpoints, but not both')
173+
subarr, freq = _get_ordinal_range(start, end, periods, freq)
174+
elif field_count > 0:
175+
subarr, freq = _range_from_fields(freq=freq, **fields)
176+
else:
177+
raise ValueError('Not enough parameters to construct '
178+
'Period range')
179+
180+
return subarr, freq
181+
160182
# --------------------------------------------------------------------
161183
# Vectorized analogues of Period properties
162184

@@ -371,3 +393,102 @@ def _add_comparison_methods(cls):
371393

372394

373395
PeriodArrayMixin._add_comparison_methods()
396+
397+
398+
# -------------------------------------------------------------------
399+
# Constructor Helpers
400+
401+
def _get_ordinal_range(start, end, periods, freq, mult=1):
402+
if com._count_not_none(start, end, periods) != 2:
403+
raise ValueError('Of the three parameters: start, end, and periods, '
404+
'exactly two must be specified')
405+
406+
if freq is not None:
407+
_, mult = frequencies.get_freq_code(freq)
408+
409+
if start is not None:
410+
start = Period(start, freq)
411+
if end is not None:
412+
end = Period(end, freq)
413+
414+
is_start_per = isinstance(start, Period)
415+
is_end_per = isinstance(end, Period)
416+
417+
if is_start_per and is_end_per and start.freq != end.freq:
418+
raise ValueError('start and end must have same freq')
419+
if (start is NaT or end is NaT):
420+
raise ValueError('start and end must not be NaT')
421+
422+
if freq is None:
423+
if is_start_per:
424+
freq = start.freq
425+
elif is_end_per:
426+
freq = end.freq
427+
else: # pragma: no cover
428+
raise ValueError('Could not infer freq from start/end')
429+
430+
if periods is not None:
431+
periods = periods * mult
432+
if start is None:
433+
data = np.arange(end.ordinal - periods + mult,
434+
end.ordinal + 1, mult,
435+
dtype=np.int64)
436+
else:
437+
data = np.arange(start.ordinal, start.ordinal + periods, mult,
438+
dtype=np.int64)
439+
else:
440+
data = np.arange(start.ordinal, end.ordinal + 1, mult, dtype=np.int64)
441+
442+
return data, freq
443+
444+
445+
def _range_from_fields(year=None, month=None, quarter=None, day=None,
446+
hour=None, minute=None, second=None, freq=None):
447+
if hour is None:
448+
hour = 0
449+
if minute is None:
450+
minute = 0
451+
if second is None:
452+
second = 0
453+
if day is None:
454+
day = 1
455+
456+
ordinals = []
457+
458+
if quarter is not None:
459+
if freq is None:
460+
freq = 'Q'
461+
base = frequencies.FreqGroup.FR_QTR
462+
else:
463+
base, mult = frequencies.get_freq_code(freq)
464+
if base != frequencies.FreqGroup.FR_QTR:
465+
raise AssertionError("base must equal FR_QTR")
466+
467+
year, quarter = _make_field_arrays(year, quarter)
468+
for y, q in compat.zip(year, quarter):
469+
y, m = _quarter_to_myear(y, q, freq)
470+
val = libperiod.period_ordinal(y, m, 1, 1, 1, 1, 0, 0, base)
471+
ordinals.append(val)
472+
else:
473+
base, mult = frequencies.get_freq_code(freq)
474+
arrays = _make_field_arrays(year, month, day, hour, minute, second)
475+
for y, mth, d, h, mn, s in compat.zip(*arrays):
476+
ordinals.append(libperiod.period_ordinal(
477+
y, mth, d, h, mn, s, 0, 0, base))
478+
479+
return np.array(ordinals, dtype=np.int64), freq
480+
481+
482+
def _make_field_arrays(*fields):
483+
length = None
484+
for x in fields:
485+
if isinstance(x, (list, np.ndarray, ABCSeries)):
486+
if length is not None and len(x) != length:
487+
raise ValueError('Mismatched Period array lengths')
488+
elif length is None:
489+
length = len(x)
490+
491+
arrays = [np.asarray(x) if isinstance(x, (np.ndarray, list, ABCSeries))
492+
else np.repeat(x, length) for x in fields]
493+
494+
return arrays

0 commit comments

Comments
 (0)