Coverage for rivapy/tools/_validators.py: 50%

82 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-05 14:27 +0000

1# -*- coding: utf-8 -*- 

2import pandas as pd 

3from enum import Enum 

4from datetime import datetime, date 

5from typing import List as _List, Tuple as _Tuple, Union as _Union 

6from holidays import HolidayBase as _HolidayBase, country_holidays as _CountryHoliday 

7from holidays.utils import list_supported_countries as _list_supported_countries 

8 

9# from iso4217parse import \ 

10# by_alpha3 as _iso4217_by_alpha3, \ 

11# by_code_num as _iso4217_by_code_num, \ 

12# Currency as _Currency 

13 

14 

15def _date_to_datetime(date_time: _Union[datetime, date]) -> date: 

16 """ 

17 Converts a date to a datetime or leaves it unchanged if it is already of type datetime. 

18 

19 Args: 

20 date_time (_Union[datetime, date]): Date(time) to be converted. 

21 

22 Returns: 

23 date: (Potentially) Converted datetime. 

24 """ 

25 if isinstance(date_time, datetime): 

26 return date_time 

27 elif isinstance(date_time, date): 

28 return datetime.combine(date_time, datetime.min.time()) 

29 else: 

30 raise TypeError("'" + str(date_time) + "' must be of type datetime or date!") 

31 

32 

33def _check_positivity(value: float) -> float: 

34 """ 

35 Checks if value is positive. 

36 

37 Args: 

38 value (float): value to be checked for positivity. 

39 

40 Returns: 

41 float: positive value 

42 """ 

43 if value > 0.0: 

44 return value 

45 else: 

46 raise Exception(str(value) + " must be positive!") 

47 

48 

49# def check_non_negativity(value: float 

50# ) -> float: 

51# """ 

52# Checks if value is non-negative. 

53# 

54# Args: 

55# value (float): value to be checked for non-negativity. 

56# 

57# Returns: 

58# float: non-negative value 

59# """ 

60# if value < 0.0: 

61# raise Exception(str(value) + ' must not be negative!') 

62# else: 

63# return value 

64 

65 

66def _check_relation(less: float, more: float) -> _Tuple[float, float]: 

67 """ 

68 Checks if the relative size of two floating numbers is as expected. 

69 

70 Args: 

71 less (float): Number expected to be smaller. 

72 more (float): Number expected to be bigger. 

73 

74 Returns: 

75 Tuple[float, float]: Tuple of (two) ascending numbers. 

76 """ 

77 if less < more: 

78 return less, more 

79 else: 

80 raise Exception("'" + str(less) + "' must be smaller than '" + str(more) + "'.") 

81 

82 

83def _is_start_before_end(start: date, end: date, strictly: bool = True) -> bool: 

84 """ 

85 Checks if the start date is before (strictly = True) of not after (strictly = False) the end date, respectively. 

86 

87 Args: 

88 start (date): Start date 

89 end (date: End date 

90 strictly (bool): Flag defining if the start date shall be strictly before or not after the end date, 

91 respectively. 

92 

93 Returns: 

94 bool: True if start date <(=) end date. False otherwise. 

95 """ 

96 if start < end: 

97 return True 

98 elif start == end: 

99 if strictly: 

100 print("WARNING: '" + str(start) + "' must not be after '" + str(end) + "'!") 

101 return False 

102 else: 

103 return True 

104 else: 

105 print("WARNING: '" + str(start) + "' must be earlier than '" + str(end) + "'!") 

106 return False 

107 

108 

109def _check_start_before_end(start: _Union[date, datetime], end: _Union[date, datetime]) -> _Tuple[date, date]: 

110 """ 

111 Converts the two input dates from datetime to date format it necessary and checks if the first date is earlier 

112 than the second one. 

113 

114 Args: 

115 start (_Union[date, datetime]): Start date 

116 end (_Union[date, datetime]): End date 

117 

118 Returns: 

119 Tuple[date, date]: start date, end date 

120 """ 

121 start_date = _date_to_datetime(start) 

122 end_date = _date_to_datetime(end) 

123 if start_date < end_date: 

124 return start_date, end_date 

125 else: 

126 raise Exception("'" + str(start) + "' must be earlier than '" + str(end) + "'!") 

127 

128 

129def _is_chronological( 

130 start_date: date, 

131 end_date: date, 

132 dates: _List[date] = None, 

133 strictly_start: bool = True, 

134 strictly_between: bool = True, 

135 strictly_end: bool = False, 

136) -> bool: 

137 """ 

138 Checks if a given set of dates fulfills the following requirements: 

139 - start date <(=) end date 

140 - start date <(=) dates[0] <(=) dates[1] <(=) ... <(=) dates[n] <(=) end date 

141 

142 Flags will control if the chronological order shall be fulfilled strictly or not. 

143 

144 Args: 

145 start_date (date): First day of the interval the dates shall chronologically fall in. 

146 end_date (date): Last day of the interval the dates shall chronologically fall in. 

147 dates (List[date], optional): List of dates to be tested if they are ascending and between start and end date. 

148 Defaults to None. 

149 strictly_start(bool, optional): True, if start date must be strictly before following date, False otherwise. 

150 Defaults to True. 

151 strictly_between(bool, optional): True, if dates must be strictly monotonically ascending, False otherwise. 

152 Defaults to True. 

153 strictly_end(bool, optional): True, if end date must be strictly after previous date, False otherwise. 

154 Defaults to False. 

155 Returns: 

156 bool: True, if all chronological requirements w.r.t. given dates are fulfilled. False, otherwise. 

157 """ 

158 if dates is None: 

159 return _is_start_before_end(start_date, end_date, (strictly_start & strictly_end)) 

160 else: 

161 if ~_is_start_before_end(start_date, dates[0], strictly_start): 

162 return False 

163 

164 for i in range(1, len(dates)): 

165 if ~_is_start_before_end(dates[i], dates[i - 1], strictly_between): 

166 return False 

167 

168 if ~_is_start_before_end(dates[-1], end_date, strictly_end): 

169 return False 

170 

171 return True 

172 

173 

174def check_start_before_end(start: _Union[date, datetime], end: _Union[date, datetime]) -> _Tuple[date, date]: 

175 """ 

176 Converts the two input dates from datetime to date format it necessary and checks if the first date is earlier 

177 than the second one. 

178 

179 Args: 

180 start (_Union[date, datetime]): Start date 

181 end (_Union[date, datetime]): End date 

182 

183 Returns: 

184 Tuple[date, date]: start date, end date 

185 """ 

186 start_date = _date_to_datetime(start) 

187 end_date = _date_to_datetime(end) 

188 if start_date < end_date: 

189 return start_date, end_date 

190 else: 

191 raise Exception("'" + str(start) + "' must be earlier than '" + str(end) + "'!") 

192 

193 

194def _is_ascending_date_list(start_date: date, dates: _List[date], end_date: date, exclude_start: bool = True, exclude_end: bool = False) -> bool: 

195 """ 

196 Checks if all specified dates, e.g. coupon payment dates, fall between start date and end date. Start and end date 

197 are excluded dependent on the corresponding boolean flags. Moreover, the dates are verified to be ascending. 

198 

199 Args: 

200 start_date (date): First day of the interval the dates shall foll in. 

201 dates (List[date]): List of dates to be tested if they are ascending and between start and end date. 

202 end_date (date): Last day of the interval the dates shall foll in. 

203 exclude_start (bool, optional): True, if start date does not belong to the interval. False, otherwise. 

204 Defaults to True. 

205 exclude_end (bool, optional): True, if end date does not belong to the interval. False, otherwise. 

206 Defaults to False. 

207 

208 Returns: 

209 bool: True, if dates are ascending and fall between the interval given by start and end date. False, otherwise. 

210 """ 

211 if dates[0] < start_date: 

212 return False 

213 elif exclude_start & (dates[0] == start_date): 

214 return False 

215 

216 for i in range(1, len(dates)): 

217 if dates[i] <= dates[i - 1]: 

218 return False 

219 

220 if dates[-1] > end_date: 

221 return False 

222 elif exclude_end & (dates[-1] == end_date): 

223 return False 

224 

225 return True 

226 

227 

228def _string_to_calendar(calendar: _Union[_HolidayBase, str]) -> _HolidayBase: 

229 """ 

230 Checks if calendar provided as _HolidayBase or string (of corresponding country), respectively, is known and 

231 converts it if necessary into the HolidayBse format. 

232 

233 Args: 

234 calendar (_Union[_HolidayBase, str]): Calendar provided as _HolidayBase or (country) string. 

235 

236 Returns: 

237 _HolidayBase: (Potentially) converted calendar. 

238 """ 

239 if isinstance(calendar, _HolidayBase): 

240 return calendar 

241 elif isinstance(calendar, str): 

242 if calendar in _list_supported_countries(): 

243 return _CountryHoliday(calendar) 

244 else: 

245 raise Exception("Unknown calendar " + calendar + "'!") 

246 else: 

247 raise TypeError("The holiday calendar '" + str(calendar) + "' must be provided as HolidayBase or string!") 

248 

249 

250def _validate_schedule(self): 

251 if ~_is_start_before_end(self.__start_day, self.__end_day, True): 

252 raise Exception("Chronological order mismatch!") 

253 

254 

255def _check_pandas_index_for_datetime(dataframe: pd.DataFrame): 

256 if isinstance(dataframe, pd.DataFrame): 

257 if not isinstance(dataframe.index, pd.DatetimeIndex): 

258 raise TypeError("The index of the DataFrame is not of type pd.DatetimeIndex!") 

259 else: 

260 raise TypeError(f"The argument is not of type pd.DataFrame!")