vtlengine 1.0.2__py3-none-any.whl → 1.0.3__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 (46) hide show
  1. vtlengine/API/_InternalApi.py +12 -5
  2. vtlengine/API/__init__.py +8 -8
  3. vtlengine/AST/ASTConstructor.py +23 -43
  4. vtlengine/AST/ASTConstructorModules/Expr.py +69 -84
  5. vtlengine/AST/ASTConstructorModules/ExprComponents.py +47 -57
  6. vtlengine/AST/ASTConstructorModules/Terminals.py +28 -39
  7. vtlengine/AST/ASTTemplate.py +0 -1
  8. vtlengine/AST/DAG/__init__.py +12 -15
  9. vtlengine/AST/Grammar/tokens.py +2 -2
  10. vtlengine/AST/VtlVisitor.py +0 -1
  11. vtlengine/AST/__init__.py +2 -3
  12. vtlengine/DataTypes/TimeHandling.py +10 -7
  13. vtlengine/DataTypes/__init__.py +17 -24
  14. vtlengine/Exceptions/__init__.py +3 -5
  15. vtlengine/Exceptions/messages.py +68 -56
  16. vtlengine/Interpreter/__init__.py +82 -103
  17. vtlengine/Model/__init__.py +10 -12
  18. vtlengine/Operators/Aggregation.py +14 -14
  19. vtlengine/Operators/Analytic.py +3 -10
  20. vtlengine/Operators/Assignment.py +2 -3
  21. vtlengine/Operators/Boolean.py +5 -7
  22. vtlengine/Operators/CastOperator.py +12 -13
  23. vtlengine/Operators/Clause.py +11 -13
  24. vtlengine/Operators/Comparison.py +31 -17
  25. vtlengine/Operators/Conditional.py +48 -49
  26. vtlengine/Operators/General.py +4 -4
  27. vtlengine/Operators/HROperators.py +41 -34
  28. vtlengine/Operators/Join.py +18 -22
  29. vtlengine/Operators/Numeric.py +44 -45
  30. vtlengine/Operators/RoleSetter.py +6 -8
  31. vtlengine/Operators/Set.py +7 -12
  32. vtlengine/Operators/String.py +19 -27
  33. vtlengine/Operators/Time.py +298 -109
  34. vtlengine/Operators/Validation.py +4 -7
  35. vtlengine/Operators/__init__.py +38 -41
  36. vtlengine/Utils/__init__.py +133 -114
  37. vtlengine/__init__.py +1 -1
  38. vtlengine/files/output/__init__.py +2 -2
  39. vtlengine/files/output/_time_period_representation.py +0 -1
  40. vtlengine/files/parser/__init__.py +16 -18
  41. vtlengine/files/parser/_time_checking.py +1 -2
  42. {vtlengine-1.0.2.dist-info → vtlengine-1.0.3.dist-info}/METADATA +5 -2
  43. vtlengine-1.0.3.dist-info/RECORD +58 -0
  44. vtlengine-1.0.2.dist-info/RECORD +0 -58
  45. {vtlengine-1.0.2.dist-info → vtlengine-1.0.3.dist-info}/LICENSE.md +0 -0
  46. {vtlengine-1.0.2.dist-info → vtlengine-1.0.3.dist-info}/WHEEL +0 -0
@@ -1,22 +1,45 @@
1
1
  import re
2
- import pandas as pd
2
+ from datetime import date, datetime, timedelta
3
+ from typing import Any, Dict, List, Optional, Type, Union
3
4
 
4
- from datetime import date
5
- from typing import Optional, Union, List, Any, Dict, Type
5
+ import pandas as pd
6
6
 
7
7
  import vtlengine.Operators as Operators
8
- from vtlengine.DataTypes import Date, TimePeriod, TimeInterval, Duration, ScalarType
9
- from vtlengine.DataTypes.TimeHandling import DURATION_MAPPING, date_to_period, TimePeriodHandler
10
-
11
8
  from vtlengine.AST.Grammar.tokens import (
12
- TIME_AGG,
13
- TIMESHIFT,
14
- PERIOD_INDICATOR,
9
+ DATE_ADD,
10
+ DATEDIFF,
11
+ DAYOFMONTH,
12
+ DAYOFYEAR,
13
+ DAYTOMONTH,
14
+ DAYTOYEAR,
15
15
  FILL_TIME_SERIES,
16
16
  FLOW_TO_STOCK,
17
+ MONTH,
18
+ MONTHTODAY,
19
+ PERIOD_INDICATOR,
20
+ TIME_AGG,
21
+ TIMESHIFT,
22
+ YEAR,
23
+ YEARTODAY,
24
+ )
25
+ from vtlengine.DataTypes import (
26
+ Date,
27
+ Duration,
28
+ Integer,
29
+ ScalarType,
30
+ String,
31
+ TimeInterval,
32
+ TimePeriod,
33
+ unary_implicit_promotion,
34
+ )
35
+ from vtlengine.DataTypes.TimeHandling import (
36
+ DURATION_MAPPING,
37
+ TimePeriodHandler,
38
+ date_to_period,
39
+ period_to_date,
17
40
  )
18
41
  from vtlengine.Exceptions import SemanticError
19
- from vtlengine.Model import Dataset, DataComponent, Scalar, Component, Role
42
+ from vtlengine.Model import Component, DataComponent, Dataset, Role, Scalar
20
43
 
21
44
 
22
45
  class Time(Operators.Operator):
@@ -121,7 +144,7 @@ class Unary(Time):
121
144
  result.data = result.data.sort_values(by=cls.other_ids + [cls.time_id])
122
145
  if data_type == TimePeriod:
123
146
  result.data = cls._period_accumulation(result.data, measure_names)
124
- elif data_type == Date or data_type == TimeInterval:
147
+ elif data_type in (Date, TimeInterval):
125
148
  result.data[measure_names] = (
126
149
  result.data.groupby(cls.other_ids)[measure_names]
127
150
  .apply(cls.py_op)
@@ -179,7 +202,7 @@ class Period_indicator(Unary):
179
202
 
180
203
  @classmethod
181
204
  def evaluate(
182
- cls, operand: Union[Dataset, DataComponent, Scalar, str]
205
+ cls, operand: Union[Dataset, DataComponent, Scalar, str]
183
206
  ) -> Union[Dataset, DataComponent, Scalar, str]:
184
207
  result = cls.validate(operand)
185
208
  if isinstance(operand, str):
@@ -362,7 +385,7 @@ class Fill_time_series(Binary):
362
385
 
363
386
  @classmethod
364
387
  def fill_periods_rows(
365
- cls, group_df: Any, period: str, years: List[int], vals: Optional[List[int]] = None
388
+ cls, group_df: Any, period: str, years: List[int], vals: Optional[List[int]] = None
366
389
  ) -> List[Any]:
367
390
  rows = []
368
391
  for year in years:
@@ -375,7 +398,7 @@ class Fill_time_series(Binary):
375
398
 
376
399
  @classmethod
377
400
  def create_period_row(
378
- cls, group_df: Any, period: str, year: int, val: Optional[int] = None
401
+ cls, group_df: Any, period: str, year: int, val: Optional[int] = None
379
402
  ) -> Any:
380
403
  row = group_df.iloc[0].copy()
381
404
  row[cls.time_id] = f"{year}" if period == "A" else f"{year}-{period}{val:d}"
@@ -414,7 +437,7 @@ class Fill_time_series(Binary):
414
437
  filled_data = []
415
438
 
416
439
  def create_filled_dates(
417
- group: Any, min_max: Dict[str, Any]
440
+ group: Any, min_max: Dict[str, Any]
418
441
  ) -> (pd.DataFrame, str): # type: ignore[syntax]
419
442
  date_range = pd.date_range(start=min_max["min"], end=min_max["max"], freq=min_frequency)
420
443
  date_df = pd.DataFrame(date_range, columns=[cls.time_id])
@@ -457,7 +480,7 @@ class Fill_time_series(Binary):
457
480
 
458
481
  @classmethod
459
482
  def fill_time_intervals(
460
- cls, data: pd.DataFrame, fill_type: str, frequency: str
483
+ cls, data: pd.DataFrame, fill_type: str, frequency: str
461
484
  ) -> pd.DataFrame:
462
485
  result_data = cls.time_filler(data, fill_type, frequency)
463
486
  not_na = result_data[cls.measures].notna().any(axis=1)
@@ -565,7 +588,7 @@ class Time_Shift(Binary):
565
588
 
566
589
  @classmethod
567
590
  def shift_period(
568
- cls, period_str: str, shift_value: int, frequency: Optional[int] = None
591
+ cls, period_str: str, shift_value: int, frequency: Optional[int] = None
569
592
  ) -> str:
570
593
  period_type = cls._get_period(period_str)
571
594
 
@@ -619,7 +642,7 @@ class Time_Aggregation(Time):
619
642
 
620
643
  @classmethod
621
644
  def dataset_validation(
622
- cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
645
+ cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
623
646
  ) -> Dataset:
624
647
  # TODO: Review with VTL TF as this makes no sense
625
648
 
@@ -656,7 +679,7 @@ class Time_Aggregation(Time):
656
679
 
657
680
  @classmethod
658
681
  def component_validation(
659
- cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
682
+ cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
660
683
  ) -> DataComponent:
661
684
  if operand.data_type not in cls.TIME_DATA_TYPES:
662
685
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time component")
@@ -669,7 +692,7 @@ class Time_Aggregation(Time):
669
692
 
670
693
  @classmethod
671
694
  def scalar_validation(
672
- cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
695
+ cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
673
696
  ) -> Scalar:
674
697
  if operand.data_type not in cls.TIME_DATA_TYPES:
675
698
  raise SemanticError("1-1-19-8", op=cls.op, comp_type="time scalar")
@@ -678,21 +701,18 @@ class Time_Aggregation(Time):
678
701
 
679
702
  @classmethod
680
703
  def _execute_time_aggregation(
681
- cls,
682
- value: str,
683
- data_type: Type[ScalarType],
684
- period_from: Optional[str],
685
- period_to: str,
686
- conf: str,
704
+ cls,
705
+ value: str,
706
+ data_type: Type[ScalarType],
707
+ period_from: Optional[str],
708
+ period_to: str,
709
+ conf: str,
687
710
  ) -> str:
688
711
  if data_type == TimePeriod: # Time period
689
712
  return _time_period_access(value, period_to)
690
713
 
691
714
  elif data_type == Date:
692
- if conf == "first":
693
- start = True
694
- else:
695
- start = False
715
+ start = conf == "first"
696
716
  # Date
697
717
  if period_to == "D":
698
718
  return value
@@ -702,7 +722,7 @@ class Time_Aggregation(Time):
702
722
 
703
723
  @classmethod
704
724
  def dataset_evaluation(
705
- cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
725
+ cls, operand: Dataset, period_from: Optional[str], period_to: str, conf: str
706
726
  ) -> Dataset:
707
727
  result = cls.dataset_validation(operand, period_from, period_to, conf)
708
728
  result.data = operand.data.copy() if operand.data is not None else pd.DataFrame()
@@ -718,7 +738,7 @@ class Time_Aggregation(Time):
718
738
 
719
739
  @classmethod
720
740
  def component_evaluation(
721
- cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
741
+ cls, operand: DataComponent, period_from: Optional[str], period_to: str, conf: str
722
742
  ) -> DataComponent:
723
743
  result = cls.component_validation(operand, period_from, period_to, conf)
724
744
  if operand.data is not None:
@@ -732,7 +752,7 @@ class Time_Aggregation(Time):
732
752
 
733
753
  @classmethod
734
754
  def scalar_evaluation(
735
- cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
755
+ cls, operand: Scalar, period_from: Optional[str], period_to: str, conf: str
736
756
  ) -> Scalar:
737
757
  result = cls.scalar_validation(operand, period_from, period_to, conf)
738
758
  result.value = cls._execute_time_aggregation(
@@ -742,11 +762,11 @@ class Time_Aggregation(Time):
742
762
 
743
763
  @classmethod
744
764
  def validate(
745
- cls,
746
- operand: Union[Dataset, DataComponent, Scalar],
747
- period_from: Optional[str],
748
- period_to: str,
749
- conf: str,
765
+ cls,
766
+ operand: Union[Dataset, DataComponent, Scalar],
767
+ period_from: Optional[str],
768
+ period_to: str,
769
+ conf: str,
750
770
  ) -> Union[Dataset, DataComponent, Scalar]:
751
771
  cls._check_params(period_from, period_to)
752
772
  if isinstance(operand, Dataset):
@@ -758,11 +778,11 @@ class Time_Aggregation(Time):
758
778
 
759
779
  @classmethod
760
780
  def evaluate(
761
- cls,
762
- operand: Union[Dataset, DataComponent, Scalar],
763
- period_from: Optional[str],
764
- period_to: str,
765
- conf: str,
781
+ cls,
782
+ operand: Union[Dataset, DataComponent, Scalar],
783
+ period_from: Optional[str],
784
+ period_to: str,
785
+ conf: str,
766
786
  ) -> Union[Dataset, DataComponent, Scalar]:
767
787
  cls._check_params(period_from, period_to)
768
788
  if isinstance(operand, Dataset):
@@ -801,120 +821,289 @@ class Current_Date(Time):
801
821
  return result
802
822
 
803
823
 
804
- class Date_Diff(Binary):
805
-
824
+ class SimpleBinaryTime(Operators.Binary):
806
825
  @classmethod
807
- def evaluate(cls, Date1: Any, Date2: Any) -> Any:
808
- # TODO: Implement this method (or adapt Binary's validate method to work with this operator)
809
- pass
826
+ def validate_type_compatibility(cls, left: Any, right: Any) -> bool:
827
+ if left == Date and right == TimePeriod:
828
+ return False
810
829
 
811
- @classmethod
812
- def validate(cls, Date1: Any, Date2: Any) -> Any:
813
- pass
830
+ if left == TimePeriod and right == Date:
831
+ return False
814
832
 
833
+ return not (left == TimePeriod and right == Date)
815
834
 
816
- class Date_Add(Parametrized):
817
835
  @classmethod
818
- def evaluate(cls, operand: Any, param_list: List[Any]) -> Any:
819
- # TODO: Implement this method (or adapt Binary's validate method to work with this operator)
820
- pass
836
+ def validate(
837
+ cls, left_operand: Union[Dataset, DataComponent, Scalar],
838
+ right_operand: Union[Dataset, DataComponent, Scalar]
839
+ ) -> Union[Dataset, DataComponent, Scalar]:
840
+ if isinstance(left_operand, Dataset) or isinstance(right_operand, Dataset):
841
+ raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
842
+ if not cls.validate_type_compatibility(left_operand.data_type, right_operand.data_type):
843
+ 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
846
+ )
847
+ return super().validate(left_operand, right_operand)
821
848
 
822
849
  @classmethod
823
- def validate(cls, operand: Any, param_list: List[Any]) -> Any:
824
- pass
850
+ def evaluate(
851
+ cls, left_operand: Union[Dataset, DataComponent, Scalar],
852
+ right_operand: Union[Dataset, DataComponent, Scalar]
853
+ ) -> Union[Dataset, DataComponent, Scalar]:
854
+ if isinstance(left_operand, Dataset) or isinstance(right_operand, Dataset):
855
+ raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
856
+ else:
857
+ cls.validate(left_operand, right_operand)
858
+ return super().evaluate(left_operand, right_operand)
825
859
 
826
860
 
827
- class Year(Unary):
861
+ class Date_Diff(SimpleBinaryTime):
862
+ op = DATEDIFF
863
+ type_to_check = TimeInterval
864
+ return_type = Integer
828
865
 
829
866
  @classmethod
830
- def validate(cls, operand: Any) -> Any:
831
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
832
- pass
867
+ def py_op(cls, x: Any, y: Any) -> int:
868
+ if (x.count("/") >= 1) or (y.count("/") >= 1):
869
+ raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
833
870
 
834
- @classmethod
835
- def py_op(cls, x: Any) -> Any:
836
- pass
871
+ if x.count("-") == 2:
872
+ fecha1 = datetime.strptime(x, '%Y-%m-%d').date()
873
+ else:
874
+ fecha1 = TimePeriodHandler(x).end_date(as_date=True) # type: ignore[assignment]
837
875
 
876
+ if y.count("-") == 2:
877
+ fecha2 = datetime.strptime(y, '%Y-%m-%d').date()
878
+ else:
879
+ fecha2 = TimePeriodHandler(y).end_date(as_date=True) # type: ignore[assignment]
838
880
 
839
- class Month(Unary):
881
+ return abs((fecha2 - fecha1).days)
840
882
 
841
- @classmethod
842
- def validate(cls, operand: Any) -> Any:
843
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
844
- pass
883
+
884
+ class Date_Add(Parametrized):
885
+ op = DATE_ADD
886
+
887
+ @classmethod
888
+ def validate(cls,
889
+ operand: Union[Scalar, DataComponent, Dataset],
890
+ param_list: List[Scalar]
891
+ ) -> Union[Scalar, DataComponent, Dataset]:
892
+
893
+ expected_types = [Integer, String]
894
+ 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
897
+ 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]):
908
+ unary_implicit_promotion(operand.data_type, Date)
909
+
910
+ if isinstance(operand, Scalar):
911
+ return Scalar(name=operand.name, data_type=operand.data_type, value=None)
912
+ if isinstance(operand, DataComponent):
913
+ return DataComponent(name=operand.name, data_type=operand.data_type, data=None)
914
+
915
+ if all(comp.data_type not in [Date, TimePeriod] for comp in operand.components.values()):
916
+ raise SemanticError("2-1-19-14", op=cls.op, name=operand.name)
917
+ return Dataset(name='result', components=operand.components.copy(), data=None)
918
+
919
+ @classmethod
920
+ def evaluate(cls,
921
+ operand: Union[Scalar, DataComponent, Dataset],
922
+ param_list: List[Scalar]
923
+ ) -> Union[Scalar, DataComponent, Dataset]:
924
+ result = cls.validate(operand, param_list)
925
+ shift, period = param_list[0].value, param_list[1].value
926
+ is_tp = isinstance(operand, (Scalar, DataComponent)) and operand.data_type == TimePeriod
927
+
928
+ if isinstance(result, Scalar) and isinstance(operand, Scalar) and operand.value is not None:
929
+ 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):
936
+ result.data = operand.data.copy()
937
+ for measure in operand.get_measures():
938
+ if measure.data_type in [Date, TimePeriod]:
939
+ 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")
942
+ measure.data_type = Date
943
+
944
+ if isinstance(result, (Scalar, DataComponent)):
945
+ result.data_type = Date
946
+ return result
845
947
 
846
948
  @classmethod
847
- def py_op(cls, x: Any) -> Any:
848
- pass
949
+ def py_op(cls,
950
+ date_str: str,
951
+ shift: int, period: str,
952
+ is_tp: bool = False
953
+ ) -> str:
954
+ if is_tp:
955
+ tp_value = TimePeriodHandler(date_str)
956
+ date = period_to_date(tp_value.year, tp_value.period_indicator, tp_value.period_number)
957
+ else:
958
+ date = datetime.strptime(date_str, "%Y-%m-%d")
849
959
 
960
+ if period in ['D', 'W']:
961
+ days_shift = shift * (7 if period == 'W' else 1)
962
+ return (date + timedelta(days=days_shift)).strftime("%Y-%m-%d")
850
963
 
851
- class Day_of_Month(Unary):
964
+ month_shift = {'M': 1, 'Q': 3, 'S': 6, 'A': 12}[period] * shift
965
+ new_year = date.year + (date.month - 1 + month_shift) // 12
966
+ new_month = (date.month - 1 + month_shift) % 12 + 1
967
+ 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")
852
970
 
853
- @classmethod
854
- def validate(cls, operand: Any) -> Any:
855
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
856
- pass
971
+
972
+ class SimpleUnaryTime(Operators.Unary):
857
973
 
858
974
  @classmethod
859
- def py_op(cls, x: Any) -> Any:
860
- pass
975
+ def validate(
976
+ cls, operand: Union[Dataset, DataComponent, Scalar]
977
+ ) -> Union[Dataset, DataComponent, Scalar]:
978
+ if isinstance(operand, Dataset):
979
+ raise SemanticError("1-1-19-8", op=cls.op, comp_type="time dataset")
861
980
 
981
+ # 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):
983
+ raise SemanticError("1-1-19-10", op=cls.op)
862
984
 
863
- class Day_of_Year(Unary):
985
+ return super().validate(operand)
864
986
 
865
987
  @classmethod
866
- def validate(cls, operand: Any) -> Any:
867
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
868
- pass
988
+ def evaluate(
989
+ cls, operand: Union[Dataset, DataComponent, Scalar]
990
+ ) -> Union[Dataset, DataComponent, Scalar]:
991
+ cls.validate(operand)
992
+ return super().evaluate(operand)
993
+
994
+
995
+ class Year(SimpleUnaryTime):
996
+ op = YEAR
869
997
 
870
998
  @classmethod
871
- def py_op(cls, x: Any) -> Any:
872
- pass
999
+ def py_op(cls, value: str) -> int:
1000
+ return int(value[:4])
873
1001
 
1002
+ return_type = Integer
874
1003
 
875
- class Day_to_Year(Unary):
876
1004
 
877
- @classmethod
878
- def validate(cls, operand: Any) -> Any:
879
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
880
- pass
1005
+ class Month(SimpleUnaryTime):
1006
+ op = MONTH
1007
+ return_type = Integer
881
1008
 
882
1009
  @classmethod
883
- def py_op(cls, x: Any) -> Any:
884
- pass
1010
+ def py_op(cls, value: str) -> int:
1011
+ if value.count("-") == 2:
1012
+ return date.fromisoformat(value).month
1013
+
1014
+ result = TimePeriodHandler(value).start_date(as_date=True)
1015
+ return result.month # type: ignore[union-attr]
885
1016
 
886
1017
 
887
- class Day_to_Month(Unary):
1018
+ class Day_of_Month(SimpleUnaryTime):
1019
+ op = DAYOFMONTH
1020
+ return_type = Integer
888
1021
 
889
1022
  @classmethod
890
- def validate(cls, operand: Any) -> Any:
891
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
892
- pass
1023
+ def py_op(cls, value: str) -> int:
1024
+ if value.count("-") == 2:
1025
+ return date.fromisoformat(value).day
1026
+
1027
+ result = TimePeriodHandler(value).end_date(as_date=True)
1028
+ return result.day # type: ignore[union-attr]
1029
+
1030
+
1031
+ class Day_of_Year(SimpleUnaryTime):
1032
+ op = DAYOFYEAR
1033
+ return_type = Integer
893
1034
 
894
1035
  @classmethod
895
- def py_op(cls, x: Any) -> Any:
896
- pass
1036
+ def py_op(cls, value: str) -> int:
1037
+ if value.count("-") == 2:
1038
+ day_y = datetime.strptime(value, "%Y-%m-%d")
1039
+ return day_y.timetuple().tm_yday
1040
+
1041
+ result = TimePeriodHandler(value).end_date(as_date=True)
1042
+ datetime_value = datetime(
1043
+ year=result.year, month=result.month, day=result.day # type: ignore[union-attr]
1044
+ )
1045
+ return datetime_value.timetuple().tm_yday
897
1046
 
898
1047
 
899
- class Year_to_Day(Unary):
1048
+ class Day_to_Year(Operators.Unary):
1049
+ op = DAYTOYEAR
1050
+ return_type = String
900
1051
 
901
1052
  @classmethod
902
- def validate(cls, operand: Any) -> Any:
903
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
904
- pass
1053
+ def py_op(cls, value: int) -> str:
1054
+ if value < 0:
1055
+ raise SemanticError("2-1-19-17", op=cls.op)
1056
+ years = 0
1057
+ days_remaining = value
1058
+ if value >= 365:
1059
+ years = value // 365
1060
+ days_remaining = value % 365
1061
+ return f"P{int(years)}Y{int(days_remaining)}D"
1062
+
1063
+
1064
+ class Day_to_Month(Operators.Unary):
1065
+ op = DAYTOMONTH
1066
+ return_type = String
905
1067
 
906
1068
  @classmethod
907
- def py_op(cls, x: Any) -> Any:
908
- pass
1069
+ def py_op(cls, value: int) -> str:
1070
+ if value < 0:
1071
+ raise SemanticError("2-1-19-17", op=cls.op)
1072
+ months = 0
1073
+ days_remaining = value
1074
+ if value >= 30:
1075
+ months = value // 30
1076
+ days_remaining = value % 30
1077
+ return f"P{int(months)}M{int(days_remaining)}D"
909
1078
 
910
1079
 
911
- class Month_to_Day(Unary):
1080
+ class Year_to_Day(Operators.Unary):
1081
+ op = YEARTODAY
1082
+ return_type = Integer
912
1083
 
913
1084
  @classmethod
914
- def validate(cls, operand: Any) -> Any:
915
- # TODO: Implement this method (or adapt Unary's validate method to work with this operator)
916
- pass
1085
+ 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
1094
+
1095
+
1096
+ class Month_to_Day(Operators.Unary):
1097
+ op = MONTHTODAY
1098
+ return_type = Integer
917
1099
 
918
1100
  @classmethod
919
- def py_op(cls, x: Any) -> Any:
920
- pass
1101
+ 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
@@ -2,12 +2,12 @@ from copy import copy
2
2
  from typing import Any, Dict, Optional
3
3
 
4
4
  import pandas as pd
5
- from vtlengine.DataTypes import Boolean, Integer, Number, String, check_unary_implicit_promotion
6
- from vtlengine.Operators import Operator
7
5
 
8
6
  from vtlengine.AST.Grammar.tokens import CHECK, CHECK_HIERARCHY
7
+ from vtlengine.DataTypes import Boolean, Integer, Number, String, check_unary_implicit_promotion
9
8
  from vtlengine.Exceptions import SemanticError
10
9
  from vtlengine.Model import Component, Dataset, Role
10
+ from vtlengine.Operators import Operator
11
11
 
12
12
 
13
13
  # noinspection PyTypeChecker
@@ -191,16 +191,13 @@ class Check_Hierarchy(Validation):
191
191
 
192
192
  @classmethod
193
193
  def _generate_result_data(cls, rule_info: Dict[str, Any]) -> pd.DataFrame:
194
- df = None
194
+ df = pd.DataFrame()
195
195
  for rule_name, rule_data in rule_info.items():
196
196
  rule_df = rule_data["output"]
197
197
  rule_df["ruleid"] = rule_name
198
198
  rule_df["errorcode"] = rule_data["errorcode"]
199
199
  rule_df["errorlevel"] = rule_data["errorlevel"]
200
- if df is None:
201
- df = rule_df
202
- else:
203
- df = pd.concat([df, rule_df], ignore_index=True)
200
+ df = pd.concat([df, rule_df], ignore_index=True)
204
201
  if df is None:
205
202
  df = pd.DataFrame()
206
203
  return df