diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f876b9d..7175a5d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- model: fix edge case for Edm.DateTimeOffset.from_json() without offset - Petr Hanak + ## [1.10.0] ### Added diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 065f93ef..4f15c846 100644 --- a/pyodata/v2/model.py +++ b/pyodata/v2/model.py @@ -529,6 +529,12 @@ def to_json(self, value): return f'/Date({ticks}{offset_in_minutes:+05})/' def from_json(self, value): + # special edge case: + # datetimeoffset'yyyy-mm-ddThh:mm[:ss]' = defaults to UTC, when offset value is not provided in responde data by service but the metadata is EdmDateTimeOffset + # intentionally just for from_json, generation of to_json should always provide timezone info + if re.match(r"^/Date\((?P-?\d+)\)/$", value): + value = value.replace(')', '+0000)') + matches = re.match(r"^/Date\((?P-?\d+)(?P[+-]\d+)\)/$", value) try: milliseconds_since_epoch = matches.group('milliseconds_since_epoch') diff --git a/tests/test_model_v2.py b/tests/test_model_v2.py index 5bdef48d..258dde77 100644 --- a/tests/test_model_v2.py +++ b/tests/test_model_v2.py @@ -690,6 +690,9 @@ def test_traits_datetimeoffset(type_date_time_offset): def test_traits_datetimeoffset_to_literal(type_date_time_offset): """Test Edm.DateTimeOffset trait: Python -> literal""" + testdate = datetime(1, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + assert type_date_time_offset.traits.to_literal(testdate) == "datetimeoffset'0001-01-01T00:00:00+00:00'" + testdate = datetime(2005, 1, 28, 18, 30, 44, 123456, tzinfo=timezone(timedelta(hours=3, minutes=40))) assert type_date_time_offset.traits.to_literal(testdate) == "datetimeoffset'2005-01-28T18:30:44.123456+03:40'" @@ -746,7 +749,7 @@ def test_traits_datetimeoffset_from_invalid_literal(type_date_time_offset): assert str(e_info.value).startswith('Cannot decode datetimeoffset from value xyz') -def test_traits_datetimeoffset_from_odata(type_date_time_offset): +def test_traits_datetimeoffset_from_json(type_date_time_offset): """Test Edm.DateTimeOffset trait: OData -> Python""" # parsing full representation @@ -768,6 +771,14 @@ def test_traits_datetimeoffset_from_odata(type_date_time_offset): assert testdate.microsecond == 0 assert testdate.tzinfo == timezone(-timedelta(minutes=5)) + # parsing special edge case with no offset provided, defaults to UTC + testdate = type_date_time_offset.traits.from_json("/Date(217567986000)/") + assert testdate.year == 1976 + assert testdate.minute == 33 + assert testdate.second == 6 + assert testdate.microsecond == 0 + assert testdate.tzinfo == timezone.utc + # parsing below lowest value with workaround pyodata.v2.model.FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE = True testdate = type_date_time_offset.traits.from_json("/Date(-62135596800001+0001)/")