vtlengine 1.0.0__py3-none-any.whl → 1.0.1__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 (54) hide show
  1. vtlengine/API/_InternalApi.py +153 -100
  2. vtlengine/API/__init__.py +109 -67
  3. vtlengine/AST/ASTConstructor.py +188 -98
  4. vtlengine/AST/ASTConstructorModules/Expr.py +306 -200
  5. vtlengine/AST/ASTConstructorModules/ExprComponents.py +172 -102
  6. vtlengine/AST/ASTConstructorModules/Terminals.py +158 -95
  7. vtlengine/AST/ASTEncoders.py +1 -1
  8. vtlengine/AST/ASTTemplate.py +8 -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/lexer.py +732 -142
  13. vtlengine/AST/Grammar/parser.py +2188 -826
  14. vtlengine/AST/Grammar/tokens.py +128 -128
  15. vtlengine/AST/VtlVisitor.py +7 -4
  16. vtlengine/AST/__init__.py +22 -11
  17. vtlengine/DataTypes/NumericTypesHandling.py +5 -4
  18. vtlengine/DataTypes/TimeHandling.py +194 -301
  19. vtlengine/DataTypes/__init__.py +304 -218
  20. vtlengine/Exceptions/__init__.py +52 -27
  21. vtlengine/Exceptions/messages.py +134 -62
  22. vtlengine/Interpreter/__init__.py +781 -487
  23. vtlengine/Model/__init__.py +165 -121
  24. vtlengine/Operators/Aggregation.py +156 -95
  25. vtlengine/Operators/Analytic.py +115 -59
  26. vtlengine/Operators/Assignment.py +7 -4
  27. vtlengine/Operators/Boolean.py +27 -32
  28. vtlengine/Operators/CastOperator.py +177 -131
  29. vtlengine/Operators/Clause.py +137 -99
  30. vtlengine/Operators/Comparison.py +148 -117
  31. vtlengine/Operators/Conditional.py +149 -98
  32. vtlengine/Operators/General.py +68 -47
  33. vtlengine/Operators/HROperators.py +91 -72
  34. vtlengine/Operators/Join.py +217 -118
  35. vtlengine/Operators/Numeric.py +89 -44
  36. vtlengine/Operators/RoleSetter.py +16 -15
  37. vtlengine/Operators/Set.py +61 -36
  38. vtlengine/Operators/String.py +213 -139
  39. vtlengine/Operators/Time.py +334 -216
  40. vtlengine/Operators/Validation.py +117 -76
  41. vtlengine/Operators/__init__.py +340 -213
  42. vtlengine/Utils/__init__.py +195 -40
  43. vtlengine/__init__.py +1 -1
  44. vtlengine/files/output/__init__.py +15 -6
  45. vtlengine/files/output/_time_period_representation.py +10 -9
  46. vtlengine/files/parser/__init__.py +77 -52
  47. vtlengine/files/parser/_rfc_dialect.py +6 -5
  48. vtlengine/files/parser/_time_checking.py +46 -37
  49. vtlengine-1.0.1.dist-info/METADATA +236 -0
  50. vtlengine-1.0.1.dist-info/RECORD +58 -0
  51. {vtlengine-1.0.dist-info → vtlengine-1.0.1.dist-info}/WHEEL +1 -1
  52. vtlengine-1.0.dist-info/METADATA +0 -104
  53. vtlengine-1.0.dist-info/RECORD +0 -58
  54. {vtlengine-1.0.dist-info → vtlengine-1.0.1.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,24 @@ 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:
206
148
  if "-" in period:
207
- self.year, self.period_indicator, self.period_number = from_input_customer_support_to_internal(
208
- period)
149
+ self.year, self.period_indicator, self.period_number = (
150
+ from_input_customer_support_to_internal(period)
151
+ )
209
152
  else:
210
153
  self.year = int(period[:4])
211
154
  if len(period) > 4:
212
155
  self.period_indicator = period[4]
213
156
  else:
214
- self.period_indicator = 'A'
157
+ self.period_indicator = "A"
215
158
  if len(period) > 5:
216
159
  self.period_number = int(period[5:])
217
160
  else:
218
161
  self.period_number = 1
219
162
 
220
- def __str__(self):
221
- if self.period_indicator == 'A':
163
+ def __str__(self) -> str:
164
+ if self.period_indicator == "A":
222
165
  # return f"{self.year}{self.period_indicator}"
223
166
  return f"{self.year}" # Drop A from exit time period year
224
167
  if self.period_indicator in ["W", "M"]:
@@ -230,16 +173,17 @@ class TimePeriodHandler:
230
173
  return f"{self.year}-{self.period_indicator}{period_number_str}"
231
174
 
232
175
  @staticmethod
233
- def _check_year(year: int):
176
+ def _check_year(year: int) -> None:
234
177
  if year < 1900 or year > 9999:
235
- raise ValueError(f'Invalid year {year}, must be between 1900 and 9999.')
178
+ raise SemanticError("2-1-19-10", year=year)
179
+ # raise ValueError(f'Invalid year {year}, must be between 1900 and 9999.')
236
180
 
237
181
  @property
238
182
  def year(self) -> int:
239
183
  return self._year
240
184
 
241
185
  @year.setter
242
- def year(self, value: int):
186
+ def year(self, value: int) -> None:
243
187
  self._check_year(value)
244
188
  self._year = value
245
189
 
@@ -248,10 +192,9 @@ class TimePeriodHandler:
248
192
  return self._period_indicator
249
193
 
250
194
  @period_indicator.setter
251
- def period_indicator(self, value: str):
195
+ def period_indicator(self, value: str) -> None:
252
196
  if value not in PeriodDuration():
253
- raise ValueError(
254
- f'Cannot set period indicator as {value}. Possible values: {PeriodDuration().member_names}')
197
+ raise SemanticError("2-1-19-2", period=value)
255
198
  self._period_indicator = value
256
199
 
257
200
  @property
@@ -259,85 +202,92 @@ class TimePeriodHandler:
259
202
  return self._period_number
260
203
 
261
204
  @period_number.setter
262
- def period_number(self, value: int):
205
+ def period_number(self, value: int) -> None:
263
206
  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}.')
207
+ raise SemanticError(
208
+ "2-1-19-7",
209
+ periods=PeriodDuration.periods[self.period_indicator],
210
+ period_inidcator=self.period_indicator,
211
+ )
212
+ # raise ValueError(f'Period Number must be between 1 and '
213
+ # f'{PeriodDuration.periods[self.period_indicator]} '
214
+ # f'for period indicator {self.period_indicator}.')
267
215
  # check day is correct for year
268
- if self.period_indicator == 'D':
216
+ if self.period_indicator == "D":
269
217
  if calendar.isleap(self.year):
270
218
  if value > 366:
271
- raise ValueError(f'Invalid day {value} for year {self.year}.')
219
+ raise SemanticError("2-1-19-9", day=value, year=self.year)
220
+ # raise ValueError(f'Invalid day {value} for year {self.year}.')
272
221
  else:
273
222
  if value > 365:
274
- raise ValueError(f'Invalid day {value} for year {self.year}.')
223
+ raise SemanticError("2-1-19-9", day=value, year=self.year)
224
+ # raise ValueError(f'Invalid day {value} for year {self.year}.')
275
225
  self._period_number = value
276
226
 
277
- def _meta_comparison(self, other, py_op) -> Optional[bool]:
227
+ def _meta_comparison(self, other: Any, py_op: Any) -> Optional[bool]:
278
228
  if pd.isnull(other):
279
229
  return None
280
230
  if isinstance(other, str):
281
231
  if len(other) == 0:
282
232
  return False
283
233
  other = TimePeriodHandler(other)
234
+ return py_op(
235
+ DURATION_MAPPING[self.period_indicator], DURATION_MAPPING[other.period_indicator]
236
+ )
284
237
 
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]:
238
+ def start_date(self, as_date: bool = False) -> Union[date, str]:
289
239
  """
290
240
  Gets the starting date of the Period
291
241
  """
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]:
242
+ date_value = period_to_date(
243
+ year=self.year,
244
+ period_indicator=self.period_indicator,
245
+ period_number=self.period_number,
246
+ start=True,
247
+ )
248
+ return date_value if as_date else date_value.isoformat()
249
+
250
+ def end_date(self, as_date: bool = False) -> Union[date, str]:
301
251
  """
302
252
  Gets the ending date of the Period
303
253
  """
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:
254
+ date_value = period_to_date(
255
+ year=self.year,
256
+ period_indicator=self.period_indicator,
257
+ period_number=self.period_number,
258
+ start=False,
259
+ )
260
+ return date_value if as_date else date_value.isoformat()
261
+
262
+ def __eq__(self, other: Any) -> Optional[bool]: # type: ignore[override]
313
263
  return self._meta_comparison(other, operator.eq)
314
264
 
315
- def __ne__(self, other) -> bool:
265
+ def __ne__(self, other: Any) -> Optional[bool]: # type: ignore[override]
316
266
  return not self._meta_comparison(other, operator.eq)
317
267
 
318
- def __lt__(self, other) -> bool:
268
+ def __lt__(self, other: Any) -> Optional[bool]:
319
269
  return self._meta_comparison(other, operator.lt)
320
270
 
321
- def __le__(self, other) -> bool:
271
+ def __le__(self, other: Any) -> Optional[bool]:
322
272
  return self._meta_comparison(other, operator.le)
323
273
 
324
- def __gt__(self, other) -> bool:
274
+ def __gt__(self, other: Any) -> Optional[bool]:
325
275
  return self._meta_comparison(other, operator.gt)
326
276
 
327
- def __ge__(self, other) -> bool:
277
+ def __ge__(self, other: Any) -> Optional[bool]:
328
278
  return self._meta_comparison(other, operator.ge)
329
279
 
330
- def change_indicator(self, new_indicator):
280
+ def change_indicator(self, new_indicator: str) -> None:
331
281
  if self.period_indicator == new_indicator:
332
282
  return
333
283
  date_value = period_to_date(self.year, self.period_indicator, self.period_number)
334
284
  self.period_indicator = new_indicator
335
- self.period_number = date_to_period(date_value,
336
- period_indicator=new_indicator).period_number
285
+ self.period_number = date_to_period(
286
+ date_value, period_indicator=new_indicator
287
+ ).period_number
337
288
 
338
- def vtl_representation(self):
339
- if self.period_indicator == 'A':
340
- # return f"{self.year}{self.period_indicator}"
289
+ def vtl_representation(self) -> str:
290
+ if self.period_indicator == "A":
341
291
  return f"{self.year}" # Drop A from exit time period year
342
292
  if self.period_indicator in ["W", "M"]:
343
293
  period_number_str = f"{self.period_number:02}"
@@ -347,106 +297,106 @@ class TimePeriodHandler:
347
297
  period_number_str = str(self.period_number)
348
298
  return f"{self.year}{self.period_indicator}{period_number_str}"
349
299
 
350
- def sdmx_gregorian_representation(self):
300
+ def sdmx_gregorian_representation(self) -> None:
351
301
  raise NotImplementedError
352
302
 
353
303
 
354
304
  class TimeIntervalHandler:
355
- _date1: str = '0'
356
- _date2: str = 'Z'
305
+ _date1: str = "0"
306
+ _date2: str = "Z"
357
307
 
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)')
308
+ def __init__(self, date1: str, date2: str) -> None:
309
+ self.set_date1(date1)
310
+ self.set_date2(date2)
311
+ # if date1 > date2:
312
+ # raise ValueError(f'Invalid Time with duration less than 0 ({self.length} days)')
363
313
 
364
314
  @classmethod
365
- def from_dates(cls, date1: date, date2: date):
315
+ def from_dates(cls, date1: date, date2: date) -> "TimeIntervalHandler":
366
316
  return cls(date1.isoformat(), date2.isoformat())
367
317
 
368
318
  @classmethod
369
- def from_iso_format(cls, dates: str):
370
- return cls(*dates.split('/', maxsplit=1))
319
+ def from_iso_format(cls, dates: str) -> "TimeIntervalHandler":
320
+ return cls(*dates.split("/", maxsplit=1))
371
321
 
372
322
  @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
323
+ def date1(self, as_date: bool = False) -> Union[date, str]:
324
+ return date.fromisoformat(self._date1) if as_date else self._date1
377
325
 
378
326
  @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
327
+ def date2(self, as_date: bool = False) -> Union[date, str]:
328
+ return date.fromisoformat(self._date2) if as_date else self._date2
383
329
 
384
- @date1.setter
385
- def date1(self, value: str):
330
+ # @date1.setter
331
+ def set_date1(self, value: str) -> None:
386
332
  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.")
333
+ if value > self.date2.__str__():
334
+ raise SemanticError("2-1-19-4", date=self.date2, value=value)
335
+ # raise ValueError(f"({value} > {self.date2}).
336
+ # Cannot set date1 with a value greater than date2.")
390
337
  self._date1 = value
391
338
 
392
- @date2.setter
393
- def date2(self, value: str):
339
+ def set_date2(self, value: str) -> None:
394
340
  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.")
341
+ if value < self.date1.__str__():
342
+ raise SemanticError("2-1-19-5", date=self.date1, value=value)
343
+ # raise ValueError(f"({value} < {self.date1}).
344
+ # Cannot set date2 with a value lower than date1.")
398
345
  self._date2 = value
399
346
 
400
347
  @property
401
348
  def length(self) -> int:
402
- date_left = date.fromisoformat(self.date1)
403
- date_right = date.fromisoformat(self.date2)
349
+ date_left = date.fromisoformat(self.date1.__str__())
350
+ date_right = date.fromisoformat(self.date2.__str__())
404
351
  return (date_right - date_left).days
405
352
 
406
353
  __len__ = length
407
354
 
408
- def __str__(self):
355
+ def __str__(self) -> str:
409
356
  return f"{self.date1}/{self.date2}"
410
357
 
411
358
  __repr__ = __str__
412
359
 
413
- def _meta_comparison(self, other, py_op) -> Optional[bool]:
360
+ def _meta_comparison(self, other: Any, py_op: Any) -> Optional[bool]:
414
361
  if pd.isnull(other):
415
362
  return None
416
363
  if isinstance(other, str):
417
364
  if len(other) == 0:
418
365
  return False
419
- other = TimeIntervalHandler(*other.split('/', maxsplit=1))
366
+ other = TimeIntervalHandler(*other.split("/", maxsplit=1))
420
367
  return py_op(self.length, other.length)
421
368
 
422
- def __eq__(self, other) -> bool:
369
+ def __eq__(self, other: Any) -> Optional[bool]: # type: ignore[override]
423
370
  return self._meta_comparison(other, operator.eq)
424
371
 
425
- def __ne__(self, other) -> bool:
372
+ def __ne__(self, other: Any) -> Optional[bool]: # type: ignore[override]
426
373
  return self._meta_comparison(other, operator.ne)
427
374
 
428
- def __lt__(self, other) -> bool:
375
+ def __lt__(self, other: Any) -> Optional[bool]:
429
376
  return self._meta_comparison(other, operator.lt)
430
377
 
431
- def __le__(self, other) -> bool:
378
+ def __le__(self, other: Any) -> Optional[bool]:
432
379
  return self._meta_comparison(other, operator.le)
433
380
 
434
- def __gt__(self, other) -> bool:
381
+ def __gt__(self, other: Any) -> Optional[bool]:
435
382
  return self._meta_comparison(other, operator.gt)
436
383
 
437
- def __ge__(self, other) -> bool:
384
+ def __ge__(self, other: Any) -> Optional[bool]:
438
385
  return self._meta_comparison(other, operator.ge)
439
386
 
440
387
  @classmethod
441
- def from_time_period(cls, value: TimePeriodHandler):
388
+ def from_time_period(cls, value: TimePeriodHandler) -> "TimeIntervalHandler":
442
389
  date1 = period_to_date(value.year, value.period_indicator, value.period_number, start=True)
443
390
  date2 = period_to_date(value.year, value.period_indicator, value.period_number, start=False)
444
391
  return cls.from_dates(date1, date2)
445
392
 
446
393
 
447
- def sort_dataframe_by_period_column(data, name, identifiers_names):
394
+ def sort_dataframe_by_period_column(
395
+ data: pd.DataFrame, name: str, identifiers_names: list[str]
396
+ ) -> pd.DataFrame:
448
397
  """
449
- Sorts dataframe by TimePeriod period_indicator and period_number. Assuming all values are present (only for identifiers)
398
+ Sorts dataframe by TimePeriod period_indicator and period_number.
399
+ Assuming all values are present (only for identifiers)
450
400
  """
451
401
  new_component_name = "@period_number"
452
402
 
@@ -463,11 +413,10 @@ def sort_dataframe_by_period_column(data, name, identifiers_names):
463
413
 
464
414
  identifiers_names.remove("duration_var")
465
415
  identifiers_names.remove(new_component_name)
466
-
467
416
  return data
468
417
 
469
418
 
470
- def next_period(x: TimePeriodHandler):
419
+ def next_period(x: TimePeriodHandler) -> TimePeriodHandler:
471
420
  y = copy.copy(x)
472
421
  if y.period_number == PeriodDuration.periods[x.period_indicator]:
473
422
  y.year += 1
@@ -477,7 +426,7 @@ def next_period(x: TimePeriodHandler):
477
426
  return y
478
427
 
479
428
 
480
- def previous_period(x: TimePeriodHandler):
429
+ def previous_period(x: TimePeriodHandler) -> TimePeriodHandler:
481
430
  y = copy.copy(x)
482
431
  if x.period_number == 1:
483
432
  y.year -= 1
@@ -487,31 +436,32 @@ def previous_period(x: TimePeriodHandler):
487
436
  return y
488
437
 
489
438
 
490
- def shift_period(x: TimePeriodHandler, shift_param: int):
439
+ def shift_period(x: TimePeriodHandler, shift_param: int) -> TimePeriodHandler:
491
440
  if x.period_indicator == "A":
492
441
  x.year += shift_param
493
442
  return x
494
-
495
443
  for _ in range(abs(shift_param)):
496
- if shift_param >= 0:
497
- x = next_period(x)
498
- else:
499
- x = previous_period(x)
444
+ x = next_period(x) if shift_param >= 0 else previous_period(x)
500
445
  return x
501
446
 
502
447
 
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))
448
+ def sort_time_period(series: Any) -> Any:
449
+ values_sorted = sorted(
450
+ series.to_list(),
451
+ key=lambda s: (s.year, DURATION_MAPPING[s.period_indicator], s.period_number),
452
+ )
507
453
  return pd.Series(values_sorted, name=series.name)
508
454
 
509
455
 
510
- def generate_period_range(start: TimePeriodHandler, end: TimePeriodHandler):
456
+ def generate_period_range(
457
+ start: TimePeriodHandler, end: TimePeriodHandler
458
+ ) -> list[TimePeriodHandler]:
511
459
  period_range = [start]
512
460
  if start.period_indicator != end.period_indicator:
513
- raise Exception("Only same period indicator allowed")
514
-
461
+ raise SemanticError(
462
+ "2-1-19-3", period1=start.period_indicator, period2=end.period_indicator
463
+ )
464
+ # raise Exception("Only same period indicator allowed")
515
465
  if start.period_indicator == "A":
516
466
  for _ in range(end.year - start.year):
517
467
  period_range.append(next_period(period_range[-1]))
@@ -522,90 +472,33 @@ def generate_period_range(start: TimePeriodHandler, end: TimePeriodHandler):
522
472
  return period_range
523
473
 
524
474
 
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':
475
+ def check_max_date(str_: Optional[str]) -> Optional[str]:
476
+ if pd.isnull(str_) or str_ == "nan" or str_ == "NaT":
582
477
  return None
583
478
 
584
- if len(str_) == 9 and str_[7] == '-':
585
- str_ = str_[:-1] + '0' + str_[-1]
479
+ if len(str_) == 9 and str_[7] == "-":
480
+ str_ = str_[:-1] + "0" + str_[-1]
586
481
 
587
482
  # 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_}")
483
+ if len(str_) != 10 or str_[7] != "-":
484
+ raise SemanticError("2-1-19-8", date=str)
485
+ # raise ValueError(f"Invalid date format, must be YYYY-MM-DD: {str_}")
590
486
 
591
487
  result = date.fromisoformat(str_)
592
488
  return result.isoformat()
593
489
 
594
490
 
595
- def str_period_to_date(value: str, start=False) -> date:
491
+ def str_period_to_date(value: str, start: bool = False) -> Any:
596
492
  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)
493
+ return date(int(value[:4]), 1, 1) if start else date(int(value[:4]), 12, 31)
494
+ return (
495
+ TimePeriodHandler(value).start_date(as_date=False)
496
+ if start
497
+ else (TimePeriodHandler(value).end_date(as_date=False))
498
+ )
606
499
 
607
500
 
608
- def date_to_period_str(date_value: date, period_indicator):
501
+ def date_to_period_str(date_value: date, period_indicator: str) -> Any:
609
502
  if isinstance(date_value, str):
610
503
  date_value = check_max_date(date_value)
611
504
  date_value = date.fromisoformat(date_value)