Skip to content

Commit 16a15fb

Browse files
committed
model: set UTC timezone to DateTimes read rom JSON too
I came a cross this difference during testing of #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 f9144b3 commit 16a15fb

File tree

3 files changed

+25
-5
lines changed

3 files changed

+25
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111

1212
### Changed
1313
- handle GET EntitySet payload without the member results - Jakub Filak
14+
- both Literal and JSON DateTimes has Timezone set to UTC - Jakub Filak
1415

1516
### Fixed
1617
- removed superfluous debug print when parsing FunctionImports from metadata - Jakub Filak

pyodata/v2/model.py

+14-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

@@ -387,7 +398,7 @@ def to_json(self, value):
387398

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

392403
def from_json(self, value):
393404

@@ -402,7 +413,7 @@ def from_json(self, value):
402413

403414
try:
404415
# https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function
405-
value = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(milliseconds=int(value))
416+
value = datetime.datetime(1970, 1, 1, tzinfo=current_timezone()) + datetime.timedelta(milliseconds=int(value))
406417
except ValueError:
407418
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
408419

@@ -426,7 +437,7 @@ def from_literal(self, value):
426437
except ValueError:
427438
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
428439

429-
return value
440+
return value.replace(tzinfo=current_timezone())
430441

431442

432443
class EdmStringTypTraits(TypTraits):

tests/test_model_v2.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
from pyodata.v2.model import Schema, Typ, StructTypeProperty, Types, EntityType, EdmStructTypeSerializer, \
88
Association, AssociationSet, EndRole, AssociationSetEndRole, TypeInfo, MetadataBuilder, ParserError, PolicyWarning, \
9-
PolicyIgnore, Config, PolicyFatal, NullType, NullAssociation
9+
PolicyIgnore, Config, PolicyFatal, NullType, NullAssociation, current_timezone
1010
from pyodata.exceptions import PyODataException, PyODataModelError, PyODataParserError
1111
from tests.conftest import assert_logging_policy
1212

@@ -458,7 +458,7 @@ def test_traits_datetime():
458458

459459
# 1. direction Python -> OData
460460

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

464464
# without miliseconds part
@@ -481,19 +481,22 @@ def test_traits_datetime():
481481
assert testdate.minute == 33
482482
assert testdate.second == 6
483483
assert testdate.microsecond == 654321
484+
assert testdate.tzinfo == current_timezone()
484485

485486
# parsing without miliseconds
486487
testdate = typ.traits.from_literal("datetime'1976-11-23T03:33:06'")
487488
assert testdate.year == 1976
488489
assert testdate.second == 6
489490
assert testdate.microsecond == 0
491+
assert testdate.tzinfo == current_timezone()
490492

491493
# parsing without seconds and miliseconds
492494
testdate = typ.traits.from_literal("datetime'1976-11-23T03:33'")
493495
assert testdate.year == 1976
494496
assert testdate.minute == 33
495497
assert testdate.second == 0
496498
assert testdate.microsecond == 0
499+
assert testdate.tzinfo == current_timezone()
497500

498501
# parsing invalid value
499502
with pytest.raises(PyODataModelError) as e_info:
@@ -515,19 +518,22 @@ def test_traits_datetime():
515518
assert testdate.minute == 33
516519
assert testdate.second == 6
517520
assert testdate.microsecond == 10000
521+
assert testdate.tzinfo == current_timezone()
518522

519523
# parsing without miliseconds
520524
testdate = typ.traits.from_json("/Date(217567986000)/")
521525
assert testdate.year == 1976
522526
assert testdate.second == 6
523527
assert testdate.microsecond == 0
528+
assert testdate.tzinfo == current_timezone()
524529

525530
# parsing without seconds and miliseconds
526531
testdate = typ.traits.from_json("/Date(217567980000)/")
527532
assert testdate.year == 1976
528533
assert testdate.minute == 33
529534
assert testdate.second == 0
530535
assert testdate.microsecond == 0
536+
assert testdate.tzinfo == current_timezone()
531537

532538
# parsing the lowest value
533539
with pytest.raises(OverflowError):
@@ -541,6 +547,7 @@ def test_traits_datetime():
541547
assert testdate.minute == 0
542548
assert testdate.second == 0
543549
assert testdate.microsecond == 0
550+
assert testdate.tzinfo == current_timezone()
544551

545552
# parsing the highest value
546553
with pytest.raises(OverflowError):
@@ -554,6 +561,7 @@ def test_traits_datetime():
554561
assert testdate.minute == 59
555562
assert testdate.second == 59
556563
assert testdate.microsecond == 999000
564+
assert testdate.tzinfo == current_timezone()
557565

558566
# parsing invalid value
559567
with pytest.raises(PyODataModelError) as e_info:

0 commit comments

Comments
 (0)