From fb21cad5f39fc8c17e43d49c4421df331955d4f3 Mon Sep 17 00:00:00 2001 From: I335851 Date: Fri, 23 Sep 2022 16:03:10 +0200 Subject: [PATCH] model: fix edge case for Edm.DateTimeOffset.from_json() without offset We were expecting that the offset part is mandatory and that the service should return /Date(1659430517461+0000)/ for offset set to UTC. Unable confirm in odata.org and W3C specifications, but some services works for this Edm type as offset is not mandatory of UTC timezone. Now, if the offset part is missing in incoming JSON payload, it should work as if +0000 was provided. FIXES #231 --- CHANGELOG.md | 2 ++ pyodata/v2/model.py | 6 ++++++ tests/test_model_v2.py | 13 ++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) 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)/")