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.
- vtlengine/API/_InternalApi.py +153 -100
- vtlengine/API/__init__.py +109 -67
- vtlengine/AST/ASTConstructor.py +188 -98
- vtlengine/AST/ASTConstructorModules/Expr.py +306 -200
- vtlengine/AST/ASTConstructorModules/ExprComponents.py +172 -102
- vtlengine/AST/ASTConstructorModules/Terminals.py +158 -95
- vtlengine/AST/ASTEncoders.py +1 -1
- vtlengine/AST/ASTTemplate.py +8 -9
- vtlengine/AST/ASTVisitor.py +8 -12
- vtlengine/AST/DAG/__init__.py +43 -35
- vtlengine/AST/DAG/_words.py +4 -4
- vtlengine/AST/Grammar/lexer.py +732 -142
- vtlengine/AST/Grammar/parser.py +2188 -826
- vtlengine/AST/Grammar/tokens.py +128 -128
- vtlengine/AST/VtlVisitor.py +7 -4
- vtlengine/AST/__init__.py +22 -11
- vtlengine/DataTypes/NumericTypesHandling.py +5 -4
- vtlengine/DataTypes/TimeHandling.py +194 -301
- vtlengine/DataTypes/__init__.py +304 -218
- vtlengine/Exceptions/__init__.py +52 -27
- vtlengine/Exceptions/messages.py +134 -62
- vtlengine/Interpreter/__init__.py +781 -487
- vtlengine/Model/__init__.py +165 -121
- vtlengine/Operators/Aggregation.py +156 -95
- vtlengine/Operators/Analytic.py +115 -59
- vtlengine/Operators/Assignment.py +7 -4
- vtlengine/Operators/Boolean.py +27 -32
- vtlengine/Operators/CastOperator.py +177 -131
- vtlengine/Operators/Clause.py +137 -99
- vtlengine/Operators/Comparison.py +148 -117
- vtlengine/Operators/Conditional.py +149 -98
- vtlengine/Operators/General.py +68 -47
- vtlengine/Operators/HROperators.py +91 -72
- vtlengine/Operators/Join.py +217 -118
- vtlengine/Operators/Numeric.py +89 -44
- vtlengine/Operators/RoleSetter.py +16 -15
- vtlengine/Operators/Set.py +61 -36
- vtlengine/Operators/String.py +213 -139
- vtlengine/Operators/Time.py +334 -216
- vtlengine/Operators/Validation.py +117 -76
- vtlengine/Operators/__init__.py +340 -213
- vtlengine/Utils/__init__.py +195 -40
- vtlengine/__init__.py +1 -1
- vtlengine/files/output/__init__.py +15 -6
- vtlengine/files/output/_time_period_representation.py +10 -9
- vtlengine/files/parser/__init__.py +77 -52
- vtlengine/files/parser/_rfc_dialect.py +6 -5
- vtlengine/files/parser/_time_checking.py +46 -37
- vtlengine-1.0.1.dist-info/METADATA +236 -0
- vtlengine-1.0.1.dist-info/RECORD +58 -0
- {vtlengine-1.0.dist-info → vtlengine-1.0.1.dist-info}/WHEEL +1 -1
- vtlengine-1.0.dist-info/METADATA +0 -104
- vtlengine-1.0.dist-info/RECORD +0 -58
- {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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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,
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 ==
|
|
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 =
|
|
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 =
|
|
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 ==
|
|
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
|
|
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
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
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 ==
|
|
216
|
+
if self.period_indicator == "D":
|
|
269
217
|
if calendar.isleap(self.year):
|
|
270
218
|
if value > 366:
|
|
271
|
-
raise
|
|
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
|
|
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
|
-
|
|
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(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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(
|
|
336
|
-
|
|
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 ==
|
|
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 =
|
|
356
|
-
_date2: str =
|
|
305
|
+
_date1: str = "0"
|
|
306
|
+
_date2: str = "Z"
|
|
357
307
|
|
|
358
|
-
def __init__(self, date1: str, date2: str):
|
|
359
|
-
self.date1
|
|
360
|
-
self.date2
|
|
361
|
-
if date1 > date2:
|
|
362
|
-
|
|
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(
|
|
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
|
|
330
|
+
# @date1.setter
|
|
331
|
+
def set_date1(self, value: str) -> None:
|
|
386
332
|
date.fromisoformat(value)
|
|
387
|
-
if value > self.date2:
|
|
388
|
-
raise
|
|
389
|
-
|
|
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
|
-
|
|
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
|
|
397
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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:
|
|
504
|
-
values_sorted = sorted(
|
|
505
|
-
|
|
506
|
-
|
|
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(
|
|
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
|
|
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
|
|
526
|
-
if
|
|
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] +
|
|
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
|
|
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) ->
|
|
491
|
+
def str_period_to_date(value: str, start: bool = False) -> Any:
|
|
596
492
|
if len(value) < 6:
|
|
597
|
-
if start:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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)
|