From 2c6e563591a3689818daee6a08557d7f901239ea Mon Sep 17 00:00:00 2001
From: Paul Gerber
Date: Mon, 6 Jul 2026 15:00:17 +0200
Subject: [PATCH] model.datatypes: Fix xs:gYear and xs:gYearMonth rejecting
valid XSD year values
Previously, `_parse_xsd_gyear` and `_parse_xsd_gyearmonth` in `datatypes.py` reject values with more than 4 digits or negative signs.
Now, the regexes support an optional minus sign and 4+ digits, and the parsers correctly handle the sign from `match[1]`. Unit tests were added.
Fixes #566
---
sdk/basyx/aas/model/datatypes.py | 14 ++++++++++----
sdk/test/model/test_datatypes.py | 8 ++++++++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py
index e33b3e0b..2331637b 100644
--- a/sdk/basyx/aas/model/datatypes.py
+++ b/sdk/basyx/aas/model/datatypes.py
@@ -638,10 +638,10 @@ def _parse_xsd_bool(value: str) -> Boolean:
raise ValueError("Invalid literal for XSD bool type")
-GYEAR_RE = re.compile(r'^(\d\d\d\d)([+\-]\d\d:\d\d|Z)?$')
+GYEAR_RE = re.compile(r'^(-?)(\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\d\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$')
+GYEARMONTH_RE = re.compile(r'^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$')
GMONTHDAY_RE = re.compile(r'^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$')
@@ -649,7 +649,10 @@ def _parse_xsd_gyear(value: str) -> GYear:
match = GYEAR_RE.match(value)
if not match:
raise ValueError("Value is not a valid XSD GYear string")
- return GYear(int(match[1]), _parse_xsd_date_tzinfo(match[2]))
+ year = int(match[2])
+ if match[1]:
+ year = -year
+ return GYear(year, _parse_xsd_date_tzinfo(match[3]))
def _parse_xsd_gmonth(value: str) -> GMonth:
@@ -670,7 +673,10 @@ def _parse_xsd_gyearmonth(value: str) -> GYearMonth:
match = GYEARMONTH_RE.match(value)
if not match:
raise ValueError("Value is not a valid XSD GYearMonth string")
- return GYearMonth(int(match[1]), int(match[2]), _parse_xsd_date_tzinfo(match[3]))
+ year = int(match[2])
+ if match[1]:
+ year = -year
+ return GYearMonth(year, int(match[3]), _parse_xsd_date_tzinfo(match[4]))
def _parse_xsd_gmonthday(value: str) -> GMonthDay:
diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py
index f314d302..3da6750b 100644
--- a/sdk/test/model/test_datatypes.py
+++ b/sdk/test/model/test_datatypes.py
@@ -156,10 +156,18 @@ def test_serialize_date(self) -> None:
def test_parse_partial_dates(self) -> None:
self.assertEqual(model.datatypes.GYear(2019),
model.datatypes.from_xsd("2019", model.datatypes.GYear))
+ self.assertEqual(model.datatypes.GYear(-2001),
+ model.datatypes.from_xsd("-2001", model.datatypes.GYear))
+ self.assertEqual(model.datatypes.GYear(20000),
+ model.datatypes.from_xsd("20000", model.datatypes.GYear))
self.assertEqual(model.datatypes.GMonth(7),
model.datatypes.from_xsd("--07", model.datatypes.GMonth))
self.assertEqual(model.datatypes.GYearMonth(2020, 5),
model.datatypes.from_xsd("2020-05", model.datatypes.GYearMonth))
+ self.assertEqual(model.datatypes.GYearMonth(-2001, 10),
+ model.datatypes.from_xsd("-2001-10", model.datatypes.GYearMonth))
+ self.assertEqual(model.datatypes.GYearMonth(20000, 5),
+ model.datatypes.from_xsd("20000-05", model.datatypes.GYearMonth))
self.assertEqual(model.datatypes.GMonthDay(12, 6),
model.datatypes.from_xsd("--12-06", model.datatypes.GMonthDay))
self.assertEqual(model.datatypes.GDay(23),