12
12
from pandas .core .common import AbstractMethodError
13
13
14
14
# import after tools, dateutil check
15
- from dateutil .relativedelta import relativedelta , weekday
16
15
from dateutil .easter import easter
17
16
from pandas ._libs import tslib , Timestamp , OutOfBoundsDatetime , Timedelta
18
17
from pandas .util ._decorators import cache_readonly
@@ -1816,10 +1815,7 @@ class FY5253(DateOffset):
1816
1815
variation : str
1817
1816
{"nearest", "last"} for "LastOfMonth" or "NearestEndMonth"
1818
1817
"""
1819
-
1820
1818
_prefix = 'RE'
1821
- _suffix_prefix_last = 'L'
1822
- _suffix_prefix_nearest = 'N'
1823
1819
_adjust_dst = True
1824
1820
1825
1821
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,
1841
1837
raise ValueError ('{variation} is not a valid variation'
1842
1838
.format (variation = self .variation ))
1843
1839
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
-
1860
1840
@cache_readonly
1861
1841
def _offset_lwom (self ):
1862
1842
if self .variation == "nearest" :
@@ -1865,9 +1845,9 @@ def _offset_lwom(self):
1865
1845
return LastWeekOfMonth (n = 1 , weekday = self .weekday )
1866
1846
1867
1847
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 )
1871
1851
1872
1852
def onOffset (self , dt ):
1873
1853
if self .normalize and not _is_normalized (dt ):
@@ -1891,63 +1871,45 @@ def apply(self, other):
1891
1871
datetime (other .year , self .startingMonth , 1 ))
1892
1872
next_year = self .get_year_end (
1893
1873
datetime (other .year + 1 , self .startingMonth , 1 ))
1874
+
1894
1875
prev_year = tslib ._localize_pydatetime (prev_year , other .tzinfo )
1895
1876
cur_year = tslib ._localize_pydatetime (cur_year , other .tzinfo )
1896
1877
next_year = tslib ._localize_pydatetime (next_year , other .tzinfo )
1897
1878
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
1908
1890
elif other < cur_year :
1909
- year = other .year
1910
1891
n -= 1
1911
1892
elif other < next_year :
1912
- year = other .year + 1
1913
- n -= 1
1893
+ pass
1914
1894
else :
1915
1895
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
1924
1896
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
1935
1900
elif other > cur_year :
1936
- year = other .year
1937
- n -= 1
1901
+ n += 1
1938
1902
elif other > prev_year :
1939
- year = other .year - 1
1940
- n -= 1
1903
+ pass
1941
1904
else :
1942
1905
assert False
1943
1906
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
1951
1913
1952
1914
def get_year_end (self , dt ):
1953
1915
if self .variation == "nearest" :
@@ -1956,43 +1918,41 @@ def get_year_end(self, dt):
1956
1918
return self ._get_year_end_last (dt )
1957
1919
1958
1920
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?
1963
1925
1964
1926
def _get_year_end_nearest (self , dt ):
1965
1927
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 :
1967
1930
return target_date
1968
- else :
1969
- forward = target_date + self ._relativedelta_forward
1970
- backward = target_date + self ._relativedelta_backward
1971
1931
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 )
1976
1939
1977
1940
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 )
1980
1943
return current_year + self ._offset_lwom
1981
1944
1982
1945
@property
1983
1946
def rule_code (self ):
1984
- prefix = self ._get_prefix ()
1947
+ prefix = self ._prefix
1985
1948
suffix = self .get_rule_code_suffix ()
1986
1949
return "{prefix}-{suffix}" .format (prefix = prefix , suffix = suffix )
1987
1950
1988
- def _get_prefix (self ):
1989
- return self ._prefix
1990
-
1991
1951
def _get_suffix_prefix (self ):
1992
1952
if self .variation == "nearest" :
1993
- return self . _suffix_prefix_nearest
1953
+ return 'N'
1994
1954
else :
1995
- return self . _suffix_prefix_last
1955
+ return 'L'
1996
1956
1997
1957
def get_rule_code_suffix (self ):
1998
1958
prefix = self ._get_suffix_prefix ()
@@ -2008,17 +1968,15 @@ def _parse_suffix(cls, varion_code, startingMonth_code, weekday_code):
2008
1968
elif varion_code == "L" :
2009
1969
variation = "last"
2010
1970
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 ))
2013
1973
2014
1974
startingMonth = _month_to_int [startingMonth_code ]
2015
1975
weekday = _weekday_to_int [weekday_code ]
2016
1976
2017
- return {
2018
- "weekday" : weekday ,
2019
- "startingMonth" : startingMonth ,
2020
- "variation" : variation ,
2021
- }
1977
+ return {"weekday" : weekday ,
1978
+ "startingMonth" : startingMonth ,
1979
+ "variation" : variation }
2022
1980
2023
1981
@classmethod
2024
1982
def _from_name (cls , * args ):
@@ -2091,10 +2049,9 @@ def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1,
2091
2049
2092
2050
@cache_readonly
2093
2051
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 )
2098
2055
2099
2056
def isAnchored (self ):
2100
2057
return self .n == 1 and self ._offset .isAnchored ()
@@ -2203,24 +2160,21 @@ class Easter(DateOffset):
2203
2160
2204
2161
@apply_wraps
2205
2162
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
2210
2174
2211
2175
# NOTE: easter returns a datetime.date so we have to convert to type of
2212
2176
# 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 )
2224
2178
new = datetime (new .year , new .month , new .day , other .hour ,
2225
2179
other .minute , other .second , other .microsecond )
2226
2180
return new
0 commit comments