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
« 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
9# from iso4217parse import \
10# by_alpha3 as _iso4217_by_alpha3, \
11# by_code_num as _iso4217_by_code_num, \
12# Currency as _Currency
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.
19 Args:
20 date_time (_Union[datetime, date]): Date(time) to be converted.
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!")
33def _check_positivity(value: float) -> float:
34 """
35 Checks if value is positive.
37 Args:
38 value (float): value to be checked for positivity.
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!")
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
66def _check_relation(less: float, more: float) -> _Tuple[float, float]:
67 """
68 Checks if the relative size of two floating numbers is as expected.
70 Args:
71 less (float): Number expected to be smaller.
72 more (float): Number expected to be bigger.
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) + "'.")
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.
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.
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
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.
114 Args:
115 start (_Union[date, datetime]): Start date
116 end (_Union[date, datetime]): End date
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) + "'!")
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
142 Flags will control if the chronological order shall be fulfilled strictly or not.
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
164 for i in range(1, len(dates)):
165 if ~_is_start_before_end(dates[i], dates[i - 1], strictly_between):
166 return False
168 if ~_is_start_before_end(dates[-1], end_date, strictly_end):
169 return False
171 return True
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.
179 Args:
180 start (_Union[date, datetime]): Start date
181 end (_Union[date, datetime]): End date
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) + "'!")
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.
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.
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
216 for i in range(1, len(dates)):
217 if dates[i] <= dates[i - 1]:
218 return False
220 if dates[-1] > end_date:
221 return False
222 elif exclude_end & (dates[-1] == end_date):
223 return False
225 return True
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.
233 Args:
234 calendar (_Union[_HolidayBase, str]): Calendar provided as _HolidayBase or (country) string.
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!")
250def _validate_schedule(self):
251 if ~_is_start_before_end(self.__start_day, self.__end_day, True):
252 raise Exception("Chronological order mismatch!")
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!")