Skip to content

Commit 1dd3827

Browse files
filak-sapIvanShirokikh
authored andcommitted
model: set UTC timezone to DateTimes read rom JSON too
I came a cross this difference during testing of SAP#95 I decided to set tzinfo of JSON DateTimes to simplify my tests and it also make sense to me to have the values configured the same way.
1 parent 76330f5 commit 1dd3827

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

pyodata/v2/model.py

+29-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@
2626
TypeInfo = collections.namedtuple("TypeInfo", "namespace name is_collection")
2727

2828

29+
def current_timezone():
30+
"""Default Timezone for Python datetime instances when parsed from
31+
Edm.DateTime values and vice versa.
32+
33+
OData V2 does not mention Timezones in the documentation of
34+
Edm.DateTime and UTC was chosen because it is universal.
35+
"""
36+
37+
return datetime.timezone.utc
38+
39+
2940
def modlog():
3041
return logging.getLogger(LOGGER_NAME)
3142

@@ -429,11 +440,26 @@ def to_json(self, value):
429440

430441
# Converts datetime into timestamp in milliseconds in UTC timezone as defined in ODATA specification
431442
# https://www.odata.org/documentation/odata-version-2-0/json-format/
432-
return f"/Date({int(value.replace(tzinfo=datetime.timezone.utc).timestamp()) * 1000})/"
443+
return f'/Date({int(value.replace(tzinfo=current_timezone()).timestamp()) * 1000})/'
433444

434445
def from_json(self, value):
435446

436-
return self.from_literal(value)
447+
if value is None:
448+
return None
449+
450+
matches = re.match(r"^/Date\((.*)\)/$", value)
451+
if not matches:
452+
raise PyODataModelError(
453+
"Malformed value {0} for primitive Edm type. Expected format is /Date(value)/".format(value))
454+
value = matches.group(1)
455+
456+
try:
457+
# https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function
458+
value = datetime.datetime(1970, 1, 1, tzinfo=current_timezone()) + datetime.timedelta(milliseconds=int(value))
459+
except ValueError:
460+
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
461+
462+
return value
437463

438464
def from_literal(self, value):
439465

@@ -458,7 +484,7 @@ def from_literal(self, value):
458484
"Cannot decode datetime from value {}.".format(value)
459485
)
460486

461-
return value
487+
return value.replace(tzinfo=current_timezone())
462488

463489

464490
class EdmStringTypTraits(TypTraits):

tests/test_model_v2.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def test_traits_datetime():
516516

517517
# 1. direction Python -> OData
518518

519-
testdate = datetime(2005, 1, 28, 18, 30, 44, 123456, tzinfo=timezone.utc)
519+
testdate = datetime(2005, 1, 28, 18, 30, 44, 123456, tzinfo=current_timezone())
520520
assert typ.traits.to_literal(testdate) == "datetime'2005-01-28T18:30:44.123456'"
521521

522522
# without miliseconds part
@@ -539,19 +539,22 @@ def test_traits_datetime():
539539
assert testdate.minute == 33
540540
assert testdate.second == 6
541541
assert testdate.microsecond == 654321
542+
assert testdate.tzinfo == current_timezone()
542543

543544
# parsing without miliseconds
544545
testdate = typ.traits.from_literal("datetime'1976-11-23T03:33:06'")
545546
assert testdate.year == 1976
546547
assert testdate.second == 6
547548
assert testdate.microsecond == 0
549+
assert testdate.tzinfo == current_timezone()
548550

549551
# parsing without seconds and miliseconds
550552
testdate = typ.traits.from_literal("datetime'1976-11-23T03:33'")
551553
assert testdate.year == 1976
552554
assert testdate.minute == 33
553555
assert testdate.second == 0
554556
assert testdate.microsecond == 0
557+
assert testdate.tzinfo == current_timezone()
555558

556559
# parsing invalid value
557560
with pytest.raises(PyODataModelError) as e_info:
@@ -573,19 +576,22 @@ def test_traits_datetime():
573576
assert testdate.minute == 33
574577
assert testdate.second == 6
575578
assert testdate.microsecond == 10000
579+
assert testdate.tzinfo == current_timezone()
576580

577581
# parsing without miliseconds
578582
testdate = typ.traits.from_json("/Date(217567986000)/")
579583
assert testdate.year == 1976
580584
assert testdate.second == 6
581585
assert testdate.microsecond == 0
586+
assert testdate.tzinfo == current_timezone()
582587

583588
# parsing without seconds and miliseconds
584589
testdate = typ.traits.from_json("/Date(217567980000)/")
585590
assert testdate.year == 1976
586591
assert testdate.minute == 33
587592
assert testdate.second == 0
588593
assert testdate.microsecond == 0
594+
assert testdate.tzinfo == current_timezone()
589595

590596
# parsing the lowest value
591597
with pytest.raises(OverflowError):
@@ -599,6 +605,7 @@ def test_traits_datetime():
599605
assert testdate.minute == 0
600606
assert testdate.second == 0
601607
assert testdate.microsecond == 0
608+
assert testdate.tzinfo == current_timezone()
602609

603610
# parsing the highest value
604611
with pytest.raises(OverflowError):
@@ -612,6 +619,7 @@ def test_traits_datetime():
612619
assert testdate.minute == 59
613620
assert testdate.second == 59
614621
assert testdate.microsecond == 999000
622+
assert testdate.tzinfo == current_timezone()
615623

616624
# parsing invalid value
617625
with pytest.raises(PyODataModelError) as e_info:

0 commit comments

Comments
 (0)