Skip to content

Commit e28b3ca

Browse files
authored
model: fix edge case for Edm.DateTimeOffset.from_json() without offset (#232)
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
1 parent 5d14908 commit e28b3ca

3 files changed

Lines changed: 20 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased]
88

9+
- model: fix edge case for Edm.DateTimeOffset.from_json() without offset - Petr Hanak
10+
911
## [1.10.0]
1012

1113
### Added

pyodata/v2/model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,12 @@ def to_json(self, value):
529529
return f'/Date({ticks}{offset_in_minutes:+05})/'
530530

531531
def from_json(self, value):
532+
# special edge case:
533+
# 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
534+
# intentionally just for from_json, generation of to_json should always provide timezone info
535+
if re.match(r"^/Date\((?P<milliseconds_since_epoch>-?\d+)\)/$", value):
536+
value = value.replace(')', '+0000)')
537+
532538
matches = re.match(r"^/Date\((?P<milliseconds_since_epoch>-?\d+)(?P<offset_in_minutes>[+-]\d+)\)/$", value)
533539
try:
534540
milliseconds_since_epoch = matches.group('milliseconds_since_epoch')

tests/test_model_v2.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,9 @@ def test_traits_datetimeoffset(type_date_time_offset):
690690
def test_traits_datetimeoffset_to_literal(type_date_time_offset):
691691
"""Test Edm.DateTimeOffset trait: Python -> literal"""
692692

693+
testdate = datetime(1, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc)
694+
assert type_date_time_offset.traits.to_literal(testdate) == "datetimeoffset'0001-01-01T00:00:00+00:00'"
695+
693696
testdate = datetime(2005, 1, 28, 18, 30, 44, 123456, tzinfo=timezone(timedelta(hours=3, minutes=40)))
694697
assert type_date_time_offset.traits.to_literal(testdate) == "datetimeoffset'2005-01-28T18:30:44.123456+03:40'"
695698

@@ -746,7 +749,7 @@ def test_traits_datetimeoffset_from_invalid_literal(type_date_time_offset):
746749
assert str(e_info.value).startswith('Cannot decode datetimeoffset from value xyz')
747750

748751

749-
def test_traits_datetimeoffset_from_odata(type_date_time_offset):
752+
def test_traits_datetimeoffset_from_json(type_date_time_offset):
750753
"""Test Edm.DateTimeOffset trait: OData -> Python"""
751754

752755
# parsing full representation
@@ -768,6 +771,14 @@ def test_traits_datetimeoffset_from_odata(type_date_time_offset):
768771
assert testdate.microsecond == 0
769772
assert testdate.tzinfo == timezone(-timedelta(minutes=5))
770773

774+
# parsing special edge case with no offset provided, defaults to UTC
775+
testdate = type_date_time_offset.traits.from_json("/Date(217567986000)/")
776+
assert testdate.year == 1976
777+
assert testdate.minute == 33
778+
assert testdate.second == 6
779+
assert testdate.microsecond == 0
780+
assert testdate.tzinfo == timezone.utc
781+
771782
# parsing below lowest value with workaround
772783
pyodata.v2.model.FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE = True
773784
testdate = type_date_time_offset.traits.from_json("/Date(-62135596800001+0001)/")

0 commit comments

Comments
 (0)