diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index d5acc6d4..2c696ff3 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -615,8 +615,17 @@ def _parse_xsd_datetime(value: str) -> DateTime: if match[1]: raise ValueError("Negative Dates are not supported by Python") microseconds = int(float(match[8]) * 1e6) if match[8] else 0 - return DateTime(int(match[2]), int(match[3]), int(match[4]), int(match[5]), int(match[6]), int(match[7]), - microseconds, _parse_xsd_date_tzinfo(match[9])) + hour = int(match[5]) + is_midnight_24 = False + if hour == 24: + if int(match[6]) != 0 or int(match[7]) != 0 or microseconds != 0: + raise ValueError("Invalid time: 24:00:00.000000 is the only valid representation of midnight") + hour = 0 + is_midnight_24 = True + + res = DateTime(int(match[2]), int(match[3]), int(match[4]), hour, int(match[6]), int(match[7]), + microseconds, _parse_xsd_date_tzinfo(match[9])) + return res + datetime.timedelta(days=1) if is_midnight_24 else res def _parse_xsd_time(value: str) -> Time: @@ -624,7 +633,12 @@ def _parse_xsd_time(value: str) -> Time: if not match: raise ValueError("Value is not a valid XSD datetime string") microseconds = int(float(match[4]) * 1e6) if match[4] else 0 - return Time(int(match[1]), int(match[2]), int(match[3]), microseconds, _parse_xsd_date_tzinfo(match[5])) + hour = int(match[1]) + if hour == 24: + if int(match[2]) != 0 or int(match[3]) != 0 or microseconds != 0: + raise ValueError("Invalid time: 24:00:00.000000 is the only valid representation of midnight") + hour = 0 + return Time(hour, int(match[2]), int(match[3]), microseconds, _parse_xsd_date_tzinfo(match[5])) def _parse_xsd_bool(value: str) -> Boolean: diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index b83c5e5f..0f7b71e2 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -244,6 +244,32 @@ def test_serialize_time(self) -> None: self.assertEqual("15:25:17.250000+01:00", model.datatypes.xsd_repr( datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))))) + def test_parse_datetime_midnight_24(self) -> None: + res = model.datatypes.from_xsd("2020-01-24T24:00:00", model.datatypes.DateTime) + self.assertEqual(datetime.datetime(2020, 1, 25, 0, 0, 0), res) + res_tz = model.datatypes.from_xsd("2020-01-24T24:00:00Z", model.datatypes.DateTime) + self.assertEqual(datetime.datetime(2020, 1, 25, 0, 0, 0, tzinfo=datetime.timezone.utc), res_tz) + + def test_parse_datetime_midnight_24_invalid(self) -> None: + with self.assertRaises(ValueError) as cm: + model.datatypes.from_xsd("2020-01-24T24:01:00", model.datatypes.DateTime) + self.assertEqual( + "Invalid time: 24:00:00.000000 is the only valid representation of midnight", + str(cm.exception) + ) + + def test_parse_time_midnight_24(self) -> None: + res = model.datatypes.from_xsd("24:00:00", model.datatypes.Time) + self.assertEqual(datetime.time(0, 0, 0), res) + + def test_parse_time_midnight_24_invalid(self) -> None: + with self.assertRaises(ValueError) as cm: + model.datatypes.from_xsd("24:00:01", model.datatypes.Time) + self.assertEqual( + "Invalid time: 24:00:00.000000 is the only valid representation of midnight", + str(cm.exception) + ) + def test_trivial_cast(self) -> None: val = model.datatypes.trivial_cast(datetime.date(2017, 11, 13), model.datatypes.Date) self.assertEqual(model.datatypes.Date(2017, 11, 13), val)