vtlengine 1.0.3rc3__py3-none-any.whl → 1.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 (53) hide show
  1. vtlengine/API/_InternalApi.py +288 -61
  2. vtlengine/API/__init__.py +269 -71
  3. vtlengine/API/data/schema/json_schema_2.1.json +116 -0
  4. vtlengine/AST/ASTComment.py +56 -0
  5. vtlengine/AST/ASTConstructor.py +76 -22
  6. vtlengine/AST/ASTConstructorModules/Expr.py +238 -120
  7. vtlengine/AST/ASTConstructorModules/ExprComponents.py +126 -61
  8. vtlengine/AST/ASTConstructorModules/Terminals.py +97 -42
  9. vtlengine/AST/ASTConstructorModules/__init__.py +50 -0
  10. vtlengine/AST/ASTEncoders.py +5 -1
  11. vtlengine/AST/ASTString.py +608 -0
  12. vtlengine/AST/ASTTemplate.py +28 -2
  13. vtlengine/AST/DAG/__init__.py +10 -4
  14. vtlengine/AST/Grammar/lexer.py +0 -1
  15. vtlengine/AST/Grammar/parser.py +185 -440
  16. vtlengine/AST/VtlVisitor.py +0 -1
  17. vtlengine/AST/__init__.py +127 -14
  18. vtlengine/DataTypes/TimeHandling.py +50 -15
  19. vtlengine/DataTypes/__init__.py +79 -7
  20. vtlengine/Exceptions/__init__.py +3 -5
  21. vtlengine/Exceptions/messages.py +74 -105
  22. vtlengine/Interpreter/__init__.py +136 -46
  23. vtlengine/Model/__init__.py +14 -11
  24. vtlengine/Operators/Aggregation.py +17 -9
  25. vtlengine/Operators/Analytic.py +64 -20
  26. vtlengine/Operators/Assignment.py +0 -1
  27. vtlengine/Operators/CastOperator.py +44 -44
  28. vtlengine/Operators/Clause.py +16 -10
  29. vtlengine/Operators/Comparison.py +20 -12
  30. vtlengine/Operators/Conditional.py +47 -15
  31. vtlengine/Operators/General.py +9 -4
  32. vtlengine/Operators/HROperators.py +4 -14
  33. vtlengine/Operators/Join.py +15 -14
  34. vtlengine/Operators/Numeric.py +32 -26
  35. vtlengine/Operators/RoleSetter.py +6 -2
  36. vtlengine/Operators/Set.py +12 -8
  37. vtlengine/Operators/String.py +9 -9
  38. vtlengine/Operators/Time.py +145 -124
  39. vtlengine/Operators/Validation.py +10 -4
  40. vtlengine/Operators/__init__.py +56 -69
  41. vtlengine/Utils/__init__.py +55 -1
  42. vtlengine/__extras_check.py +17 -0
  43. vtlengine/__init__.py +2 -2
  44. vtlengine/files/output/__init__.py +2 -1
  45. vtlengine/files/output/_time_period_representation.py +2 -1
  46. vtlengine/files/parser/__init__.py +52 -46
  47. vtlengine/files/parser/_time_checking.py +4 -4
  48. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1.dist-info}/METADATA +21 -17
  49. vtlengine-1.1.dist-info/RECORD +61 -0
  50. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1.dist-info}/WHEEL +1 -1
  51. vtlengine/DataTypes/NumericTypesHandling.py +0 -38
  52. vtlengine-1.0.3rc3.dist-info/RECORD +0 -58
  53. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1.dist-info}/LICENSE.md +0 -0
@@ -31,7 +31,6 @@ class Unary(Operator.Unary):
31
31
 
32
32
  @classmethod
33
33
  def op_func(cls, x: Any) -> Any:
34
-
35
34
  x = "" if pd.isnull(x) else str(x)
36
35
  return cls.py_op(x)
37
36
 
@@ -116,7 +115,6 @@ class Concatenate(Binary):
116
115
 
117
116
 
118
117
  class Parameterized(Unary):
119
-
120
118
  @classmethod
121
119
  def validate(cls, *args: Any) -> Any:
122
120
  operand: Operator.ALL_MODEL_DATA_TYPES
@@ -353,7 +351,6 @@ class Instr(Parameterized):
353
351
  param2: Optional[Operator.ALL_MODEL_DATA_TYPES] = None,
354
352
  param3: Optional[Operator.ALL_MODEL_DATA_TYPES] = None,
355
353
  ) -> Any:
356
-
357
354
  if (
358
355
  isinstance(param1, Dataset)
359
356
  or isinstance(param2, Dataset)
@@ -395,7 +392,10 @@ class Instr(Parameterized):
395
392
  else:
396
393
  if not check_unary_implicit_promotion(data_type, Integer):
397
394
  raise SemanticError(
398
- "1-1-18-4", op=cls.op, param_type="Occurrence", correct_type="Integer"
395
+ "1-1-18-4",
396
+ op=cls.op,
397
+ param_type="Occurrence",
398
+ correct_type="Integer",
399
399
  )
400
400
  if isinstance(param, DataComponent):
401
401
  if param.data is not None:
@@ -408,9 +408,7 @@ class Instr(Parameterized):
408
408
  if position == 2 and not pd.isnull(param) and param < 1:
409
409
  raise SemanticError("1-1-18-4", op=cls.op, param_type="Start", correct_type=">= 1")
410
410
  elif position == 3 and not pd.isnull(param) and param < 1:
411
- raise SemanticError(
412
- "1-1-18-4", op=cls.op, param_type="Occurrence", correct_type=">= 1"
413
- )
411
+ raise SemanticError("1-1-18-4", op=cls.op, param_type="Occurrence", correct_type=">= 1")
414
412
 
415
413
  @classmethod
416
414
  def apply_operation_series_scalar(
@@ -529,7 +527,6 @@ class Instr(Parameterized):
529
527
  param2: Optional[Any],
530
528
  param3: Optional[Any],
531
529
  ) -> Any:
532
-
533
530
  if pd.isnull(x):
534
531
  return None
535
532
  return cls.py_op(x, param1, param2, param3)
@@ -560,7 +557,10 @@ class Instr(Parameterized):
560
557
  else:
561
558
  # OPERATORS_STRINGOPERATORS.93
562
559
  raise SemanticError(
563
- "1-1-18-4", op=cls.op, param_type="Occurrence", correct_type="Integer"
560
+ "1-1-18-4",
561
+ op=cls.op,
562
+ param_type="Occurrence",
563
+ correct_type="Integer",
564
564
  )
565
565
  else:
566
566
  occurrence = 0
@@ -33,7 +33,7 @@ from vtlengine.DataTypes import (
33
33
  unary_implicit_promotion,
34
34
  )
35
35
  from vtlengine.DataTypes.TimeHandling import (
36
- DURATION_MAPPING,
36
+ PERIOD_IND_MAPPING,
37
37
  TimePeriodHandler,
38
38
  date_to_period,
39
39
  period_to_date,
@@ -57,14 +57,17 @@ class Time(Operators.Operator):
57
57
  op = FLOW_TO_STOCK
58
58
 
59
59
  @classmethod
60
- def _get_time_id(cls, operand: Dataset) -> Optional[str]:
60
+ def _get_time_id(cls, operand: Dataset) -> str:
61
61
  reference_id = None
62
+ identifiers = operand.get_identifiers()
63
+ if len(identifiers) == 0:
64
+ raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
62
65
  for id in operand.get_identifiers():
63
66
  if id.data_type in cls.TIME_DATA_TYPES:
64
67
  if reference_id is not None:
65
68
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
66
69
  reference_id = id.name
67
- return reference_id
70
+ return str(reference_id)
68
71
 
69
72
  @classmethod
70
73
  def sort_by_time(cls, operand: Dataset) -> Optional[pd.DataFrame]:
@@ -98,7 +101,8 @@ class Time(Operators.Operator):
98
101
  months_deltas = differences.apply(lambda x: x.days // 30)
99
102
  days_deltas = differences.apply(lambda x: x.days)
100
103
  min_months = min(
101
- (diff for diff in months_deltas if diff > 0 and diff % 12 != 0), default=None
104
+ (diff for diff in months_deltas if diff > 0 and diff % 12 != 0),
105
+ default=None,
102
106
  )
103
107
  min_days = min(
104
108
  (diff for diff in days_deltas if diff > 0 and diff % 365 != 0 and diff % 366 != 0),
@@ -118,7 +122,6 @@ class Time(Operators.Operator):
118
122
 
119
123
 
120
124
  class Unary(Time):
121
-
122
125
  @classmethod
123
126
  def validate(cls, operand: Any) -> Any:
124
127
  if not isinstance(operand, Dataset):
@@ -182,7 +185,7 @@ class Period_indicator(Unary):
182
185
  def validate(cls, operand: Any) -> Any:
183
186
  if isinstance(operand, Dataset):
184
187
  time_id = cls._get_time_id(operand)
185
- if time_id is None or operand.components[time_id].data_type != TimePeriod:
188
+ if operand.components[time_id].data_type != TimePeriod:
186
189
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time period dataset")
187
190
  result_components = {
188
191
  comp.name: comp
@@ -190,7 +193,10 @@ class Period_indicator(Unary):
190
193
  if comp.role == Role.IDENTIFIER
191
194
  }
192
195
  result_components["duration_var"] = Component(
193
- name="duration_var", data_type=Duration, role=Role.MEASURE, nullable=True
196
+ name="duration_var",
197
+ data_type=Duration,
198
+ role=Role.MEASURE,
199
+ nullable=True,
194
200
  )
195
201
  return Dataset(name="result", components=result_components, data=None)
196
202
  # DataComponent and Scalar validation
@@ -202,7 +208,7 @@ class Period_indicator(Unary):
202
208
 
203
209
  @classmethod
204
210
  def evaluate(
205
- cls, operand: Union[Dataset, DataComponent, Scalar, str]
211
+ cls, operand: Union[Dataset, DataComponent, Scalar, str]
206
212
  ) -> Union[Dataset, DataComponent, Scalar, str]:
207
213
  result = cls.validate(operand)
208
214
  if isinstance(operand, str):
@@ -220,13 +226,12 @@ class Period_indicator(Unary):
220
226
  if (operand.data is not None)
221
227
  else pd.Series()
222
228
  )
223
- period_series: Any = result.data[cls.time_id].map(cls._get_period) # type: ignore[index]
229
+ period_series: Any = result.data[cls.time_id].map(cls._get_period)
224
230
  result.data["duration_var"] = period_series
225
231
  return result
226
232
 
227
233
 
228
234
  class Parametrized(Time):
229
-
230
235
  @classmethod
231
236
  def validate(cls, operand: Any, param: Any) -> Any:
232
237
  pass
@@ -237,14 +242,12 @@ class Parametrized(Time):
237
242
 
238
243
 
239
244
  class Flow_to_stock(Unary):
240
-
241
245
  @classmethod
242
246
  def py_op(cls, x: Any) -> Any:
243
247
  return x.cumsum().fillna(x)
244
248
 
245
249
 
246
250
  class Stock_to_flow(Unary):
247
-
248
251
  @classmethod
249
252
  def py_op(cls, x: Any) -> Any:
250
253
  return x.diff().fillna(x)
@@ -299,7 +302,6 @@ class Fill_time_series(Binary):
299
302
 
300
303
  @classmethod
301
304
  def max_min_from_period(cls, data: pd.DataFrame, mode: str = "all") -> Dict[str, Any]:
302
-
303
305
  result_dict: Dict[Any, Any] = {}
304
306
  data = data.assign(
305
307
  Periods_col=data[cls.time_id].apply(cls._get_period),
@@ -369,7 +371,10 @@ class Fill_time_series(Binary):
369
371
  else:
370
372
  if period in period_limits["min"] and period in period_limits["max"]:
371
373
  vals = list(
372
- range(period_limits["min"][period], period_limits["max"][period] + 1)
374
+ range(
375
+ period_limits["min"][period],
376
+ period_limits["max"][period] + 1,
377
+ )
373
378
  )
374
379
  filled_data.extend(
375
380
  cls.fill_periods_rows(group_df, period, years, vals=vals)
@@ -385,7 +390,11 @@ class Fill_time_series(Binary):
385
390
 
386
391
  @classmethod
387
392
  def fill_periods_rows(
388
- cls, group_df: Any, period: str, years: List[int], vals: Optional[List[int]] = None
393
+ cls,
394
+ group_df: Any,
395
+ period: str,
396
+ years: List[int],
397
+ vals: Optional[List[int]] = None,
389
398
  ) -> List[Any]:
390
399
  rows = []
391
400
  for year in years:
@@ -398,7 +407,7 @@ class Fill_time_series(Binary):
398
407
 
399
408
  @classmethod
400
409
  def create_period_row(
401
- cls, group_df: Any, period: str, year: int, val: Optional[int] = None
410
+ cls, group_df: Any, period: str, year: int, val: Optional[int] = None
402
411
  ) -> Any:
403
412
  row = group_df.iloc[0].copy()
404
413
  row[cls.time_id] = f"{year}" if period == "A" else f"{year}-{period}{val:d}"
@@ -436,9 +445,7 @@ class Fill_time_series(Binary):
436
445
  date_format = None
437
446
  filled_data = []
438
447
 
439
- def create_filled_dates(
440
- group: Any, min_max: Dict[str, Any]
441
- ) -> (pd.DataFrame, str): # type: ignore[syntax]
448
+ def create_filled_dates(group: Any, min_max: Dict[str, Any]) -> (pd.DataFrame, str): # type: ignore[syntax]
442
449
  date_range = pd.date_range(start=min_max["min"], end=min_max["max"], freq=min_frequency)
443
450
  date_df = pd.DataFrame(date_range, columns=[cls.time_id])
444
451
  date_df[cls.other_ids] = group.iloc[0][cls.other_ids]
@@ -480,7 +487,7 @@ class Fill_time_series(Binary):
480
487
 
481
488
  @classmethod
482
489
  def fill_time_intervals(
483
- cls, data: pd.DataFrame, fill_type: str, frequency: str
490
+ cls, data: pd.DataFrame, fill_type: str, frequency: str
484
491
  ) -> pd.DataFrame:
485
492
  result_data = cls.time_filler(data, fill_type, frequency)
486
493
  not_na = result_data[cls.measures].notna().any(axis=1)
@@ -540,9 +547,7 @@ class Time_Shift(Binary):
540
547
  shift_value = int(shift_value.value)
541
548
  cls.time_id = cls._get_time_id(result)
542
549
 
543
- data_type: Any = (
544
- result.components[cls.time_id].data_type if isinstance(cls.time_id, str) else None
545
- )
550
+ data_type: Any = result.components[cls.time_id].data_type
546
551
 
547
552
  if data_type == Date:
548
553
  freq = cls.find_min_frequency(
@@ -588,7 +593,7 @@ class Time_Shift(Binary):
588
593
 
589
594
  @classmethod
590
595
  def shift_period(
591
- cls, period_str: str, shift_value: int, frequency: Optional[int] = None
596
+ cls, period_str: str, shift_value: int, frequency: Optional[int] = None
592
597
  ) -> str:
593
598
  period_type = cls._get_period(period_str)
594
599
 
@@ -628,7 +633,7 @@ class Time_Aggregation(Time):
628
633
 
629
634
  @classmethod
630
635
  def _check_duration(cls, value: str) -> None:
631
- if value not in DURATION_MAPPING:
636
+ if value not in PERIOD_IND_MAPPING:
632
637
  raise SemanticError("1-1-19-3", op=cls.op, param="duration")
633
638
 
634
639
  @classmethod
@@ -636,13 +641,13 @@ class Time_Aggregation(Time):
636
641
  cls._check_duration(period_to)
637
642
  if period_from is not None:
638
643
  cls._check_duration(period_from)
639
- if DURATION_MAPPING[period_to] <= DURATION_MAPPING[period_from]:
644
+ if PERIOD_IND_MAPPING[period_to] <= PERIOD_IND_MAPPING[period_from]:
640
645
  # OPERATORS_TIMEOPERATORS.19
641
646
  raise SemanticError("1-1-19-4", op=cls.op, value_1=period_from, value_2=period_to)
642
647
 
643
648
  @classmethod
644
649
  def dataset_validation(
645
- cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
650
+ cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
646
651
  ) -> Dataset:
647
652
  # TODO: Review with VTL TF as this makes no sense
648
653
 
@@ -661,7 +666,10 @@ class Time_Aggregation(Time):
661
666
  count_time_types += 1
662
667
  if count_time_types != 1:
663
668
  raise SemanticError(
664
- "1-1-19-9", op=cls.op, comp_type="dataset", param="single time identifier"
669
+ "1-1-19-9",
670
+ op=cls.op,
671
+ comp_type="dataset",
672
+ param="single time identifier",
665
673
  )
666
674
 
667
675
  if count_time_types != 1:
@@ -679,7 +687,11 @@ class Time_Aggregation(Time):
679
687
 
680
688
  @classmethod
681
689
  def component_validation(
682
- cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
690
+ cls,
691
+ operand: DataComponent,
692
+ period_from: Optional[str],
693
+ period_to: str,
694
+ conf: str,
683
695
  ) -> DataComponent:
684
696
  if operand.data_type not in cls.TIME_DATA_TYPES:
685
697
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time component")
@@ -692,7 +704,7 @@ class Time_Aggregation(Time):
692
704
 
693
705
  @classmethod
694
706
  def scalar_validation(
695
- cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
707
+ cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
696
708
  ) -> Scalar:
697
709
  if operand.data_type not in cls.TIME_DATA_TYPES:
698
710
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time scalar")
@@ -701,12 +713,12 @@ class Time_Aggregation(Time):
701
713
 
702
714
  @classmethod
703
715
  def _execute_time_aggregation(
704
- cls,
705
- value: str,
706
- data_type: Type[ScalarType],
707
- period_from: Optional[str],
708
- period_to: str,
709
- conf: str,
716
+ cls,
717
+ value: str,
718
+ data_type: Type[ScalarType],
719
+ period_from: Optional[str],
720
+ period_to: str,
721
+ conf: str,
710
722
  ) -> str:
711
723
  if data_type == TimePeriod: # Time period
712
724
  return _time_period_access(value, period_to)
@@ -722,7 +734,7 @@ class Time_Aggregation(Time):
722
734
 
723
735
  @classmethod
724
736
  def dataset_evaluation(
725
- cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
737
+ cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
726
738
  ) -> Dataset:
727
739
  result = cls.dataset_validation(operand, period_from, period_to, conf)
728
740
  result.data = operand.data.copy() if operand.data is not None else pd.DataFrame()
@@ -738,7 +750,11 @@ class Time_Aggregation(Time):
738
750
 
739
751
  @classmethod
740
752
  def component_evaluation(
741
- cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
753
+ cls,
754
+ operand: DataComponent,
755
+ period_from: Optional[str],
756
+ period_to: str,
757
+ conf: str,
742
758
  ) -> DataComponent:
743
759
  result = cls.component_validation(operand, period_from, period_to, conf)
744
760
  if operand.data is not None:
@@ -752,7 +768,7 @@ class Time_Aggregation(Time):
752
768
 
753
769
  @classmethod
754
770
  def scalar_evaluation(
755
- cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
771
+ cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
756
772
  ) -> Scalar:
757
773
  result = cls.scalar_validation(operand, period_from, period_to, conf)
758
774
  result.value = cls._execute_time_aggregation(
@@ -762,11 +778,11 @@ class Time_Aggregation(Time):
762
778
 
763
779
  @classmethod
764
780
  def validate(
765
- cls,
766
- operand: Union[Dataset, DataComponent, Scalar],
767
- period_from: Optional[str],
768
- period_to: str,
769
- conf: str,
781
+ cls,
782
+ operand: Union[Dataset, DataComponent, Scalar],
783
+ period_from: Optional[str],
784
+ period_to: str,
785
+ conf: str,
770
786
  ) -> Union[Dataset, DataComponent, Scalar]:
771
787
  cls._check_params(period_from, period_to)
772
788
  if isinstance(operand, Dataset):
@@ -778,11 +794,11 @@ class Time_Aggregation(Time):
778
794
 
779
795
  @classmethod
780
796
  def evaluate(
781
- cls,
782
- operand: Union[Dataset, DataComponent, Scalar],
783
- period_from: Optional[str],
784
- period_to: str,
785
- conf: str,
797
+ cls,
798
+ operand: Union[Dataset, DataComponent, Scalar],
799
+ period_from: Optional[str],
800
+ period_to: str,
801
+ conf: str,
786
802
  ) -> Union[Dataset, DataComponent, Scalar]:
787
803
  cls._check_params(period_from, period_to)
788
804
  if isinstance(operand, Dataset):
@@ -809,7 +825,6 @@ def _date_access(v: str, to_param: str, start: bool) -> Any:
809
825
 
810
826
 
811
827
  class Current_Date(Time):
812
-
813
828
  @classmethod
814
829
  def validate(cls) -> Scalar:
815
830
  return Scalar(name="current_date", data_type=Date, value=None)
@@ -830,26 +845,30 @@ class SimpleBinaryTime(Operators.Binary):
830
845
  if left == TimePeriod and right == Date:
831
846
  return False
832
847
 
833
- return not (left == TimePeriod and right == Date)
848
+ return not (left == TimePeriod and right == Date)
834
849
 
835
850
  @classmethod
836
851
  def validate(
837
- cls, left_operand: Union[Dataset, DataComponent, Scalar],
838
- right_operand: Union[Dataset, DataComponent, Scalar]
852
+ cls,
853
+ left_operand: Union[Dataset, DataComponent, Scalar],
854
+ right_operand: Union[Dataset, DataComponent, Scalar],
839
855
  ) -> Union[Dataset, DataComponent, Scalar]:
840
856
  if isinstance(left_operand, Dataset) or isinstance(right_operand, Dataset):
841
857
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
842
858
  if not cls.validate_type_compatibility(left_operand.data_type, right_operand.data_type):
843
859
  raise SemanticError(
844
- "1-1-1-2", type_1=left_operand.data_type, type_2=right_operand.data_type,
845
- type_check=cls.type_to_check
860
+ "1-1-1-2",
861
+ type_1=left_operand.data_type,
862
+ type_2=right_operand.data_type,
863
+ type_check=cls.type_to_check,
846
864
  )
847
865
  return super().validate(left_operand, right_operand)
848
866
 
849
867
  @classmethod
850
868
  def evaluate(
851
- cls, left_operand: Union[Dataset, DataComponent, Scalar],
852
- right_operand: Union[Dataset, DataComponent, Scalar]
869
+ cls,
870
+ left_operand: Union[Dataset, DataComponent, Scalar],
871
+ right_operand: Union[Dataset, DataComponent, Scalar],
853
872
  ) -> Union[Dataset, DataComponent, Scalar]:
854
873
  if isinstance(left_operand, Dataset) or isinstance(right_operand, Dataset):
855
874
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
@@ -869,12 +888,12 @@ class Date_Diff(SimpleBinaryTime):
869
888
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
870
889
 
871
890
  if x.count("-") == 2:
872
- fecha1 = datetime.strptime(x, '%Y-%m-%d').date()
891
+ fecha1 = datetime.strptime(x, "%Y-%m-%d").date()
873
892
  else:
874
893
  fecha1 = TimePeriodHandler(x).end_date(as_date=True) # type: ignore[assignment]
875
894
 
876
895
  if y.count("-") == 2:
877
- fecha2 = datetime.strptime(y, '%Y-%m-%d').date()
896
+ fecha2 = datetime.strptime(y, "%Y-%m-%d").date()
878
897
  else:
879
898
  fecha2 = TimePeriodHandler(y).end_date(as_date=True) # type: ignore[assignment]
880
899
 
@@ -885,26 +904,31 @@ class Date_Add(Parametrized):
885
904
  op = DATE_ADD
886
905
 
887
906
  @classmethod
888
- def validate(cls,
889
- operand: Union[Scalar, DataComponent, Dataset],
890
- param_list: List[Scalar]
891
- ) -> Union[Scalar, DataComponent, Dataset]:
892
-
907
+ def validate(
908
+ cls, operand: Union[Scalar, DataComponent, Dataset], param_list: List[Scalar]
909
+ ) -> Union[Scalar, DataComponent, Dataset]:
893
910
  expected_types = [Integer, String]
894
911
  for i, param in enumerate(param_list):
895
- error = 12 if not isinstance(param, Scalar) else 13 if ( # type: ignore[redundant-expr]
896
- param.data_type != expected_types[i]) else None
912
+ error = (
913
+ 12
914
+ if not isinstance(param, Scalar) # type: ignore[redundant-expr]
915
+ else 13
916
+ if (param.data_type != expected_types[i])
917
+ else None
918
+ )
897
919
  if error is not None:
898
- raise SemanticError(f"2-1-19-{error}",
899
- op=cls.op,
900
- type=param.__class__.__name__ if error == 12 else
901
- param.data_type.__name__,
902
- name="shiftNumber" if error == 12 else "periodInd",
903
- expected="Scalar" if error == 12 else expected_types[i].__name__
904
- )
905
-
906
- if (isinstance(operand, (Scalar, DataComponent)) and
907
- operand.data_type not in [Date, TimePeriod]):
920
+ raise SemanticError(
921
+ f"2-1-19-{error}",
922
+ op=cls.op,
923
+ type=(param.__class__.__name__ if error == 12 else param.data_type.__name__),
924
+ name="shiftNumber" if error == 12 else "periodInd",
925
+ expected="Scalar" if error == 12 else expected_types[i].__name__,
926
+ )
927
+
928
+ if isinstance(operand, (Scalar, DataComponent)) and operand.data_type not in [
929
+ Date,
930
+ TimePeriod,
931
+ ]:
908
932
  unary_implicit_promotion(operand.data_type, Date)
909
933
 
910
934
  if isinstance(operand, Scalar):
@@ -914,31 +938,38 @@ class Date_Add(Parametrized):
914
938
 
915
939
  if all(comp.data_type not in [Date, TimePeriod] for comp in operand.components.values()):
916
940
  raise SemanticError("2-1-19-14", op=cls.op, name=operand.name)
917
- return Dataset(name='result', components=operand.components.copy(), data=None)
941
+ return Dataset(name="result", components=operand.components.copy(), data=None)
918
942
 
919
943
  @classmethod
920
- def evaluate(cls,
921
- operand: Union[Scalar, DataComponent, Dataset],
922
- param_list: List[Scalar]
923
- ) -> Union[Scalar, DataComponent, Dataset]:
944
+ def evaluate(
945
+ cls, operand: Union[Scalar, DataComponent, Dataset], param_list: List[Scalar]
946
+ ) -> Union[Scalar, DataComponent, Dataset]:
924
947
  result = cls.validate(operand, param_list)
925
948
  shift, period = param_list[0].value, param_list[1].value
926
949
  is_tp = isinstance(operand, (Scalar, DataComponent)) and operand.data_type == TimePeriod
927
950
 
928
951
  if isinstance(result, Scalar) and isinstance(operand, Scalar) and operand.value is not None:
929
952
  result.value = cls.py_op(operand.value, shift, period, is_tp)
930
- elif (isinstance(result, DataComponent) and isinstance(operand, DataComponent) and
931
- operand.data is not None):
932
- result.data = operand.data.map(lambda x: cls.py_op(x, shift, period, is_tp),
933
- na_action="ignore")
934
- elif (isinstance(result, Dataset) and isinstance(operand, Dataset) and
935
- operand.data is not None):
953
+ elif (
954
+ isinstance(result, DataComponent)
955
+ and isinstance(operand, DataComponent)
956
+ and operand.data is not None
957
+ ):
958
+ result.data = operand.data.map(
959
+ lambda x: cls.py_op(x, shift, period, is_tp), na_action="ignore"
960
+ )
961
+ elif (
962
+ isinstance(result, Dataset)
963
+ and isinstance(operand, Dataset)
964
+ and operand.data is not None
965
+ ):
936
966
  result.data = operand.data.copy()
937
967
  for measure in operand.get_measures():
938
968
  if measure.data_type in [Date, TimePeriod]:
939
969
  result.data[measure.name] = result.data[measure.name].map(
940
- lambda x: cls.py_op(x, shift, period, measure.data_type == TimePeriod),
941
- na_action="ignore")
970
+ lambda x: cls.py_op(str(x), shift, period, measure.data_type == TimePeriod),
971
+ na_action="ignore",
972
+ )
942
973
  measure.data_type = Date
943
974
 
944
975
  if isinstance(result, (Scalar, DataComponent)):
@@ -946,47 +977,47 @@ class Date_Add(Parametrized):
946
977
  return result
947
978
 
948
979
  @classmethod
949
- def py_op(cls,
950
- date_str: str,
951
- shift: int, period: str,
952
- is_tp: bool = False
953
- ) -> str:
980
+ def py_op(cls, date_str: str, shift: int, period: str, is_tp: bool = False) -> str:
954
981
  if is_tp:
955
982
  tp_value = TimePeriodHandler(date_str)
956
983
  date = period_to_date(tp_value.year, tp_value.period_indicator, tp_value.period_number)
957
984
  else:
958
985
  date = datetime.strptime(date_str, "%Y-%m-%d")
959
986
 
960
- if period in ['D', 'W']:
961
- days_shift = shift * (7 if period == 'W' else 1)
987
+ if period in ["D", "W"]:
988
+ days_shift = shift * (7 if period == "W" else 1)
962
989
  return (date + timedelta(days=days_shift)).strftime("%Y-%m-%d")
963
990
 
964
- month_shift = {'M': 1, 'Q': 3, 'S': 6, 'A': 12}[period] * shift
991
+ month_shift = {"M": 1, "Q": 3, "S": 6, "A": 12}[period] * shift
965
992
  new_year = date.year + (date.month - 1 + month_shift) // 12
966
993
  new_month = (date.month - 1 + month_shift) % 12 + 1
967
994
  last_day = (datetime(new_year, new_month % 12 + 1, 1) - timedelta(days=1)).day
968
- return date.replace(year=new_year, month=new_month,
969
- day=min(date.day, last_day)).strftime("%Y-%m-%d")
995
+ return date.replace(year=new_year, month=new_month, day=min(date.day, last_day)).strftime(
996
+ "%Y-%m-%d"
997
+ )
970
998
 
971
999
 
972
1000
  class SimpleUnaryTime(Operators.Unary):
973
-
974
1001
  @classmethod
975
1002
  def validate(
976
- cls, operand: Union[Dataset, DataComponent, Scalar]
1003
+ cls, operand: Union[Dataset, DataComponent, Scalar]
977
1004
  ) -> Union[Dataset, DataComponent, Scalar]:
978
1005
  if isinstance(operand, Dataset):
979
1006
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
980
1007
 
981
1008
  # Limit the operand to Date and TimePeriod (cannot be implemented with type_to_check)
982
- if operand.data_type == TimeInterval or operand.data_type not in (Date, TimePeriod):
1009
+ if operand.data_type == TimeInterval or operand.data_type not in (
1010
+ Date,
1011
+ TimePeriod,
1012
+ Duration,
1013
+ ):
983
1014
  raise SemanticError("1-1-19-10", op=cls.op)
984
1015
 
985
1016
  return super().validate(operand)
986
1017
 
987
1018
  @classmethod
988
1019
  def evaluate(
989
- cls, operand: Union[Dataset, DataComponent, Scalar]
1020
+ cls, operand: Union[Dataset, DataComponent, Scalar]
990
1021
  ) -> Union[Dataset, DataComponent, Scalar]:
991
1022
  cls.validate(operand)
992
1023
  return super().evaluate(operand)
@@ -1040,19 +1071,21 @@ class Day_of_Year(SimpleUnaryTime):
1040
1071
 
1041
1072
  result = TimePeriodHandler(value).end_date(as_date=True)
1042
1073
  datetime_value = datetime(
1043
- year=result.year, month=result.month, day=result.day # type: ignore[union-attr]
1074
+ year=result.year, # type: ignore[union-attr]
1075
+ month=result.month, # type: ignore[union-attr]
1076
+ day=result.day, # type: ignore[union-attr]
1044
1077
  )
1045
1078
  return datetime_value.timetuple().tm_yday
1046
1079
 
1047
1080
 
1048
1081
  class Day_to_Year(Operators.Unary):
1049
1082
  op = DAYTOYEAR
1050
- return_type = String
1083
+ return_type = Duration
1051
1084
 
1052
1085
  @classmethod
1053
1086
  def py_op(cls, value: int) -> str:
1054
1087
  if value < 0:
1055
- raise SemanticError("2-1-19-17", op=cls.op)
1088
+ raise SemanticError("2-1-19-16", op=cls.op)
1056
1089
  years = 0
1057
1090
  days_remaining = value
1058
1091
  if value >= 365:
@@ -1063,12 +1096,12 @@ class Day_to_Year(Operators.Unary):
1063
1096
 
1064
1097
  class Day_to_Month(Operators.Unary):
1065
1098
  op = DAYTOMONTH
1066
- return_type = String
1099
+ return_type = Duration
1067
1100
 
1068
1101
  @classmethod
1069
1102
  def py_op(cls, value: int) -> str:
1070
1103
  if value < 0:
1071
- raise SemanticError("2-1-19-17", op=cls.op)
1104
+ raise SemanticError("2-1-19-16", op=cls.op)
1072
1105
  months = 0
1073
1106
  days_remaining = value
1074
1107
  if value >= 30:
@@ -1083,14 +1116,8 @@ class Year_to_Day(Operators.Unary):
1083
1116
 
1084
1117
  @classmethod
1085
1118
  def py_op(cls, value: str) -> int:
1086
- if "/" in value:
1087
- raise SemanticError("2-1-19-11", op=cls.op)
1088
- if "Y" not in value:
1089
- raise SemanticError("2-1-19-15", op=cls.op)
1090
- index_y = value.index("Y")
1091
- years = int(value[1:index_y])
1092
- days = int(value[(index_y + 1): -1])
1093
- return years * 365 + days
1119
+ days = Duration.to_days(value)
1120
+ return days
1094
1121
 
1095
1122
 
1096
1123
  class Month_to_Day(Operators.Unary):
@@ -1099,11 +1126,5 @@ class Month_to_Day(Operators.Unary):
1099
1126
 
1100
1127
  @classmethod
1101
1128
  def py_op(cls, value: str) -> int:
1102
- if "/" in value:
1103
- raise SemanticError("2-1-19-11", op=cls.op)
1104
- if "M" not in value:
1105
- raise SemanticError("2-1-19-16", op=cls.op)
1106
- index_m = value.index("M")
1107
- months = int(value[1:index_m])
1108
- days = int(value[(index_m + 1): -1])
1109
- return months * 30 + days
1129
+ days = Duration.to_days(value)
1130
+ return days