-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
REF: De-duplicate pieces of datetimelike arithmetic #23166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
1293941
dc468a7
e51a5fe
858cb08
b96bd93
6ed0694
fd28157
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -425,10 +425,20 @@ def _assert_tzawareness_compat(self, other): | |
# Arithmetic Methods | ||
|
||
def _sub_datelike_dti(self, other): | ||
"""subtraction of two DatetimeIndexes""" | ||
if not len(self) == len(other): | ||
"""subtraction of DatetimeArray/Index or ndarray[datetime64]""" | ||
if len(self) != len(other): | ||
raise ValueError("cannot add indices of unequal length") | ||
|
||
if not isinstance(other, DatetimeArrayMixin): | ||
# i.e. np.ndarray; we assume it is datetime64-dtype | ||
other = type(self)(other) | ||
|
||
if not self._has_same_tz(other): | ||
# require tz compat | ||
raise TypeError("{cls} subtraction must have the same " | ||
"timezones or no timezones" | ||
.format(cls=type(self).__name__)) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The casting and timezone checking used to be done inside the calling function. Cleaner to do it here. |
||
self_i8 = self.asi8 | ||
other_i8 = other.asi8 | ||
new_values = checked_add_with_arr(self_i8, -other_i8, | ||
|
@@ -459,34 +469,22 @@ def _add_offset(self, offset): | |
def _sub_datelike(self, other): | ||
# subtract a datetime from myself, yielding a ndarray[timedelta64[ns]] | ||
if isinstance(other, (DatetimeArrayMixin, np.ndarray)): | ||
if isinstance(other, np.ndarray): | ||
# if other is an ndarray, we assume it is datetime64-dtype | ||
other = type(self)(other) | ||
if not self._has_same_tz(other): | ||
# require tz compat | ||
raise TypeError("{cls} subtraction must have the same " | ||
"timezones or no timezones" | ||
.format(cls=type(self).__name__)) | ||
result = self._sub_datelike_dti(other) | ||
elif isinstance(other, (datetime, np.datetime64)): | ||
assert other is not NaT | ||
other = Timestamp(other) | ||
if other is NaT: | ||
return self - NaT | ||
return self._sub_datelike_dti(other) | ||
|
||
assert isinstance(other, (datetime, np.datetime64)) | ||
assert other is not NaT | ||
other = Timestamp(other) | ||
if other is NaT: | ||
return self - NaT | ||
elif not self._has_same_tz(other): | ||
# require tz compat | ||
elif not self._has_same_tz(other): | ||
raise TypeError("Timestamp subtraction must have the same " | ||
"timezones or no timezones") | ||
else: | ||
i8 = self.asi8 | ||
result = checked_add_with_arr(i8, -other.value, | ||
arr_mask=self._isnan) | ||
result = self._maybe_mask_results(result, | ||
fill_value=iNaT) | ||
else: | ||
raise TypeError("cannot subtract {cls} and {typ}" | ||
.format(cls=type(self).__name__, | ||
typ=type(other).__name__)) | ||
raise TypeError("Timestamp subtraction must have the same " | ||
"timezones or no timezones") | ||
i8 = self.asi8 | ||
result = checked_add_with_arr(i8, -other.value, | ||
arr_mask=self._isnan) | ||
result = self._maybe_mask_results(result, | ||
fill_value=iNaT) | ||
return result.view('timedelta64[ns]') | ||
|
||
def _add_delta(self, delta): | ||
|
@@ -508,22 +506,12 @@ def _add_delta(self, delta): | |
The result's name is set outside of _add_delta by the calling | ||
method (__add__ or __sub__) | ||
""" | ||
from pandas.core.arrays.timedeltas import TimedeltaArrayMixin | ||
|
||
if isinstance(delta, (Tick, timedelta, np.timedelta64)): | ||
new_values = self._add_delta_td(delta) | ||
elif is_timedelta64_dtype(delta): | ||
if not isinstance(delta, TimedeltaArrayMixin): | ||
delta = TimedeltaArrayMixin(delta) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do this inside add_delta_dti |
||
new_values = self._add_delta_tdi(delta) | ||
else: | ||
new_values = self.astype('O') + delta | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not reachable |
||
|
||
tz = 'UTC' if self.tz is not None else None | ||
result = type(self)(new_values, tz=tz, freq='infer') | ||
if self.tz is not None and self.tz is not utc: | ||
result = result.tz_convert(self.tz) | ||
return result | ||
return type(self)(new_values, tz=self.tz, freq='infer') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the more complicated construction above necessary for some reason? AFAICT these are equivalent. |
||
|
||
# ----------------------------------------------------------------- | ||
# Timezone Conversion and Localization Methods | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
|
||
import numpy as np | ||
|
||
from pandas._libs import lib | ||
from pandas._libs.tslib import NaT, iNaT | ||
from pandas._libs.tslibs.period import ( | ||
Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX, | ||
|
@@ -26,7 +25,7 @@ | |
import pandas.core.common as com | ||
|
||
from pandas.tseries import frequencies | ||
from pandas.tseries.offsets import Tick, DateOffset | ||
from pandas.tseries.offsets import Tick | ||
|
||
from pandas.core.arrays import datetimelike as dtl | ||
from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin | ||
|
@@ -404,13 +403,11 @@ def _add_delta(self, other): | |
# i8 view or _shallow_copy | ||
if isinstance(other, (Tick, timedelta, np.timedelta64)): | ||
new_values = self._add_delta_td(other) | ||
return self._shallow_copy(new_values) | ||
elif is_timedelta64_dtype(other): | ||
# ndarray[timedelta64] or TimedeltaArray/index | ||
new_values = self._add_delta_tdi(other) | ||
return self._shallow_copy(new_values) | ||
else: # pragma: no cover | ||
raise TypeError(type(other).__name__) | ||
|
||
return self._shallow_copy(new_values) | ||
|
||
@deprecate_kwarg(old_arg_name='n', new_arg_name='periods') | ||
def shift(self, periods): | ||
|
@@ -445,48 +442,6 @@ def _time_shift(self, n): | |
values[self._isnan] = iNaT | ||
return self._shallow_copy(values=values) | ||
|
||
def _maybe_convert_timedelta(self, other): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since #23031 this is no longer used by the Array class; moved back to the Index class. |
||
""" | ||
Convert timedelta-like input to an integer multiple of self.freq | ||
|
||
Parameters | ||
---------- | ||
other : timedelta, np.timedelta64, DateOffset, int, np.ndarray | ||
|
||
Returns | ||
------- | ||
converted : int, np.ndarray[int64] | ||
|
||
Raises | ||
------ | ||
IncompatibleFrequency : if the input cannot be written as a multiple | ||
of self.freq. Note IncompatibleFrequency subclasses ValueError. | ||
""" | ||
if isinstance( | ||
other, (timedelta, np.timedelta64, Tick, np.ndarray)): | ||
offset = frequencies.to_offset(self.freq.rule_code) | ||
if isinstance(offset, Tick): | ||
# _check_timedeltalike_freq_compat will raise if incompatible | ||
delta = self._check_timedeltalike_freq_compat(other) | ||
return delta | ||
elif isinstance(other, DateOffset): | ||
freqstr = other.rule_code | ||
base = frequencies.get_base_alias(freqstr) | ||
if base == self.freq.rule_code: | ||
return other.n | ||
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) | ||
raise IncompatibleFrequency(msg) | ||
elif lib.is_integer(other): | ||
# integer is passed to .shift via | ||
# _add_datetimelike_methods basically | ||
# but ufunc may pass integer to _add_delta | ||
return other | ||
|
||
# raise when input doesn't have freq | ||
msg = "Input has different freq from {cls}(freq={freqstr})" | ||
raise IncompatibleFrequency(msg.format(cls=type(self).__name__, | ||
freqstr=self.freqstr)) | ||
|
||
def _check_timedeltalike_freq_compat(self, other): | ||
""" | ||
Arithmetic operations with timedelta-like scalars or array `other` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only used once outside of tests; less verbose to do inline.