diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index 5c59db17..fb4f82e9 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -121,6 +121,8 @@ def into_date(self, day: int = 1) -> Date: raise ValueError( "Negative years are not supported by Python's `datetime` library." ) from e + if self.year > datetime.MAXYEAR: + raise ValueError("Year of date exceeds Python's datetime.MAXYEAR.") from e raise e @classmethod @@ -157,6 +159,8 @@ def into_date(self, month: int = 1, day: int = 1) -> Date: raise ValueError( "Negative years are not supported by Python's `datetime` library." ) from e + if self.year > datetime.MAXYEAR: + raise ValueError("Year of date exceeds Python's datetime.MAXYEAR.") from e raise e @classmethod @@ -695,10 +699,10 @@ def from_xsd( r"^(-?)P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?((\d+)(\.\d+)?S)?)?$" ) DATETIME_RE = re.compile( - r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$" + r"^(-?)([1-9]\d{4,}|\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$" ) TIME_RE = re.compile(r"^(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$") -DATE_RE = re.compile(r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$") +DATE_RE = re.compile(r"^(-?)([1-9]\d{4,}|\d{4})-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$") def _parse_xsd_duration(value: str) -> Duration: @@ -739,6 +743,12 @@ def _parse_xsd_date(value: str) -> Date: "Negative dates are not supported: Python stdlib datetime requires year >= 1. " "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" ) + year = int(match[2]) + if year > datetime.MAXYEAR: + raise NotImplementedError( + "Year of date exceeds Python datetime.MAXYEAR " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) return Date( year=int(match[2]), month=int(match[3]), @@ -756,6 +766,12 @@ def _parse_xsd_datetime(value: str) -> DateTime: "Negative dates are not supported: Python stdlib datetime requires year >= 1. " "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" ) + year = int(match[2]) + if year > datetime.MAXYEAR: + raise NotImplementedError( + "Year of date exceeds Python datetime.MAXYEAR. " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) microseconds = int(float(match[8]) * 1e6) if match[8] else 0 hour = int(match[5]) # xsd_datetime allows for hour=24 to represent midnight, @@ -813,10 +829,10 @@ def _parse_xsd_bool(value: str) -> Boolean: raise ValueError("Invalid literal for XSD bool type") -GYEAR_RE = re.compile(r"^(-?)(\d{4,})([+\-]\d\d:\d\d|Z)?$") +GYEAR_RE = re.compile(r"^(-?)([1-9]\d{4,}|\d{4})([+\-]\d\d:\d\d|Z)?$") GMONTH_RE = re.compile(r"^--(\d\d)([+\-]\d\d:\d\d|Z)?$") GDAY_RE = re.compile(r"^---(\d\d)([+\-]\d\d:\d\d|Z)?$") -GYEARMONTH_RE = re.compile(r"^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$") +GYEARMONTH_RE = re.compile(r"^(-?)([1-9]\d{4,}|\d{4})-(\d\d)([+\-]\d\d:\d\d|Z)?$") GMONTHDAY_RE = re.compile(r"^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$") diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index df533833..7308ba7e 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -232,8 +232,13 @@ def test_parse_date(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("2020-01-24+11", model.datatypes.Date) self.assertEqual("Value is not a valid XSD date string", str(cm.exception)) + with self.assertRaises(ValueError) as cm: + model.datatypes.from_xsd("02020-01-24", model.datatypes.Date) + self.assertEqual("Value is not a valid XSD date string", str(cm.exception)) with self.assertRaises(NotImplementedError): model.datatypes.from_xsd("-2020-01-24", model.datatypes.Date) + with self.assertRaises(NotImplementedError): + model.datatypes.from_xsd(f"{datetime.MAXYEAR+1}-01-24", model.datatypes.Date) def test_serialize_date(self) -> None: self.assertEqual( @@ -303,9 +308,15 @@ def test_parse_partial_dates(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("10", model.datatypes.GYear) self.assertEqual("Value is not a valid XSD GYear string", str(cm.exception)) + with self.assertRaises(ValueError) as cm: + model.datatypes.from_xsd("02010", model.datatypes.GYear) + self.assertEqual("Value is not a valid XSD GYear string", str(cm.exception)) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("25-10", model.datatypes.GMonthDay) self.assertEqual("Value is not a valid XSD GMonthDay string", str(cm.exception)) + with self.assertRaises(ValueError) as cm: + model.datatypes.from_xsd("02025-10", model.datatypes.GMonthDay) + self.assertEqual("Value is not a valid XSD GMonthDay string", str(cm.exception)) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("10-10", model.datatypes.GYearMonth) self.assertEqual( @@ -326,6 +337,12 @@ def test_partial_dates_negative_year_into_date(self) -> None: str(cm.exception), ) + def test_partial_dates_max_year_into_date(self) -> None: + for value in (model.datatypes.GYear(datetime.MAXYEAR+1), model.datatypes.GYearMonth(datetime.MAXYEAR+1, 1)): + with self.assertRaises(ValueError) as cm: + value.into_date() + self.assertEqual("Year of date exceeds Python's datetime.MAXYEAR.", str(cm.exception)) + def test_copy_date(self) -> None: date = model.datatypes.Date(2020, 1, 24) date_copy_shallow = copy.copy(date) @@ -413,10 +430,14 @@ def test_parse_datetime(self) -> None: "--2020-01-24T15:25:17-00:20 is not a valid XSD datetime string", str(cm.exception), ) + with self.assertRaises(ValueError): + model.datatypes.from_xsd("02020-01-24T15:25:17", model.datatypes.DateTime) with self.assertRaises(NotImplementedError): model.datatypes.from_xsd( "-2020-01-24T15:25:17+01:00", model.datatypes.DateTime ) + with self.assertRaises(NotImplementedError): + model.datatypes.from_xsd(f"{datetime.MAXYEAR+1}-01-24T15:25:17", model.datatypes.DateTime) def test_serialize_datetime(self) -> None: self.assertEqual(