Skip to content

Commit 01853fc

Browse files
authored
REF: move Year/Month/Quarter offsets to liboffsets (#34346)
1 parent 0e5c9d8 commit 01853fc

File tree

3 files changed

+176
-151
lines changed

3 files changed

+176
-151
lines changed

pandas/_libs/tslibs/offsets.pyx

+163-4
Original file line numberDiff line numberDiff line change
@@ -456,11 +456,12 @@ cdef class BaseOffset:
456456
equality between two DateOffset objects.
457457
"""
458458
# NB: non-cython subclasses override property with cache_readonly
459-
all_paras = self.__dict__.copy()
459+
d = getattr(self, "__dict__", {})
460+
all_paras = d.copy()
460461
all_paras["n"] = self.n
461462
all_paras["normalize"] = self.normalize
462463
for attr in self._attributes:
463-
if hasattr(self, attr) and attr not in self.__dict__:
464+
if hasattr(self, attr) and attr not in d:
464465
# cython attributes are not in __dict__
465466
all_paras[attr] = getattr(self, attr)
466467

@@ -711,6 +712,15 @@ cdef class BaseOffset:
711712

712713
def __setstate__(self, state):
713714
"""Reconstruct an instance from a pickled state"""
715+
if isinstance(self, MonthOffset):
716+
# We can't just override MonthOffset.__setstate__ because of the
717+
# combination of MRO resolution and cython not handling
718+
# multiple inheritance nicely for cdef classes.
719+
state.pop("_use_relativedelta", False)
720+
state.pop("offset", None)
721+
state.pop("_offset", None)
722+
state.pop("kwds", {})
723+
714724
if 'offset' in state:
715725
# Older (<0.22.0) versions have offset attribute instead of _offset
716726
if '_offset' in state: # pragma: no cover
@@ -728,6 +738,12 @@ cdef class BaseOffset:
728738
self.n = state.pop("n")
729739
self.normalize = state.pop("normalize")
730740
self._cache = state.pop("_cache", {})
741+
742+
if not len(state):
743+
# FIXME: kludge because some classes no longer have a __dict__,
744+
# so we need to short-circuit before raising on the next line
745+
return
746+
731747
self.__dict__.update(state)
732748

733749
if 'weekmask' in state and 'holidays' in state:
@@ -739,7 +755,7 @@ cdef class BaseOffset:
739755

740756
def __getstate__(self):
741757
"""Return a pickleable state"""
742-
state = self.__dict__.copy()
758+
state = getattr(self, "__dict__", {}).copy()
743759
state["n"] = self.n
744760
state["normalize"] = self.normalize
745761

@@ -1020,8 +1036,11 @@ cdef class BusinessMixin(SingleConstructorOffset):
10201036

10211037
cpdef __setstate__(self, state):
10221038
# We need to use a cdef/cpdef method to set the readonly _offset attribute
1039+
if "_offset" in state:
1040+
self._offset = state.pop("_offset")
1041+
elif "offset" in state:
1042+
self._offset = state.pop("offset")
10231043
BaseOffset.__setstate__(self, state)
1024-
self._offset = state["_offset"]
10251044

10261045

10271046
class BusinessHourMixin(BusinessMixin):
@@ -1180,6 +1199,7 @@ class WeekOfMonthMixin(SingleConstructorOffset):
11801199

11811200

11821201
# ----------------------------------------------------------------------
1202+
# Year-Based Offset Classes
11831203

11841204
cdef class YearOffset(SingleConstructorOffset):
11851205
"""
@@ -1201,6 +1221,12 @@ cdef class YearOffset(SingleConstructorOffset):
12011221
if month < 1 or month > 12:
12021222
raise ValueError("Month must go from 1 to 12")
12031223

1224+
cpdef __setstate__(self, state):
1225+
self.month = state.pop("month")
1226+
self.n = state.pop("n")
1227+
self.normalize = state.pop("normalize")
1228+
self._cache = {}
1229+
12041230
def __reduce__(self):
12051231
return type(self), (self.n, self.normalize, self.month)
12061232

@@ -1242,6 +1268,51 @@ cdef class YearOffset(SingleConstructorOffset):
12421268
return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype)
12431269

12441270

1271+
cdef class BYearEnd(YearOffset):
1272+
"""
1273+
DateOffset increments between business EOM dates.
1274+
"""
1275+
1276+
_outputName = "BusinessYearEnd"
1277+
_default_month = 12
1278+
_prefix = "BA"
1279+
_day_opt = "business_end"
1280+
1281+
1282+
cdef class BYearBegin(YearOffset):
1283+
"""
1284+
DateOffset increments between business year begin dates.
1285+
"""
1286+
1287+
_outputName = "BusinessYearBegin"
1288+
_default_month = 1
1289+
_prefix = "BAS"
1290+
_day_opt = "business_start"
1291+
1292+
1293+
cdef class YearEnd(YearOffset):
1294+
"""
1295+
DateOffset increments between calendar year ends.
1296+
"""
1297+
1298+
_default_month = 12
1299+
_prefix = "A"
1300+
_day_opt = "end"
1301+
1302+
1303+
cdef class YearBegin(YearOffset):
1304+
"""
1305+
DateOffset increments between calendar year begin dates.
1306+
"""
1307+
1308+
_default_month = 1
1309+
_prefix = "AS"
1310+
_day_opt = "start"
1311+
1312+
1313+
# ----------------------------------------------------------------------
1314+
# Quarter-Based Offset Classes
1315+
12451316
cdef class QuarterOffset(SingleConstructorOffset):
12461317
_attributes = frozenset(["n", "normalize", "startingMonth"])
12471318
# TODO: Consider combining QuarterOffset and YearOffset __init__ at some
@@ -1262,6 +1333,11 @@ cdef class QuarterOffset(SingleConstructorOffset):
12621333
startingMonth = self._default_startingMonth
12631334
self.startingMonth = startingMonth
12641335

1336+
cpdef __setstate__(self, state):
1337+
self.startingMonth = state.pop("startingMonth")
1338+
self.n = state.pop("n")
1339+
self.normalize = state.pop("normalize")
1340+
12651341
def __reduce__(self):
12661342
return type(self), (self.n, self.normalize, self.startingMonth)
12671343

@@ -1311,6 +1387,57 @@ cdef class QuarterOffset(SingleConstructorOffset):
13111387
return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype)
13121388

13131389

1390+
cdef class BQuarterEnd(QuarterOffset):
1391+
"""
1392+
DateOffset increments between business Quarter dates.
1393+
1394+
startingMonth = 1 corresponds to dates like 1/31/2007, 4/30/2007, ...
1395+
startingMonth = 2 corresponds to dates like 2/28/2007, 5/31/2007, ...
1396+
startingMonth = 3 corresponds to dates like 3/30/2007, 6/29/2007, ...
1397+
"""
1398+
_outputName = "BusinessQuarterEnd"
1399+
_default_startingMonth = 3
1400+
_from_name_startingMonth = 12
1401+
_prefix = "BQ"
1402+
_day_opt = "business_end"
1403+
1404+
1405+
# TODO: This is basically the same as BQuarterEnd
1406+
cdef class BQuarterBegin(QuarterOffset):
1407+
_outputName = "BusinessQuarterBegin"
1408+
# I suspect this is wrong for *all* of them.
1409+
# TODO: What does the above comment refer to?
1410+
_default_startingMonth = 3
1411+
_from_name_startingMonth = 1
1412+
_prefix = "BQS"
1413+
_day_opt = "business_start"
1414+
1415+
1416+
cdef class QuarterEnd(QuarterOffset):
1417+
"""
1418+
DateOffset increments between business Quarter dates.
1419+
1420+
startingMonth = 1 corresponds to dates like 1/31/2007, 4/30/2007, ...
1421+
startingMonth = 2 corresponds to dates like 2/28/2007, 5/31/2007, ...
1422+
startingMonth = 3 corresponds to dates like 3/31/2007, 6/30/2007, ...
1423+
"""
1424+
_outputName = "QuarterEnd"
1425+
_default_startingMonth = 3
1426+
_prefix = "Q"
1427+
_day_opt = "end"
1428+
1429+
1430+
cdef class QuarterBegin(QuarterOffset):
1431+
_outputName = "QuarterBegin"
1432+
_default_startingMonth = 3
1433+
_from_name_startingMonth = 1
1434+
_prefix = "QS"
1435+
_day_opt = "start"
1436+
1437+
1438+
# ----------------------------------------------------------------------
1439+
# Month-Based Offset Classes
1440+
13141441
cdef class MonthOffset(SingleConstructorOffset):
13151442
def is_on_offset(self, dt) -> bool:
13161443
if self.normalize and not is_normalized(dt):
@@ -1329,6 +1456,38 @@ cdef class MonthOffset(SingleConstructorOffset):
13291456
return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype)
13301457

13311458

1459+
cdef class MonthEnd(MonthOffset):
1460+
"""
1461+
DateOffset of one month end.
1462+
"""
1463+
_prefix = "M"
1464+
_day_opt = "end"
1465+
1466+
1467+
cdef class MonthBegin(MonthOffset):
1468+
"""
1469+
DateOffset of one month at beginning.
1470+
"""
1471+
_prefix = "MS"
1472+
_day_opt = "start"
1473+
1474+
1475+
cdef class BusinessMonthEnd(MonthOffset):
1476+
"""
1477+
DateOffset increments between business EOM dates.
1478+
"""
1479+
_prefix = "BM"
1480+
_day_opt = "business_end"
1481+
1482+
1483+
cdef class BusinessMonthBegin(MonthOffset):
1484+
"""
1485+
DateOffset of one business month at beginning.
1486+
"""
1487+
_prefix = "BMS"
1488+
_day_opt = "business_start"
1489+
1490+
13321491
# ---------------------------------------------------------------------
13331492
# Special Offset Classes
13341493

pandas/tests/tseries/offsets/test_offsets_properties.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
# enough runtime information (e.g. type hints) to infer how to build them.
6363
gen_yqm_offset = st.one_of(
6464
*map(
65-
st.from_type, # type: ignore
65+
st.from_type,
6666
[
6767
MonthBegin,
6868
MonthEnd,

0 commit comments

Comments
 (0)