Skip to content

Commit dac9b43

Browse files
jbrockmendelgfyoung
authored andcommitted
CLN: Algebra cleanup in FY5253 and Easter offsets (#18350)
1 parent cf90995 commit dac9b43

File tree

1 file changed

+65
-111
lines changed

1 file changed

+65
-111
lines changed

pandas/tseries/offsets.py

+65-111
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from pandas.core.common import AbstractMethodError
1313

1414
# import after tools, dateutil check
15-
from dateutil.relativedelta import relativedelta, weekday
1615
from dateutil.easter import easter
1716
from pandas._libs import tslib, Timestamp, OutOfBoundsDatetime, Timedelta
1817
from pandas.util._decorators import cache_readonly
@@ -1816,10 +1815,7 @@ class FY5253(DateOffset):
18161815
variation : str
18171816
{"nearest", "last"} for "LastOfMonth" or "NearestEndMonth"
18181817
"""
1819-
18201818
_prefix = 'RE'
1821-
_suffix_prefix_last = 'L'
1822-
_suffix_prefix_nearest = 'N'
18231819
_adjust_dst = True
18241820

18251821
def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1,
@@ -1841,22 +1837,6 @@ def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1,
18411837
raise ValueError('{variation} is not a valid variation'
18421838
.format(variation=self.variation))
18431839

1844-
@cache_readonly
1845-
def _relativedelta_forward(self):
1846-
if self.variation == "nearest":
1847-
weekday_offset = weekday(self.weekday)
1848-
return relativedelta(weekday=weekday_offset)
1849-
else:
1850-
return None
1851-
1852-
@cache_readonly
1853-
def _relativedelta_backward(self):
1854-
if self.variation == "nearest":
1855-
weekday_offset = weekday(self.weekday)
1856-
return relativedelta(weekday=weekday_offset(-1))
1857-
else:
1858-
return None
1859-
18601840
@cache_readonly
18611841
def _offset_lwom(self):
18621842
if self.variation == "nearest":
@@ -1865,9 +1845,9 @@ def _offset_lwom(self):
18651845
return LastWeekOfMonth(n=1, weekday=self.weekday)
18661846

18671847
def isAnchored(self):
1868-
return self.n == 1 \
1869-
and self.startingMonth is not None \
1870-
and self.weekday is not None
1848+
return (self.n == 1 and
1849+
self.startingMonth is not None and
1850+
self.weekday is not None)
18711851

18721852
def onOffset(self, dt):
18731853
if self.normalize and not _is_normalized(dt):
@@ -1891,63 +1871,45 @@ def apply(self, other):
18911871
datetime(other.year, self.startingMonth, 1))
18921872
next_year = self.get_year_end(
18931873
datetime(other.year + 1, self.startingMonth, 1))
1874+
18941875
prev_year = tslib._localize_pydatetime(prev_year, other.tzinfo)
18951876
cur_year = tslib._localize_pydatetime(cur_year, other.tzinfo)
18961877
next_year = tslib._localize_pydatetime(next_year, other.tzinfo)
18971878

1898-
if n > 0:
1899-
if other == prev_year:
1900-
year = other.year - 1
1901-
elif other == cur_year:
1902-
year = other.year
1903-
elif other == next_year:
1904-
year = other.year + 1
1905-
elif other < prev_year:
1906-
year = other.year - 1
1907-
n -= 1
1879+
if other == prev_year:
1880+
n -= 1
1881+
elif other == cur_year:
1882+
pass
1883+
elif other == next_year:
1884+
n += 1
1885+
# TODO: Not hit in tests
1886+
elif n > 0:
1887+
if other < prev_year:
1888+
n -= 2
1889+
# TODO: Not hit in tests
19081890
elif other < cur_year:
1909-
year = other.year
19101891
n -= 1
19111892
elif other < next_year:
1912-
year = other.year + 1
1913-
n -= 1
1893+
pass
19141894
else:
19151895
assert False
1916-
1917-
result = self.get_year_end(
1918-
datetime(year + n, self.startingMonth, 1))
1919-
1920-
result = datetime(result.year, result.month, result.day,
1921-
other.hour, other.minute, other.second,
1922-
other.microsecond)
1923-
return result
19241896
else:
1925-
n = -n
1926-
if other == prev_year:
1927-
year = other.year - 1
1928-
elif other == cur_year:
1929-
year = other.year
1930-
elif other == next_year:
1931-
year = other.year + 1
1932-
elif other > next_year:
1933-
year = other.year + 1
1934-
n -= 1
1897+
if other > next_year:
1898+
n += 2
1899+
# TODO: Not hit in tests
19351900
elif other > cur_year:
1936-
year = other.year
1937-
n -= 1
1901+
n += 1
19381902
elif other > prev_year:
1939-
year = other.year - 1
1940-
n -= 1
1903+
pass
19411904
else:
19421905
assert False
19431906

1944-
result = self.get_year_end(
1945-
datetime(year - n, self.startingMonth, 1))
1946-
1947-
result = datetime(result.year, result.month, result.day,
1948-
other.hour, other.minute, other.second,
1949-
other.microsecond)
1950-
return result
1907+
shifted = datetime(other.year + n, self.startingMonth, 1)
1908+
result = self.get_year_end(shifted)
1909+
result = datetime(result.year, result.month, result.day,
1910+
other.hour, other.minute, other.second,
1911+
other.microsecond)
1912+
return result
19511913

19521914
def get_year_end(self, dt):
19531915
if self.variation == "nearest":
@@ -1956,43 +1918,41 @@ def get_year_end(self, dt):
19561918
return self._get_year_end_last(dt)
19571919

19581920
def get_target_month_end(self, dt):
1959-
target_month = datetime(
1960-
dt.year, self.startingMonth, 1, tzinfo=dt.tzinfo)
1961-
next_month_first_of = shift_month(target_month, 1, None)
1962-
return next_month_first_of + timedelta(days=-1)
1921+
target_month = datetime(dt.year, self.startingMonth, 1,
1922+
tzinfo=dt.tzinfo)
1923+
return shift_month(target_month, 0, 'end')
1924+
# TODO: is this DST-safe?
19631925

19641926
def _get_year_end_nearest(self, dt):
19651927
target_date = self.get_target_month_end(dt)
1966-
if target_date.weekday() == self.weekday:
1928+
wkday_diff = self.weekday - target_date.weekday()
1929+
if wkday_diff == 0:
19671930
return target_date
1968-
else:
1969-
forward = target_date + self._relativedelta_forward
1970-
backward = target_date + self._relativedelta_backward
19711931

1972-
if forward - target_date < target_date - backward:
1973-
return forward
1974-
else:
1975-
return backward
1932+
days_forward = wkday_diff % 7
1933+
if days_forward <= 3:
1934+
# The upcoming self.weekday is closer than the previous one
1935+
return target_date + timedelta(days_forward)
1936+
else:
1937+
# The previous self.weekday is closer than the upcoming one
1938+
return target_date + timedelta(days_forward - 7)
19761939

19771940
def _get_year_end_last(self, dt):
1978-
current_year = datetime(
1979-
dt.year, self.startingMonth, 1, tzinfo=dt.tzinfo)
1941+
current_year = datetime(dt.year, self.startingMonth, 1,
1942+
tzinfo=dt.tzinfo)
19801943
return current_year + self._offset_lwom
19811944

19821945
@property
19831946
def rule_code(self):
1984-
prefix = self._get_prefix()
1947+
prefix = self._prefix
19851948
suffix = self.get_rule_code_suffix()
19861949
return "{prefix}-{suffix}".format(prefix=prefix, suffix=suffix)
19871950

1988-
def _get_prefix(self):
1989-
return self._prefix
1990-
19911951
def _get_suffix_prefix(self):
19921952
if self.variation == "nearest":
1993-
return self._suffix_prefix_nearest
1953+
return 'N'
19941954
else:
1995-
return self._suffix_prefix_last
1955+
return 'L'
19961956

19971957
def get_rule_code_suffix(self):
19981958
prefix = self._get_suffix_prefix()
@@ -2008,17 +1968,15 @@ def _parse_suffix(cls, varion_code, startingMonth_code, weekday_code):
20081968
elif varion_code == "L":
20091969
variation = "last"
20101970
else:
2011-
raise ValueError(
2012-
"Unable to parse varion_code: {code}".format(code=varion_code))
1971+
raise ValueError("Unable to parse varion_code: "
1972+
"{code}".format(code=varion_code))
20131973

20141974
startingMonth = _month_to_int[startingMonth_code]
20151975
weekday = _weekday_to_int[weekday_code]
20161976

2017-
return {
2018-
"weekday": weekday,
2019-
"startingMonth": startingMonth,
2020-
"variation": variation,
2021-
}
1977+
return {"weekday": weekday,
1978+
"startingMonth": startingMonth,
1979+
"variation": variation}
20221980

20231981
@classmethod
20241982
def _from_name(cls, *args):
@@ -2091,10 +2049,9 @@ def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1,
20912049

20922050
@cache_readonly
20932051
def _offset(self):
2094-
return FY5253(
2095-
startingMonth=self.startingMonth,
2096-
weekday=self.weekday,
2097-
variation=self.variation)
2052+
return FY5253(startingMonth=self.startingMonth,
2053+
weekday=self.weekday,
2054+
variation=self.variation)
20982055

20992056
def isAnchored(self):
21002057
return self.n == 1 and self._offset.isAnchored()
@@ -2203,24 +2160,21 @@ class Easter(DateOffset):
22032160

22042161
@apply_wraps
22052162
def apply(self, other):
2206-
currentEaster = easter(other.year)
2207-
currentEaster = datetime(
2208-
currentEaster.year, currentEaster.month, currentEaster.day)
2209-
currentEaster = tslib._localize_pydatetime(currentEaster, other.tzinfo)
2163+
current_easter = easter(other.year)
2164+
current_easter = datetime(current_easter.year,
2165+
current_easter.month, current_easter.day)
2166+
current_easter = tslib._localize_pydatetime(current_easter,
2167+
other.tzinfo)
2168+
2169+
n = self.n
2170+
if n >= 0 and other < current_easter:
2171+
n -= 1
2172+
elif n < 0 and other > current_easter:
2173+
n += 1
22102174

22112175
# NOTE: easter returns a datetime.date so we have to convert to type of
22122176
# other
2213-
if self.n >= 0:
2214-
if other >= currentEaster:
2215-
new = easter(other.year + self.n)
2216-
else:
2217-
new = easter(other.year + self.n - 1)
2218-
else:
2219-
if other > currentEaster:
2220-
new = easter(other.year + self.n + 1)
2221-
else:
2222-
new = easter(other.year + self.n)
2223-
2177+
new = easter(other.year + n)
22242178
new = datetime(new.year, new.month, new.day, other.hour,
22252179
other.minute, other.second, other.microsecond)
22262180
return new

0 commit comments

Comments
 (0)