1818import calendar
1919import logging
2020from abc import ABC , abstractmethod
21- from typing import List , Union
21+ from typing import List , Union , Optional
2222
2323from dateutil .relativedelta import relativedelta , FR , SA , SU , TH , TU , WE , MO
2424import numpy as np
@@ -38,6 +38,7 @@ class RDateRule(ABC):
3838 currencies : List [Union [Currency , str ]] = None
3939 exchanges : List [Union [ExchangeCode , str ]] = None
4040 holiday_calendar : List [dt .date ] = None
41+ sign : Optional [str ] = None
4142
4243 def __init__ (self , result : dt .date , ** params ):
4344 self .result = result
@@ -47,6 +48,8 @@ def __init__(self, result: dt.date, **params):
4748 self .exchanges = params .get ('exchanges' )
4849 self .holiday_calendar = params .get ('holiday_calendar' )
4950 self .usd_calendar = params .get ('usd_calendar' )
51+ self .roll = params .get ('roll' )
52+ self .sign = params .get ('sign' )
5053 super ().__init__ ()
5154
5255 @abstractmethod
@@ -98,6 +101,9 @@ def add_years(self, holidays: List[dt.date]):
98101 def is_weekend (d : dt .date ):
99102 return False if d .weekday () < 5 else True # 5 Sat, 6 Sun
100103
104+ def roll_convention (self , default = None ):
105+ return self .roll or default
106+
101107
102108class ARule (RDateRule ):
103109 def handle (self ) -> dt .date :
@@ -108,7 +114,7 @@ def handle(self) -> dt.date:
108114class bRule (RDateRule ):
109115 def handle (self ) -> dt .date :
110116 holidays = self ._get_holidays ()
111- roll = 'forward' if self .number <= 0 else 'preceding'
117+ roll = self . roll_convention ( 'forward' if self .number <= 0 else 'preceding' )
112118 return self ._apply_business_days_logic (holidays , offset = self .number , roll = roll )
113119
114120
@@ -132,7 +138,7 @@ class gRule(RDateRule):
132138 def handle (self ) -> dt .date :
133139 self .result = self .result + relativedelta (weeks = self .number )
134140 holidays = self ._get_holidays ()
135- return self ._apply_business_days_logic (holidays , offset = 0 )
141+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self . roll_convention ( 'backward' ) )
136142
137143
138144class NRule (RDateRule ):
@@ -161,13 +167,13 @@ def handle(self) -> dt.date:
161167 while self .week_mask [self .result .isoweekday () - 1 ] == '0' :
162168 self .result += relativedelta (days = 1 )
163169 holidays = self ._get_holidays ()
164- return self ._apply_business_days_logic (holidays , offset = 0 )
170+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self . roll_convention ( 'backward' ) )
165171
166172
167173class mRule (RDateRule ):
168174 def handle (self ) -> dt .date :
169175 self .result = self .result + relativedelta (months = self .number )
170- return self ._apply_business_days_logic (self ._get_holidays (), offset = 0 , roll = 'forward' )
176+ return self ._apply_business_days_logic (self ._get_holidays (), offset = 0 , roll = self . roll_convention ( 'forward' ) )
171177
172178
173179class MRule (RDateRule ):
@@ -201,16 +207,9 @@ def handle(self) -> dt.date:
201207
202208
203209class uRule (RDateRule ):
204- def __init__ (self , result : dt .date , ** params ):
205- super ().__init__ (result , ** params )
206- self .roll = params .get ('roll' )
207-
208210 def handle (self ) -> dt .date :
209211 holidays = self ._get_holidays ()
210- if self .number == 0 and self .roll :
211- roll = self .roll
212- else :
213- roll = 'forward' if self .number <= 0 else 'preceding'
212+ roll = 'preceding' if self .sign == "-" and self .number == 0 else 'forward' if self .number <= 0 else 'preceding'
214213 return self ._apply_business_days_logic (holidays , offset = self .number , roll = roll )
215214
216215
@@ -225,7 +224,7 @@ def handle(self) -> dt.date:
225224 month_range = calendar .monthrange (self .result .year , self .result .month )
226225 self .result = self .result .replace (day = month_range [1 ])
227226 holidays = self ._get_holidays ()
228- return self ._apply_business_days_logic (holidays , offset = 0 , roll = 'backward' )
227+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self . roll_convention ( 'backward' ) )
229228
230229
231230class VRule (RDateRule ):
@@ -242,15 +241,16 @@ class wRule(RDateRule):
242241 def handle (self ) -> dt .date :
243242 self .result = self .result + relativedelta (weeks = self .number )
244243 holidays = self ._get_holidays ()
245- return self ._apply_business_days_logic (holidays , offset = 0 )
244+ roll = 'forward' if self .number >= 0 else 'backward'
245+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self .roll_convention (roll ))
246246
247247
248248class xRule (RDateRule ):
249249 def handle (self ) -> dt .date :
250250 month_range = calendar .monthrange (self .result .year , self .result .month )
251251 self .result = self .result .replace (day = month_range [1 ])
252252 holidays = self ._get_holidays ()
253- return self ._apply_business_days_logic (holidays , offset = 0 , roll = 'backward' )
253+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self . roll_convention ( 'backward' ) )
254254
255255
256256class XRule (RDateRule ):
@@ -264,7 +264,7 @@ def handle(self) -> dt.date:
264264 while self .week_mask [self .result .isoweekday () - 1 ] == '0' :
265265 self .result += relativedelta (days = 1 )
266266 holidays = self ._get_holidays ()
267- return self ._apply_business_days_logic (holidays , offset = 0 )
267+ return self ._apply_business_days_logic (holidays , offset = 0 , roll = self . roll_convention ( 'backward' ) )
268268
269269
270270class ZRule (RDateRule ):
0 commit comments