Skip to content

REF: move more of Tick into liboffsets._Tick #34148

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

Merged
merged 6 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/source/reference/offset_frequency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ Properties
Tick.nanos
Tick.normalize
Tick.rule_code
Tick.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1077,6 +1078,7 @@ Properties
Day.nanos
Day.normalize
Day.rule_code
Day.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1110,6 +1112,7 @@ Properties
Hour.nanos
Hour.normalize
Hour.rule_code
Hour.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1143,6 +1146,7 @@ Properties
Minute.nanos
Minute.normalize
Minute.rule_code
Minute.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1176,6 +1180,7 @@ Properties
Second.nanos
Second.normalize
Second.rule_code
Second.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1209,6 +1214,7 @@ Properties
Milli.nanos
Milli.normalize
Milli.rule_code
Milli.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1242,6 +1248,7 @@ Properties
Micro.nanos
Micro.normalize
Micro.rule_code
Micro.n

Methods
~~~~~~~
Expand Down Expand Up @@ -1275,6 +1282,7 @@ Properties
Nano.nanos
Nano.normalize
Nano.rule_code
Nano.n

Methods
~~~~~~~
Expand Down
77 changes: 75 additions & 2 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ from pandas._libs.tslibs.np_datetime cimport (
from pandas._libs.tslibs.timezones cimport utc_pytz as UTC
from pandas._libs.tslibs.tzconversion cimport tz_convert_single

from pandas._libs.tslibs.timedeltas import Timedelta
from pandas._libs.tslibs.timestamps import Timestamp

# ---------------------------------------------------------------------
Expand Down Expand Up @@ -643,7 +644,10 @@ class _BaseOffset:

# ------------------------------------------------------------------

def _validate_n(self, n):
# Staticmethod so we can call from _Tick.__init__, will be unnecessary
# once BaseOffset is a cdef class and is inherited by _Tick
@staticmethod
def _validate_n(n):
"""
Require that `n` be an integer.

Expand Down Expand Up @@ -760,13 +764,78 @@ cdef class _Tick(ABCTick):
# ensure that reversed-ops with numpy scalars return NotImplemented
__array_priority__ = 1000
_adjust_dst = False
_inc = Timedelta(microseconds=1000)
_prefix = "undefined"
_attributes = frozenset(["n", "normalize"])

cdef readonly:
int64_t n
bint normalize
dict _cache

def __init__(self, n=1, normalize=False):
n = _BaseOffset._validate_n(n)
self.n = n
self.normalize = False
self._cache = {}
if normalize:
# GH#21427
raise ValueError(
"Tick offset with `normalize=True` are not allowed."
)

@property
def delta(self) -> Timedelta:
return self.n * self._inc

@property
def nanos(self) -> int64_t:
return self.delta.value

def is_on_offset(self, dt) -> bool:
return True

def is_anchored(self) -> bool:
return False

# --------------------------------------------------------------------
# Comparison and Arithmetic Methods

def __eq__(self, other):
if isinstance(other, str):
try:
# GH#23524 if to_offset fails, we are dealing with an
# incomparable type so == is False and != is True
other = to_offset(other)
except ValueError:
# e.g. "infer"
return False
return self.delta == other

def __ne__(self, other):
if isinstance(other, str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this just not self.eq...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, thats how it is in the status quo. ill give it a shot and see if it breaks anything

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt break anything, just pushed with this update

try:
# GH#23524 if to_offset fails, we are dealing with an
# incomparable type so == is False and != is True
other = to_offset(other)
except ValueError:
# e.g. "infer"
return True

return self.delta != other

def __le__(self, other):
return self.delta.__le__(other)

def __lt__(self, other):
return self.delta.__lt__(other)

def __ge__(self, other):
return self.delta.__ge__(other)

def __gt__(self, other):
return self.delta.__gt__(other)

def __truediv__(self, other):
if not isinstance(self, _Tick):
# cython semantics mean the args are sometimes swapped
Expand All @@ -775,11 +844,15 @@ cdef class _Tick(ABCTick):
result = self.delta.__truediv__(other)
return _wrap_timedelta_result(result)

# --------------------------------------------------------------------
# Pickle Methods

def __reduce__(self):
return (type(self), (self.n,))

def __setstate__(self, state):
object.__setattr__(self, "n", state["n"])
self.n = state["n"]
self.normalize = False


class BusinessMixin:
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/scalar/timestamp/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_overflow_offset_raises(self):
# used to crash, so check for proper overflow exception

stamp = Timestamp("2000/1/1")
offset_overflow = to_offset("D") * 100 ** 25
offset_overflow = to_offset("D") * 100 ** 5

with pytest.raises(OverflowError, match=msg):
stamp + offset_overflow
Expand Down
69 changes: 4 additions & 65 deletions pandas/tseries/offsets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import date, datetime, timedelta
import operator
from typing import Any, Optional
from typing import Optional

from dateutil.easter import easter
import numpy as np
Expand Down Expand Up @@ -2134,35 +2134,7 @@ def is_on_offset(self, dt: datetime) -> bool:
# Ticks


def _tick_comp(op):
"""
Tick comparisons should behave identically to Timedelta comparisons.
"""

def f(self, other):
return op(self.delta, other)

f.__name__ = f"__{op.__name__}__"
return f


class Tick(liboffsets._Tick, SingleConstructorOffset):
_inc = Timedelta(microseconds=1000)
_prefix = "undefined"
_attributes = frozenset(["n", "normalize"])

def __init__(self, n=1, normalize=False):
BaseOffset.__init__(self, n, normalize)
if normalize:
raise ValueError(
"Tick offset with `normalize=True` are not allowed."
) # GH#21427

__gt__ = _tick_comp(operator.gt)
__ge__ = _tick_comp(operator.ge)
__lt__ = _tick_comp(operator.lt)
__le__ = _tick_comp(operator.le)

def __add__(self, other):
if isinstance(other, Tick):
if type(self) == type(other):
Expand All @@ -2180,47 +2152,11 @@ def __add__(self, other):
f"the add operation between {self} and {other} will overflow"
) from err

def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
from pandas.tseries.frequencies import to_offset

try:
# GH#23524 if to_offset fails, we are dealing with an
# incomparable type so == is False and != is True
other = to_offset(other)
except ValueError:
# e.g. "infer"
return False

return _tick_comp(operator.eq)(self, other)

# This is identical to DateOffset.__hash__, but has to be redefined here
# for Python 3, because we've redefined __eq__.
def __hash__(self) -> int:
return hash(self._params)

def __ne__(self, other):
if isinstance(other, str):
from pandas.tseries.frequencies import to_offset

try:
# GH#23524 if to_offset fails, we are dealing with an
# incomparable type so == is False and != is True
other = to_offset(other)
except ValueError:
# e.g. "infer"
return True

return _tick_comp(operator.ne)(self, other)

@property
def delta(self) -> Timedelta:
return self.n * self._inc

@property
def nanos(self):
return delta_to_nanoseconds(self.delta)

def apply(self, other):
# Timestamp can handle tz and nano sec, thus no need to use apply_wraps
if isinstance(other, Timestamp):
Expand All @@ -2240,6 +2176,9 @@ def apply(self, other):
if isinstance(other, timedelta):
return other + self.delta
elif isinstance(other, type(self)):
# TODO: this is reached in tests that specifically call apply,
# but should not be reached "naturally" because __add__ should
# catch this case first.
return type(self)(self.n + other.n)

raise ApplyTypeError(f"Unhandled type: {type(other).__name__}")
Expand Down