@@ -6,7 +6,7 @@ import warnings
6
6
from cpython.datetime cimport (PyDateTime_IMPORT,
7
7
PyDateTime_Check,
8
8
PyDelta_Check,
9
- datetime, timedelta,
9
+ datetime, timedelta, date,
10
10
time as dt_time)
11
11
PyDateTime_IMPORT
12
12
@@ -29,12 +29,13 @@ from pandas._libs.tslibs.conversion cimport (
29
29
convert_datetime_to_tsobject,
30
30
localize_pydatetime,
31
31
)
32
- from pandas._libs.tslibs.nattype cimport NPY_NAT
32
+ from pandas._libs.tslibs.nattype cimport NPY_NAT, c_NaT as NaT
33
33
from pandas._libs.tslibs.np_datetime cimport (
34
34
npy_datetimestruct, dtstruct_to_dt64, dt64_to_dtstruct)
35
35
from pandas._libs.tslibs.timezones cimport utc_pytz as UTC
36
36
from pandas._libs.tslibs.tzconversion cimport tz_convert_single
37
37
38
+ from pandas._libs.tslibs.timestamps import Timestamp
38
39
39
40
# ---------------------------------------------------------------------
40
41
# Constants
@@ -154,6 +155,64 @@ def apply_index_wraps(func):
154
155
return wrapper
155
156
156
157
158
+ def apply_wraps (func ):
159
+ # Note: normally we would use `@functools.wraps(func)`, but this does
160
+ # not play nicely with cython class methods
161
+
162
+ def wrapper (self , other ):
163
+ if other is NaT:
164
+ return NaT
165
+ elif isinstance (other, (timedelta, BaseOffset)):
166
+ # timedelta path
167
+ return func(self , other)
168
+ elif isinstance (other, (np.datetime64, datetime, date)):
169
+ other = Timestamp(other)
170
+ else :
171
+ # This will end up returning NotImplemented back in __add__
172
+ raise ApplyTypeError
173
+
174
+ tz = other.tzinfo
175
+ nano = other.nanosecond
176
+
177
+ if self ._adjust_dst:
178
+ other = other.tz_localize(None )
179
+
180
+ result = func(self , other)
181
+
182
+ result = Timestamp(result)
183
+ if self ._adjust_dst:
184
+ result = result.tz_localize(tz)
185
+
186
+ if self .normalize:
187
+ result = result.normalize()
188
+
189
+ # nanosecond may be deleted depending on offset process
190
+ if not self .normalize and nano != 0 :
191
+ if result.nanosecond != nano:
192
+ if result.tz is not None :
193
+ # convert to UTC
194
+ value = result.tz_localize(None ).value
195
+ else :
196
+ value = result.value
197
+ result = Timestamp(value + nano)
198
+
199
+ if tz is not None and result.tzinfo is None :
200
+ result = result.tz_localize(tz)
201
+
202
+ return result
203
+
204
+ # do @functools.wraps(func) manually since it doesn't work on cdef funcs
205
+ wrapper.__name__ = func.__name__
206
+ wrapper.__doc__ = func.__doc__
207
+ try :
208
+ wrapper.__module__ = func.__module__
209
+ except AttributeError :
210
+ # AttributeError: 'method_descriptor' object has no
211
+ # attribute '__module__'
212
+ pass
213
+ return wrapper
214
+
215
+
157
216
cdef _wrap_timedelta_result(result):
158
217
"""
159
218
Tick operations dispatch to their Timedelta counterparts. Wrap the result
@@ -348,6 +407,10 @@ class _BaseOffset:
348
407
_typ = " dateoffset"
349
408
_day_opt = None
350
409
_attributes = frozenset ([' n' , ' normalize' ])
410
+ _use_relativedelta = False
411
+ _adjust_dst = False
412
+ _deprecations = frozenset ([" isAnchored" , " onOffset" ])
413
+ normalize = False # default for prior pickles
351
414
352
415
def __init__ (self , n = 1 , normalize = False ):
353
416
n = self ._validate_n(n)
@@ -503,11 +566,41 @@ class _BaseOffset:
503
566
504
567
# ------------------------------------------------------------------
505
568
569
+ def rollback(self , dt ):
570
+ """
571
+ Roll provided date backward to next offset only if not on offset.
572
+
573
+ Returns
574
+ -------
575
+ TimeStamp
576
+ Rolled timestamp if not on offset, otherwise unchanged timestamp.
577
+ """
578
+ dt = Timestamp(dt)
579
+ if not self .is_on_offset(dt):
580
+ dt = dt - type (self )(1 , normalize = self .normalize, ** self .kwds)
581
+ return dt
582
+
583
+ def rollforward (self , dt ):
584
+ """
585
+ Roll provided date forward to next offset only if not on offset.
586
+
587
+ Returns
588
+ -------
589
+ TimeStamp
590
+ Rolled timestamp if not on offset, otherwise unchanged timestamp.
591
+ """
592
+ dt = Timestamp(dt)
593
+ if not self .is_on_offset(dt):
594
+ dt = dt + type (self )(1 , normalize = self .normalize, ** self .kwds)
595
+ return dt
596
+
506
597
def _get_offset_day (self , datetime other ):
507
598
# subclass must implement `_day_opt`; calling from the base class
508
599
# will raise NotImplementedError.
509
600
return get_day_of_month(other, self ._day_opt)
510
601
602
+ # ------------------------------------------------------------------
603
+
511
604
def _validate_n (self , n ):
512
605
"""
513
606
Require that `n` be an integer.
@@ -620,6 +713,12 @@ cdef class _Tick(ABCTick):
620
713
# ensure that reversed-ops with numpy scalars return NotImplemented
621
714
__array_priority__ = 1000
622
715
716
+ def is_on_offset (self , dt ) -> bool:
717
+ return True
718
+
719
+ def is_anchored(self ) -> bool:
720
+ return False
721
+
623
722
def __truediv__(self , other ):
624
723
if not isinstance (self , _Tick):
625
724
# cython semantics mean the args are sometimes swapped
0 commit comments