vtlengine 1.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vtlengine might be problematic. Click here for more details.

Files changed (56) hide show
  1. vtlengine/API/_InternalApi.py +159 -102
  2. vtlengine/API/__init__.py +110 -68
  3. vtlengine/AST/ASTConstructor.py +188 -98
  4. vtlengine/AST/ASTConstructorModules/Expr.py +402 -205
  5. vtlengine/AST/ASTConstructorModules/ExprComponents.py +248 -104
  6. vtlengine/AST/ASTConstructorModules/Terminals.py +158 -95
  7. vtlengine/AST/ASTEncoders.py +1 -1
  8. vtlengine/AST/ASTTemplate.py +24 -9
  9. vtlengine/AST/ASTVisitor.py +8 -12
  10. vtlengine/AST/DAG/__init__.py +43 -35
  11. vtlengine/AST/DAG/_words.py +4 -4
  12. vtlengine/AST/Grammar/Vtl.g4 +49 -20
  13. vtlengine/AST/Grammar/VtlTokens.g4 +13 -1
  14. vtlengine/AST/Grammar/lexer.py +2012 -1312
  15. vtlengine/AST/Grammar/parser.py +7524 -4343
  16. vtlengine/AST/Grammar/tokens.py +140 -128
  17. vtlengine/AST/VtlVisitor.py +16 -5
  18. vtlengine/AST/__init__.py +41 -11
  19. vtlengine/DataTypes/NumericTypesHandling.py +5 -4
  20. vtlengine/DataTypes/TimeHandling.py +196 -301
  21. vtlengine/DataTypes/__init__.py +304 -218
  22. vtlengine/Exceptions/__init__.py +96 -27
  23. vtlengine/Exceptions/messages.py +149 -69
  24. vtlengine/Interpreter/__init__.py +817 -497
  25. vtlengine/Model/__init__.py +172 -121
  26. vtlengine/Operators/Aggregation.py +156 -95
  27. vtlengine/Operators/Analytic.py +167 -79
  28. vtlengine/Operators/Assignment.py +7 -4
  29. vtlengine/Operators/Boolean.py +27 -32
  30. vtlengine/Operators/CastOperator.py +177 -131
  31. vtlengine/Operators/Clause.py +137 -99
  32. vtlengine/Operators/Comparison.py +148 -117
  33. vtlengine/Operators/Conditional.py +290 -98
  34. vtlengine/Operators/General.py +68 -47
  35. vtlengine/Operators/HROperators.py +91 -72
  36. vtlengine/Operators/Join.py +217 -118
  37. vtlengine/Operators/Numeric.py +129 -46
  38. vtlengine/Operators/RoleSetter.py +16 -15
  39. vtlengine/Operators/Set.py +61 -36
  40. vtlengine/Operators/String.py +213 -139
  41. vtlengine/Operators/Time.py +467 -215
  42. vtlengine/Operators/Validation.py +117 -76
  43. vtlengine/Operators/__init__.py +340 -213
  44. vtlengine/Utils/__init__.py +232 -41
  45. vtlengine/__init__.py +1 -1
  46. vtlengine/files/output/__init__.py +15 -6
  47. vtlengine/files/output/_time_period_representation.py +10 -9
  48. vtlengine/files/parser/__init__.py +79 -52
  49. vtlengine/files/parser/_rfc_dialect.py +6 -5
  50. vtlengine/files/parser/_time_checking.py +48 -37
  51. vtlengine-1.0.2.dist-info/METADATA +245 -0
  52. vtlengine-1.0.2.dist-info/RECORD +58 -0
  53. {vtlengine-1.0.dist-info → vtlengine-1.0.2.dist-info}/WHEEL +1 -1
  54. vtlengine-1.0.dist-info/METADATA +0 -104
  55. vtlengine-1.0.dist-info/RECORD +0 -58
  56. {vtlengine-1.0.dist-info → vtlengine-1.0.2.dist-info}/LICENSE.md +0 -0
@@ -2,32 +2,20 @@ import calendar
2
2
  import copy
3
3
  import operator
4
4
  from datetime import date, datetime as dt
5
- from typing import Union, Optional
5
+ from typing import Union, Optional, Any, Dict
6
6
 
7
7
  import pandas as pd
8
8
 
9
- DURATION_MAPPING = {
10
- "A": 6,
11
- "S": 5,
12
- "Q": 4,
13
- "M": 3,
14
- "W": 2,
15
- "D": 1
16
- }
17
-
18
- DURATION_MAPPING_REVERSED = {
19
- 6: "A",
20
- 5: "S",
21
- 4: "Q",
22
- 3: "M",
23
- 2: "W",
24
- 1: "D"
25
- }
9
+ from vtlengine.Exceptions import SemanticError
10
+
11
+ DURATION_MAPPING = {"A": 6, "S": 5, "Q": 4, "M": 3, "W": 2, "D": 1}
12
+
13
+ DURATION_MAPPING_REVERSED = {6: "A", 5: "S", 4: "Q", 3: "M", 2: "W", 1: "D"}
26
14
 
27
15
  PERIOD_INDICATORS = ["A", "S", "Q", "M", "W", "D"]
28
16
 
29
17
 
30
- def date_to_period(date_value: date, period_indicator):
18
+ def date_to_period(date_value: date, period_indicator: str) -> Any:
31
19
  if period_indicator == "A":
32
20
  return TimePeriodHandler(f"{date_value.year}A")
33
21
  elif period_indicator == "S":
@@ -43,76 +31,47 @@ def date_to_period(date_value: date, period_indicator):
43
31
  return TimePeriodHandler(f"{date_value.year}D{date_value.timetuple().tm_yday}")
44
32
 
45
33
 
46
- def period_to_date(year, period_indicator, period_number, start=False):
47
- if period_indicator == 'A':
48
- if start:
49
- return date(year, 1, 1)
50
- else:
51
- return date(year, 12, 31)
52
- if period_indicator == 'S':
53
- if period_number == 1:
54
- if start:
55
- return date(year, 1, 1)
56
- else:
57
- return date(year, 6, 30)
58
- else:
59
- if start:
60
- return date(year, 7, 1)
61
- else:
62
- return date(year, 12, 31)
63
- if period_indicator == 'Q':
64
- if period_number == 1:
65
- if start:
66
- return date(year, 1, 1)
67
- else:
68
- return date(year, 3, 31)
69
- elif period_number == 2:
70
- if start:
71
- return date(year, 4, 1)
72
- else:
73
- return date(year, 6, 30)
74
- elif period_number == 3:
75
- if start:
76
- return date(year, 7, 1)
77
- else:
78
- return date(year, 9, 30)
79
- else:
80
- if start:
81
- return date(year, 10, 1)
82
- else:
83
- return date(year, 12, 31)
34
+ def period_to_date(
35
+ year: int, period_indicator: str, period_number: int, start: bool = False
36
+ ) -> date:
37
+ if period_indicator == "A":
38
+ return date(year, 1, 1) if start else date(year, 12, 31)
39
+ periods = {
40
+ "S": [(date(year, 1, 1), date(year, 6, 30)), (date(year, 7, 1), date(year, 12, 31))],
41
+ "Q": [
42
+ (date(year, 1, 1), date(year, 3, 31)),
43
+ (date(year, 4, 1), date(year, 6, 30)),
44
+ (date(year, 7, 1), date(year, 9, 30)),
45
+ (date(year, 10, 1), date(year, 12, 31)),
46
+ ],
47
+ }
48
+ if period_indicator in periods:
49
+ start_date, end_date = periods[period_indicator][period_number - 1]
50
+ return start_date if start else end_date
84
51
  if period_indicator == "M":
85
- if start:
86
- return date(year, period_number, 1)
87
- else:
88
- day = int(calendar.monthrange(year, period_number)[1])
89
- return date(year, period_number, day)
90
- if period_indicator == "W": # 0 for Sunday, 1 for Monday in %w
91
- if start:
92
- return dt.strptime(f"{year}-W{period_number}-1", "%G-W%V-%w").date()
93
- else:
94
- return dt.strptime(f"{year}-W{period_number}-0", "%G-W%V-%w").date()
52
+ day = 1 if start else calendar.monthrange(year, period_number)[1]
53
+ return date(year, period_number, day)
54
+ if period_indicator == "W":
55
+ week_day = 1 if start else 0
56
+ return dt.strptime(f"{year}-W{period_number}-{week_day}", "%G-W%V-%w").date()
95
57
  if period_indicator == "D":
96
58
  return dt.strptime(f"{year}-D{period_number}", "%Y-D%j").date()
97
-
98
- raise ValueError(f'Invalid Period Indicator {period_indicator}')
59
+ raise SemanticError("2-1-19-2", period=period_indicator)
99
60
 
100
61
 
101
- def day_of_year(date: str):
62
+ def day_of_year(date: str) -> int:
102
63
  """
103
64
  Returns the day of the year for a given date string
104
65
  2020-01-01 -> 1
105
66
  """
106
67
  # Convert the date string to a datetime object
107
- date_object = dt.strptime(date, '%Y-%m-%d')
108
-
68
+ date_object = dt.strptime(date, "%Y-%m-%d")
109
69
  # Get the day number in the year
110
70
  day_number = date_object.timetuple().tm_yday
111
-
112
71
  return day_number
113
72
 
114
73
 
115
- def from_input_customer_support_to_internal(period: str):
74
+ def from_input_customer_support_to_internal(period: str) -> tuple[int, str, int]:
116
75
  """
117
76
  Converts a period string from the input customer support format to the internal format
118
77
  2020-01-01 -> (2020, 'D', 1)
@@ -122,35 +81,25 @@ def from_input_customer_support_to_internal(period: str):
122
81
  2020-M01 -> (2020, 'M', 1)
123
82
  2020-W01 -> (2020, 'W', 1)
124
83
  """
125
- if period.count("-") == 2:
126
- period_indicator = 'D'
127
- year = int(period.split("-")[0])
128
- period_number = int(day_of_year(period))
129
- return year, period_indicator, period_number
130
- if period.count("-") == 1:
131
- year = int(period.split("-")[0])
132
- second_term = period.split("-")[1]
133
- if len(second_term) == 4:
134
- period_indicator = 'D'
135
- period_number = int(second_term[1:])
136
- elif len(second_term) == 3:
137
- # Could be W or M YYYY-Www or YYYY-Mmm
138
- period_indicator = second_term[0]
139
- period_number = int(second_term[1:])
140
- elif len(second_term) == 2:
141
- # Could be M or Q or S or A YYYY-MM or YYYY-Qq or YYYY-Ss or YYYY-A1
142
- if second_term[0] in PERIOD_INDICATORS:
143
- period_indicator = second_term[0]
144
- period_number = int(second_term[1:])
145
- else:
146
- period_indicator = 'M'
147
- period_number = int(second_term)
148
- else:
149
- raise ValueError
150
-
151
- return year, period_indicator, period_number
152
-
153
- raise ValueError
84
+ parts = period.split("-")
85
+ year = int(parts[0])
86
+ if len(parts) == 3: # 'YYYY-MM-DD' case
87
+ return year, "D", int(day_of_year(period))
88
+ second_term = parts[1]
89
+ length = len(second_term)
90
+ if length == 4: # 'YYYY-Dxxx' case
91
+ return year, "D", int(second_term[1:])
92
+ if length == 3: # 'YYYY-Wxx' or 'YYYY-Mxx' case
93
+ return year, second_term[0], int(second_term[1:])
94
+ if length == 2: # 'YYYY-Qx', 'YYYY-Sx', 'YYYY-Ax', or 'YYYY-MM' case
95
+ indicator = second_term[0]
96
+ return (
97
+ (year, indicator, int(second_term[1:]))
98
+ if indicator in PERIOD_INDICATORS
99
+ else (year, "M", int(second_term))
100
+ )
101
+ raise SemanticError("2-1-19-6", period_format=period)
102
+ # raise ValueError
154
103
 
155
104
 
156
105
  class SingletonMeta(type):
@@ -160,9 +109,9 @@ class SingletonMeta(type):
160
109
  metaclass because it is best suited for this purpose.
161
110
  """
162
111
 
163
- _instances = {}
112
+ _instances: Dict[Any, Any] = {}
164
113
 
165
- def __call__(cls, *args, **kwargs):
114
+ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
166
115
  """
167
116
  Possible changes to the value of the `__init__` argument do not affect
168
117
  the returned instance.
@@ -174,25 +123,18 @@ class SingletonMeta(type):
174
123
 
175
124
 
176
125
  class PeriodDuration(metaclass=SingletonMeta):
177
- periods = {
178
- 'D': 366,
179
- 'W': 53,
180
- 'M': 12,
181
- 'Q': 4,
182
- 'S': 2,
183
- 'A': 1
184
- }
126
+ periods = {"D": 366, "W": 53, "M": 12, "Q": 4, "S": 2, "A": 1}
185
127
 
186
- def __contains__(self, item):
128
+ def __contains__(self, item: Any) -> bool:
187
129
  return item in self.periods
188
130
 
189
131
  @property
190
- def member_names(self):
132
+ def member_names(self) -> list[str]:
191
133
  return list(self.periods.keys())
192
134
 
193
135
  @classmethod
194
- def check_period_range(cls, letter, value):
195
- if letter == 'A':
136
+ def check_period_range(cls, letter: str, value: Any) -> bool:
137
+ if letter == "A":
196
138
  return True
197
139
  return value in range(1, cls.periods[letter] + 1)
198
140
 
@@ -202,23 +144,26 @@ class TimePeriodHandler:
202
144
  _period_indicator: str
203
145
  _period_number: int
204
146
 
205
- def __init__(self, period: str):
147
+ def __init__(self, period: str) -> None:
148
+ if isinstance(period, int):
149
+ period = str(period)
206
150
  if "-" in period:
207
- self.year, self.period_indicator, self.period_number = from_input_customer_support_to_internal(
208
- period)
151
+ self.year, self.period_indicator, self.period_number = (
152
+ from_input_customer_support_to_internal(period)
153
+ )
209
154
  else:
210
155
  self.year = int(period[:4])
211
156
  if len(period) > 4:
212
157
  self.period_indicator = period[4]
213
158
  else:
214
- self.period_indicator = 'A'
159
+ self.period_indicator = "A"
215
160
  if len(period) > 5:
216
161
  self.period_number = int(period[5:])
217
162
  else:
218
163
  self.period_number = 1
219
164
 
220
- def __str__(self):
221
- if self.period_indicator == 'A':
165
+ def __str__(self) -> str:
166
+ if self.period_indicator == "A":
222
167
  # return f"{self.year}{self.period_indicator}"
223
168
  return f"{self.year}" # Drop A from exit time period year
224
169
  if self.period_indicator in ["W", "M"]:
@@ -230,16 +175,17 @@ class TimePeriodHandler:
230
175
  return f"{self.year}-{self.period_indicator}{period_number_str}"
231
176
 
232
177
  @staticmethod
233
- def _check_year(year: int):
178
+ def _check_year(year: int) -> None:
234
179
  if year < 1900 or year > 9999:
235
- raise ValueError(f'Invalid year {year}, must be between 1900 and 9999.')
180
+ raise SemanticError("2-1-19-10", year=year)
181
+ # raise ValueError(f'Invalid year {year}, must be between 1900 and 9999.')
236
182
 
237
183
  @property
238
184
  def year(self) -> int:
239
185
  return self._year
240
186
 
241
187
  @year.setter
242
- def year(self, value: int):
188
+ def year(self, value: int) -> None:
243
189
  self._check_year(value)
244
190
  self._year = value
245
191
 
@@ -248,10 +194,9 @@ class TimePeriodHandler:
248
194
  return self._period_indicator
249
195
 
250
196
  @period_indicator.setter
251
- def period_indicator(self, value: str):
197
+ def period_indicator(self, value: str) -> None:
252
198
  if value not in PeriodDuration():
253
- raise ValueError(
254
- f'Cannot set period indicator as {value}. Possible values: {PeriodDuration().member_names}')
199
+ raise SemanticError("2-1-19-2", period=value)
255
200
  self._period_indicator = value
256
201
 
257
202
  @property
@@ -259,85 +204,92 @@ class TimePeriodHandler:
259
204
  return self._period_number
260
205
 
261
206
  @period_number.setter
262
- def period_number(self, value: int):
207
+ def period_number(self, value: int) -> None:
263
208
  if not PeriodDuration.check_period_range(self.period_indicator, value):
264
- raise ValueError(f'Period Number must be between 1 and '
265
- f'{PeriodDuration.periods[self.period_indicator]} '
266
- f'for period indicator {self.period_indicator}.')
209
+ raise SemanticError(
210
+ "2-1-19-7",
211
+ periods=PeriodDuration.periods[self.period_indicator],
212
+ period_inidcator=self.period_indicator,
213
+ )
214
+ # raise ValueError(f'Period Number must be between 1 and '
215
+ # f'{PeriodDuration.periods[self.period_indicator]} '
216
+ # f'for period indicator {self.period_indicator}.')
267
217
  # check day is correct for year
268
- if self.period_indicator == 'D':
218
+ if self.period_indicator == "D":
269
219
  if calendar.isleap(self.year):
270
220
  if value > 366:
271
- raise ValueError(f'Invalid day {value} for year {self.year}.')
221
+ raise SemanticError("2-1-19-9", day=value, year=self.year)
222
+ # raise ValueError(f'Invalid day {value} for year {self.year}.')
272
223
  else:
273
224
  if value > 365:
274
- raise ValueError(f'Invalid day {value} for year {self.year}.')
225
+ raise SemanticError("2-1-19-9", day=value, year=self.year)
226
+ # raise ValueError(f'Invalid day {value} for year {self.year}.')
275
227
  self._period_number = value
276
228
 
277
- def _meta_comparison(self, other, py_op) -> Optional[bool]:
229
+ def _meta_comparison(self, other: Any, py_op: Any) -> Optional[bool]:
278
230
  if pd.isnull(other):
279
231
  return None
280
232
  if isinstance(other, str):
281
233
  if len(other) == 0:
282
234
  return False
283
235
  other = TimePeriodHandler(other)
236
+ return py_op(
237
+ DURATION_MAPPING[self.period_indicator], DURATION_MAPPING[other.period_indicator]
238
+ )
284
239
 
285
- return py_op(DURATION_MAPPING[self.period_indicator],
286
- DURATION_MAPPING[other.period_indicator])
287
-
288
- def start_date(self, as_date=False) -> Union[date, str]:
240
+ def start_date(self, as_date: bool = False) -> Union[date, str]:
289
241
  """
290
242
  Gets the starting date of the Period
291
243
  """
292
- date_value = period_to_date(year=self.year,
293
- period_indicator=self.period_indicator,
294
- period_number=self.period_number,
295
- start=True)
296
- if as_date:
297
- return date_value
298
- return date_value.isoformat()
299
-
300
- def end_date(self, as_date=False) -> Union[date, str]:
244
+ date_value = period_to_date(
245
+ year=self.year,
246
+ period_indicator=self.period_indicator,
247
+ period_number=self.period_number,
248
+ start=True,
249
+ )
250
+ return date_value if as_date else date_value.isoformat()
251
+
252
+ def end_date(self, as_date: bool = False) -> Union[date, str]:
301
253
  """
302
254
  Gets the ending date of the Period
303
255
  """
304
- date_value = period_to_date(year=self.year,
305
- period_indicator=self.period_indicator,
306
- period_number=self.period_number,
307
- start=False)
308
- if as_date:
309
- return date_value
310
- return date_value.isoformat()
311
-
312
- def __eq__(self, other) -> bool:
256
+ date_value = period_to_date(
257
+ year=self.year,
258
+ period_indicator=self.period_indicator,
259
+ period_number=self.period_number,
260
+ start=False,
261
+ )
262
+ return date_value if as_date else date_value.isoformat()
263
+
264
+ def __eq__(self, other: Any) -> Optional[bool]: # type: ignore[override]
313
265
  return self._meta_comparison(other, operator.eq)
314
266
 
315
- def __ne__(self, other) -> bool:
267
+ def __ne__(self, other: Any) -> Optional[bool]: # type: ignore[override]
316
268
  return not self._meta_comparison(other, operator.eq)
317
269
 
318
- def __lt__(self, other) -> bool:
270
+ def __lt__(self, other: Any) -> Optional[bool]:
319
271
  return self._meta_comparison(other, operator.lt)
320
272
 
321
- def __le__(self, other) -> bool:
273
+ def __le__(self, other: Any) -> Optional[bool]:
322
274
  return self._meta_comparison(other, operator.le)
323
275
 
324
- def __gt__(self, other) -> bool:
276
+ def __gt__(self, other: Any) -> Optional[bool]:
325
277
  return self._meta_comparison(other, operator.gt)
326
278
 
327
- def __ge__(self, other) -> bool:
279
+ def __ge__(self, other: Any) -> Optional[bool]:
328
280
  return self._meta_comparison(other, operator.ge)
329
281
 
330
- def change_indicator(self, new_indicator):
282
+ def change_indicator(self, new_indicator: str) -> None:
331
283
  if self.period_indicator == new_indicator:
332
284
  return
333
285
  date_value = period_to_date(self.year, self.period_indicator, self.period_number)
334
286
  self.period_indicator = new_indicator
335
- self.period_number = date_to_period(date_value,
336
- period_indicator=new_indicator).period_number
287
+ self.period_number = date_to_period(
288
+ date_value, period_indicator=new_indicator
289
+ ).period_number
337
290
 
338
- def vtl_representation(self):
339
- if self.period_indicator == 'A':
340
- # return f"{self.year}{self.period_indicator}"
291
+ def vtl_representation(self) -> str:
292
+ if self.period_indicator == "A":
341
293
  return f"{self.year}" # Drop A from exit time period year
342
294
  if self.period_indicator in ["W", "M"]:
343
295
  period_number_str = f"{self.period_number:02}"
@@ -347,106 +299,106 @@ class TimePeriodHandler:
347
299
  period_number_str = str(self.period_number)
348
300
  return f"{self.year}{self.period_indicator}{period_number_str}"
349
301
 
350
- def sdmx_gregorian_representation(self):
302
+ def sdmx_gregorian_representation(self) -> None:
351
303
  raise NotImplementedError
352
304
 
353
305
 
354
306
  class TimeIntervalHandler:
355
- _date1: str = '0'
356
- _date2: str = 'Z'
307
+ _date1: str = "0"
308
+ _date2: str = "Z"
357
309
 
358
- def __init__(self, date1: str, date2: str):
359
- self.date1 = date1
360
- self.date2 = date2
361
- if date1 > date2:
362
- raise ValueError(f'Invalid Time with duration less than 0 ({self.length} days)')
310
+ def __init__(self, date1: str, date2: str) -> None:
311
+ self.set_date1(date1)
312
+ self.set_date2(date2)
313
+ # if date1 > date2:
314
+ # raise ValueError(f'Invalid Time with duration less than 0 ({self.length} days)')
363
315
 
364
316
  @classmethod
365
- def from_dates(cls, date1: date, date2: date):
317
+ def from_dates(cls, date1: date, date2: date) -> "TimeIntervalHandler":
366
318
  return cls(date1.isoformat(), date2.isoformat())
367
319
 
368
320
  @classmethod
369
- def from_iso_format(cls, dates: str):
370
- return cls(*dates.split('/', maxsplit=1))
321
+ def from_iso_format(cls, dates: str) -> "TimeIntervalHandler":
322
+ return cls(*dates.split("/", maxsplit=1))
371
323
 
372
324
  @property
373
- def date1(self, as_date=False) -> Union[date, str]:
374
- if as_date:
375
- return date.fromisoformat(self._date1)
376
- return self._date1
325
+ def date1(self, as_date: bool = False) -> Union[date, str]:
326
+ return date.fromisoformat(self._date1) if as_date else self._date1
377
327
 
378
328
  @property
379
- def date2(self, as_date=False) -> Union[date, str]:
380
- if as_date:
381
- return date.fromisoformat(self._date2)
382
- return self._date2
329
+ def date2(self, as_date: bool = False) -> Union[date, str]:
330
+ return date.fromisoformat(self._date2) if as_date else self._date2
383
331
 
384
- @date1.setter
385
- def date1(self, value: str):
332
+ # @date1.setter
333
+ def set_date1(self, value: str) -> None:
386
334
  date.fromisoformat(value)
387
- if value > self.date2:
388
- raise ValueError(
389
- f"({value} > {self.date2}). Cannot set date1 with a value greater than date2.")
335
+ if value > self.date2.__str__():
336
+ raise SemanticError("2-1-19-4", date=self.date2, value=value)
337
+ # raise ValueError(f"({value} > {self.date2}).
338
+ # Cannot set date1 with a value greater than date2.")
390
339
  self._date1 = value
391
340
 
392
- @date2.setter
393
- def date2(self, value: str):
341
+ def set_date2(self, value: str) -> None:
394
342
  date.fromisoformat(value)
395
- if value < self.date1:
396
- raise ValueError(
397
- f"({value} < {self.date1}). Cannot set date2 with a value lower than date1.")
343
+ if value < self.date1.__str__():
344
+ raise SemanticError("2-1-19-5", date=self.date1, value=value)
345
+ # raise ValueError(f"({value} < {self.date1}).
346
+ # Cannot set date2 with a value lower than date1.")
398
347
  self._date2 = value
399
348
 
400
349
  @property
401
350
  def length(self) -> int:
402
- date_left = date.fromisoformat(self.date1)
403
- date_right = date.fromisoformat(self.date2)
351
+ date_left = date.fromisoformat(self.date1.__str__())
352
+ date_right = date.fromisoformat(self.date2.__str__())
404
353
  return (date_right - date_left).days
405
354
 
406
355
  __len__ = length
407
356
 
408
- def __str__(self):
357
+ def __str__(self) -> str:
409
358
  return f"{self.date1}/{self.date2}"
410
359
 
411
360
  __repr__ = __str__
412
361
 
413
- def _meta_comparison(self, other, py_op) -> Optional[bool]:
362
+ def _meta_comparison(self, other: Any, py_op: Any) -> Optional[bool]:
414
363
  if pd.isnull(other):
415
364
  return None
416
365
  if isinstance(other, str):
417
366
  if len(other) == 0:
418
367
  return False
419
- other = TimeIntervalHandler(*other.split('/', maxsplit=1))
368
+ other = TimeIntervalHandler(*other.split("/", maxsplit=1))
420
369
  return py_op(self.length, other.length)
421
370
 
422
- def __eq__(self, other) -> bool:
371
+ def __eq__(self, other: Any) -> Optional[bool]: # type: ignore[override]
423
372
  return self._meta_comparison(other, operator.eq)
424
373
 
425
- def __ne__(self, other) -> bool:
374
+ def __ne__(self, other: Any) -> Optional[bool]: # type: ignore[override]
426
375
  return self._meta_comparison(other, operator.ne)
427
376
 
428
- def __lt__(self, other) -> bool:
377
+ def __lt__(self, other: Any) -> Optional[bool]:
429
378
  return self._meta_comparison(other, operator.lt)
430
379
 
431
- def __le__(self, other) -> bool:
380
+ def __le__(self, other: Any) -> Optional[bool]:
432
381
  return self._meta_comparison(other, operator.le)
433
382
 
434
- def __gt__(self, other) -> bool:
383
+ def __gt__(self, other: Any) -> Optional[bool]:
435
384
  return self._meta_comparison(other, operator.gt)
436
385
 
437
- def __ge__(self, other) -> bool:
386
+ def __ge__(self, other: Any) -> Optional[bool]:
438
387
  return self._meta_comparison(other, operator.ge)
439
388
 
440
389
  @classmethod
441
- def from_time_period(cls, value: TimePeriodHandler):
390
+ def from_time_period(cls, value: TimePeriodHandler) -> "TimeIntervalHandler":
442
391
  date1 = period_to_date(value.year, value.period_indicator, value.period_number, start=True)
443
392
  date2 = period_to_date(value.year, value.period_indicator, value.period_number, start=False)
444
393
  return cls.from_dates(date1, date2)
445
394
 
446
395
 
447
- def sort_dataframe_by_period_column(data, name, identifiers_names):
396
+ def sort_dataframe_by_period_column(
397
+ data: pd.DataFrame, name: str, identifiers_names: list[str]
398
+ ) -> pd.DataFrame:
448
399
  """
449
- Sorts dataframe by TimePeriod period_indicator and period_number. Assuming all values are present (only for identifiers)
400
+ Sorts dataframe by TimePeriod period_indicator and period_number.
401
+ Assuming all values are present (only for identifiers)
450
402
  """
451
403
  new_component_name = "@period_number"
452
404
 
@@ -463,11 +415,10 @@ def sort_dataframe_by_period_column(data, name, identifiers_names):
463
415
 
464
416
  identifiers_names.remove("duration_var")
465
417
  identifiers_names.remove(new_component_name)
466
-
467
418
  return data
468
419
 
469
420
 
470
- def next_period(x: TimePeriodHandler):
421
+ def next_period(x: TimePeriodHandler) -> TimePeriodHandler:
471
422
  y = copy.copy(x)
472
423
  if y.period_number == PeriodDuration.periods[x.period_indicator]:
473
424
  y.year += 1
@@ -477,7 +428,7 @@ def next_period(x: TimePeriodHandler):
477
428
  return y
478
429
 
479
430
 
480
- def previous_period(x: TimePeriodHandler):
431
+ def previous_period(x: TimePeriodHandler) -> TimePeriodHandler:
481
432
  y = copy.copy(x)
482
433
  if x.period_number == 1:
483
434
  y.year -= 1
@@ -487,31 +438,32 @@ def previous_period(x: TimePeriodHandler):
487
438
  return y
488
439
 
489
440
 
490
- def shift_period(x: TimePeriodHandler, shift_param: int):
441
+ def shift_period(x: TimePeriodHandler, shift_param: int) -> TimePeriodHandler:
491
442
  if x.period_indicator == "A":
492
443
  x.year += shift_param
493
444
  return x
494
-
495
445
  for _ in range(abs(shift_param)):
496
- if shift_param >= 0:
497
- x = next_period(x)
498
- else:
499
- x = previous_period(x)
446
+ x = next_period(x) if shift_param >= 0 else previous_period(x)
500
447
  return x
501
448
 
502
449
 
503
- def sort_time_period(series: pd.Series):
504
- values_sorted = sorted(list(series.values),
505
- key=lambda s: (s.year, DURATION_MAPPING[s.period_indicator],
506
- s.period_number))
450
+ def sort_time_period(series: Any) -> Any:
451
+ values_sorted = sorted(
452
+ series.to_list(),
453
+ key=lambda s: (s.year, DURATION_MAPPING[s.period_indicator], s.period_number),
454
+ )
507
455
  return pd.Series(values_sorted, name=series.name)
508
456
 
509
457
 
510
- def generate_period_range(start: TimePeriodHandler, end: TimePeriodHandler):
458
+ def generate_period_range(
459
+ start: TimePeriodHandler, end: TimePeriodHandler
460
+ ) -> list[TimePeriodHandler]:
511
461
  period_range = [start]
512
462
  if start.period_indicator != end.period_indicator:
513
- raise Exception("Only same period indicator allowed")
514
-
463
+ raise SemanticError(
464
+ "2-1-19-3", period1=start.period_indicator, period2=end.period_indicator
465
+ )
466
+ # raise Exception("Only same period indicator allowed")
515
467
  if start.period_indicator == "A":
516
468
  for _ in range(end.year - start.year):
517
469
  period_range.append(next_period(period_range[-1]))
@@ -522,90 +474,33 @@ def generate_period_range(start: TimePeriodHandler, end: TimePeriodHandler):
522
474
  return period_range
523
475
 
524
476
 
525
- def period_to_date(year, period_indicator, period_number, start=False):
526
- if period_indicator == 'A':
527
- if start:
528
- return date(year, 1, 1)
529
- else:
530
- return date(year, 12, 31)
531
- if period_indicator == 'S':
532
- if period_number == 1:
533
- if start:
534
- return date(year, 1, 1)
535
- else:
536
- return date(year, 6, 30)
537
- else:
538
- if start:
539
- return date(year, 7, 1)
540
- else:
541
- return date(year, 12, 31)
542
- if period_indicator == 'Q':
543
- if period_number == 1:
544
- if start:
545
- return date(year, 1, 1)
546
- else:
547
- return date(year, 3, 31)
548
- elif period_number == 2:
549
- if start:
550
- return date(year, 4, 1)
551
- else:
552
- return date(year, 6, 30)
553
- elif period_number == 3:
554
- if start:
555
- return date(year, 7, 1)
556
- else:
557
- return date(year, 9, 30)
558
- else:
559
- if start:
560
- return date(year, 10, 1)
561
- else:
562
- return date(year, 12, 31)
563
- if period_indicator == "M":
564
- if start:
565
- return date(year, period_number, 1)
566
- else:
567
- day = int(calendar.monthrange(year, period_number)[1])
568
- return date(year, period_number, day)
569
- if period_indicator == "W": # 0 for Sunday, 1 for Monday in %w
570
- if start:
571
- return dt.strptime(f"{year}-W{period_number}-1", "%G-W%V-%w").date()
572
- else:
573
- return dt.strptime(f"{year}-W{period_number}-0", "%G-W%V-%w").date()
574
- if period_indicator == "D":
575
- return dt.strptime(f"{year}-D{period_number}", "%Y-D%j").date()
576
-
577
- raise ValueError(f'Invalid Period Indicator {period_indicator}')
578
-
579
-
580
- def check_max_date(str_: str):
581
- if pd.isnull(str_) or str_ == 'nan' or str_ == 'NaT':
477
+ def check_max_date(str_: Optional[str]) -> Optional[str]:
478
+ if pd.isnull(str_) or str_ == "nan" or str_ == "NaT":
582
479
  return None
583
480
 
584
- if len(str_) == 9 and str_[7] == '-':
585
- str_ = str_[:-1] + '0' + str_[-1]
481
+ if len(str_) == 9 and str_[7] == "-":
482
+ str_ = str_[:-1] + "0" + str_[-1]
586
483
 
587
484
  # Format 2010-01-01. Prevent passthrough of other ISO 8601 formats.
588
- if len(str_) != 10 or str_[7] != '-':
589
- raise ValueError(f"Invalid date format, must be YYYY-MM-DD: {str_}")
485
+ if len(str_) != 10 or str_[7] != "-":
486
+ raise SemanticError("2-1-19-8", date=str)
487
+ # raise ValueError(f"Invalid date format, must be YYYY-MM-DD: {str_}")
590
488
 
591
489
  result = date.fromisoformat(str_)
592
490
  return result.isoformat()
593
491
 
594
492
 
595
- def str_period_to_date(value: str, start=False) -> date:
493
+ def str_period_to_date(value: str, start: bool = False) -> Any:
596
494
  if len(value) < 6:
597
- if start:
598
- return date(int(value[:4]), 1, 1)
599
- else:
600
- return date(int(value[:4]), 12, 31)
601
-
602
- if start:
603
- return TimePeriodHandler(value).start_date(as_date=False)
604
- else:
605
- return TimePeriodHandler(value).end_date(as_date=False)
495
+ return date(int(value[:4]), 1, 1) if start else date(int(value[:4]), 12, 31)
496
+ return (
497
+ TimePeriodHandler(value).start_date(as_date=False)
498
+ if start
499
+ else (TimePeriodHandler(value).end_date(as_date=False))
500
+ )
606
501
 
607
502
 
608
- def date_to_period_str(date_value: date, period_indicator):
503
+ def date_to_period_str(date_value: date, period_indicator: str) -> Any:
609
504
  if isinstance(date_value, str):
610
505
  date_value = check_max_date(date_value)
611
506
  date_value = date.fromisoformat(date_value)