Skip to content

Commit 1722c05

Browse files
authored
REF: move more of Tick into liboffsets._Tick (pandas-dev#34148)
1 parent e66e5c3 commit 1722c05

File tree

4 files changed

+79
-68
lines changed

4 files changed

+79
-68
lines changed

doc/source/reference/offset_frequency.rst

+8
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ Properties
10441044
Tick.nanos
10451045
Tick.normalize
10461046
Tick.rule_code
1047+
Tick.n
10471048

10481049
Methods
10491050
~~~~~~~
@@ -1077,6 +1078,7 @@ Properties
10771078
Day.nanos
10781079
Day.normalize
10791080
Day.rule_code
1081+
Day.n
10801082

10811083
Methods
10821084
~~~~~~~
@@ -1110,6 +1112,7 @@ Properties
11101112
Hour.nanos
11111113
Hour.normalize
11121114
Hour.rule_code
1115+
Hour.n
11131116

11141117
Methods
11151118
~~~~~~~
@@ -1143,6 +1146,7 @@ Properties
11431146
Minute.nanos
11441147
Minute.normalize
11451148
Minute.rule_code
1149+
Minute.n
11461150

11471151
Methods
11481152
~~~~~~~
@@ -1176,6 +1180,7 @@ Properties
11761180
Second.nanos
11771181
Second.normalize
11781182
Second.rule_code
1183+
Second.n
11791184

11801185
Methods
11811186
~~~~~~~
@@ -1209,6 +1214,7 @@ Properties
12091214
Milli.nanos
12101215
Milli.normalize
12111216
Milli.rule_code
1217+
Milli.n
12121218

12131219
Methods
12141220
~~~~~~~
@@ -1242,6 +1248,7 @@ Properties
12421248
Micro.nanos
12431249
Micro.normalize
12441250
Micro.rule_code
1251+
Micro.n
12451252

12461253
Methods
12471254
~~~~~~~
@@ -1275,6 +1282,7 @@ Properties
12751282
Nano.nanos
12761283
Nano.normalize
12771284
Nano.rule_code
1285+
Nano.n
12781286

12791287
Methods
12801288
~~~~~~~

pandas/_libs/tslibs/offsets.pyx

+66-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ from pandas._libs.tslibs.np_datetime cimport (
3535
from pandas._libs.tslibs.timezones cimport utc_pytz as UTC
3636
from pandas._libs.tslibs.tzconversion cimport tz_convert_single
3737

38+
from pandas._libs.tslibs.timedeltas import Timedelta
3839
from pandas._libs.tslibs.timestamps import Timestamp
3940

4041
# ---------------------------------------------------------------------
@@ -649,7 +650,10 @@ class _BaseOffset:
649650

650651
# ------------------------------------------------------------------
651652

652-
def _validate_n(self, n):
653+
# Staticmethod so we can call from _Tick.__init__, will be unnecessary
654+
# once BaseOffset is a cdef class and is inherited by _Tick
655+
@staticmethod
656+
def _validate_n(n):
653657
"""
654658
Require that `n` be an integer.
655659
@@ -766,13 +770,69 @@ cdef class _Tick(ABCTick):
766770
# ensure that reversed-ops with numpy scalars return NotImplemented
767771
__array_priority__ = 1000
768772
_adjust_dst = False
773+
_inc = Timedelta(microseconds=1000)
774+
_prefix = "undefined"
775+
_attributes = frozenset(["n", "normalize"])
776+
777+
cdef readonly:
778+
int64_t n
779+
bint normalize
780+
dict _cache
781+
782+
def __init__(self, n=1, normalize=False):
783+
n = _BaseOffset._validate_n(n)
784+
self.n = n
785+
self.normalize = False
786+
self._cache = {}
787+
if normalize:
788+
# GH#21427
789+
raise ValueError(
790+
"Tick offset with `normalize=True` are not allowed."
791+
)
792+
793+
@property
794+
def delta(self) -> Timedelta:
795+
return self.n * self._inc
796+
797+
@property
798+
def nanos(self) -> int64_t:
799+
return self.delta.value
769800

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

773804
def is_anchored(self) -> bool:
774805
return False
775806

807+
# --------------------------------------------------------------------
808+
# Comparison and Arithmetic Methods
809+
810+
def __eq__(self, other):
811+
if isinstance(other, str):
812+
try:
813+
# GH#23524 if to_offset fails, we are dealing with an
814+
# incomparable type so == is False and != is True
815+
other = to_offset(other)
816+
except ValueError:
817+
# e.g. "infer"
818+
return False
819+
return self.delta == other
820+
821+
def __ne__(self, other):
822+
return not (self == other)
823+
824+
def __le__(self, other):
825+
return self.delta.__le__(other)
826+
827+
def __lt__(self, other):
828+
return self.delta.__lt__(other)
829+
830+
def __ge__(self, other):
831+
return self.delta.__ge__(other)
832+
833+
def __gt__(self, other):
834+
return self.delta.__gt__(other)
835+
776836
def __truediv__(self, other):
777837
if not isinstance(self, _Tick):
778838
# cython semantics mean the args are sometimes swapped
@@ -781,11 +841,15 @@ cdef class _Tick(ABCTick):
781841
result = self.delta.__truediv__(other)
782842
return _wrap_timedelta_result(result)
783843

844+
# --------------------------------------------------------------------
845+
# Pickle Methods
846+
784847
def __reduce__(self):
785848
return (type(self), (self.n,))
786849

787850
def __setstate__(self, state):
788-
object.__setattr__(self, "n", state["n"])
851+
self.n = state["n"]
852+
self.normalize = False
789853

790854

791855
class BusinessMixin:

pandas/tests/scalar/timestamp/test_arithmetic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_overflow_offset_raises(self):
5252
# used to crash, so check for proper overflow exception
5353

5454
stamp = Timestamp("2000/1/1")
55-
offset_overflow = to_offset("D") * 100 ** 25
55+
offset_overflow = to_offset("D") * 100 ** 5
5656

5757
with pytest.raises(OverflowError, match=msg):
5858
stamp + offset_overflow

pandas/tseries/offsets.py

+4-65
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import date, datetime, timedelta
22
import operator
3-
from typing import Any, Optional
3+
from typing import Optional
44

55
from dateutil.easter import easter
66
import numpy as np
@@ -2134,35 +2134,7 @@ def is_on_offset(self, dt: datetime) -> bool:
21342134
# Ticks
21352135

21362136

2137-
def _tick_comp(op):
2138-
"""
2139-
Tick comparisons should behave identically to Timedelta comparisons.
2140-
"""
2141-
2142-
def f(self, other):
2143-
return op(self.delta, other)
2144-
2145-
f.__name__ = f"__{op.__name__}__"
2146-
return f
2147-
2148-
21492137
class Tick(liboffsets._Tick, SingleConstructorOffset):
2150-
_inc = Timedelta(microseconds=1000)
2151-
_prefix = "undefined"
2152-
_attributes = frozenset(["n", "normalize"])
2153-
2154-
def __init__(self, n=1, normalize=False):
2155-
BaseOffset.__init__(self, n, normalize)
2156-
if normalize:
2157-
raise ValueError(
2158-
"Tick offset with `normalize=True` are not allowed."
2159-
) # GH#21427
2160-
2161-
__gt__ = _tick_comp(operator.gt)
2162-
__ge__ = _tick_comp(operator.ge)
2163-
__lt__ = _tick_comp(operator.lt)
2164-
__le__ = _tick_comp(operator.le)
2165-
21662138
def __add__(self, other):
21672139
if isinstance(other, Tick):
21682140
if type(self) == type(other):
@@ -2180,47 +2152,11 @@ def __add__(self, other):
21802152
f"the add operation between {self} and {other} will overflow"
21812153
) from err
21822154

2183-
def __eq__(self, other: Any) -> bool:
2184-
if isinstance(other, str):
2185-
from pandas.tseries.frequencies import to_offset
2186-
2187-
try:
2188-
# GH#23524 if to_offset fails, we are dealing with an
2189-
# incomparable type so == is False and != is True
2190-
other = to_offset(other)
2191-
except ValueError:
2192-
# e.g. "infer"
2193-
return False
2194-
2195-
return _tick_comp(operator.eq)(self, other)
2196-
21972155
# This is identical to DateOffset.__hash__, but has to be redefined here
21982156
# for Python 3, because we've redefined __eq__.
21992157
def __hash__(self) -> int:
22002158
return hash(self._params)
22012159

2202-
def __ne__(self, other):
2203-
if isinstance(other, str):
2204-
from pandas.tseries.frequencies import to_offset
2205-
2206-
try:
2207-
# GH#23524 if to_offset fails, we are dealing with an
2208-
# incomparable type so == is False and != is True
2209-
other = to_offset(other)
2210-
except ValueError:
2211-
# e.g. "infer"
2212-
return True
2213-
2214-
return _tick_comp(operator.ne)(self, other)
2215-
2216-
@property
2217-
def delta(self) -> Timedelta:
2218-
return self.n * self._inc
2219-
2220-
@property
2221-
def nanos(self):
2222-
return delta_to_nanoseconds(self.delta)
2223-
22242160
def apply(self, other):
22252161
# Timestamp can handle tz and nano sec, thus no need to use apply_wraps
22262162
if isinstance(other, Timestamp):
@@ -2240,6 +2176,9 @@ def apply(self, other):
22402176
if isinstance(other, timedelta):
22412177
return other + self.delta
22422178
elif isinstance(other, type(self)):
2179+
# TODO: this is reached in tests that specifically call apply,
2180+
# but should not be reached "naturally" because __add__ should
2181+
# catch this case first.
22432182
return type(self)(self.n + other.n)
22442183

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

0 commit comments

Comments
 (0)