From 2923f7477fd7fb2a8c14208ca9ff4d49337f7ed8 Mon Sep 17 00:00:00 2001 From: Timo S Date: Fri, 19 Mar 2021 13:11:03 +0000 Subject: [PATCH 1/2] #143 Workaround for screwed up date values - Adds constant FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE to return 0001-01-01 - Adds constant FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE to return 9999-12-31 - Add new unittests --- pyodata/v2/model.py | 17 +++++++++++++++-- tests/test_model_v2.py | 27 ++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 5412ca9c..55c4d610 100644 --- a/pyodata/v2/model.py +++ b/pyodata/v2/model.py @@ -21,6 +21,8 @@ from pyodata.exceptions import PyODataException, PyODataModelError, PyODataParserError LOGGER_NAME = 'pyodata.model' +FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE = False +FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE = False IdentifierInfo = collections.namedtuple('IdentifierInfo', 'namespace name') TypeInfo = collections.namedtuple('TypeInfo', 'namespace name is_collection') @@ -414,8 +416,19 @@ def from_json(self, value): try: # https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function value = datetime.datetime(1970, 1, 1, tzinfo=current_timezone()) + datetime.timedelta(milliseconds=int(value)) - except ValueError: - raise PyODataModelError(f'Cannot decode datetime from value {value}.') + except (ValueError, OverflowError): + if FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE and int(value) < -62135596800000: + # Some service providers return false minimal date values. + # -62135596800000 is the lowest value PyOData could read. + # This workaroud fixes this issue and returns 0001-01-01 00:00:00+00:00 in such a case. + return datetime.datetime(year=1, day=1, month=1, tzinfo=current_timezone()) + elif FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE and int(value) > 253402300799999: + return datetime.datetime(year=9999, day=31, month=12, tzinfo=current_timezone()) + else: + raise PyODataModelError(f'Cannot decode datetime from value {value}. ' + f'Possible value range: -62135596800000 to 253402300799999. ' + f'You may fix this by setting `FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE` ' + f' or `FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE` as a workaround.') return value diff --git a/tests/test_model_v2.py b/tests/test_model_v2.py index ca724304..8f7eceff 100644 --- a/tests/test_model_v2.py +++ b/tests/test_model_v2.py @@ -9,6 +9,7 @@ PolicyIgnore, Config, PolicyFatal, NullType, NullAssociation, current_timezone, StructType from pyodata.exceptions import PyODataException, PyODataModelError, PyODataParserError from tests.conftest import assert_logging_policy +import pyodata.v2.model def test_edmx(schema): @@ -537,10 +538,20 @@ def test_traits_datetime(): assert testdate.microsecond == 0 assert testdate.tzinfo == current_timezone() + # parsing below lowest value with workaround + pyodata.v2.model.FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE = True + testdate = typ.traits.from_json("/Date(-62135596800001)/") + assert testdate.year == 1 + assert testdate.month == 1 + assert testdate.day == 1 + assert testdate.tzinfo == current_timezone() + # parsing the lowest value - with pytest.raises(OverflowError): + pyodata.v2.model.FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE = False + with pytest.raises(PyODataModelError) as e_info: typ.traits.from_json("/Date(-62135596800001)/") - + assert str(e_info.value).startswith('Cannot decode datetime from value -62135596800001.') + testdate = typ.traits.from_json("/Date(-62135596800000)/") assert testdate.year == 1 assert testdate.month == 1 @@ -551,9 +562,19 @@ def test_traits_datetime(): assert testdate.microsecond == 0 assert testdate.tzinfo == current_timezone() + # parsing above highest value with workaround + pyodata.v2.model.FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE = True + testdate = typ.traits.from_json("/Date(253402300800000)/") + assert testdate.year == 9999 + assert testdate.month == 12 + assert testdate.day == 31 + assert testdate.tzinfo == current_timezone() + # parsing the highest value - with pytest.raises(OverflowError): + pyodata.v2.model.FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE = False + with pytest.raises(PyODataModelError) as e_info: typ.traits.from_json("/Date(253402300800000)/") + assert str(e_info.value).startswith('Cannot decode datetime from value 253402300800000.') testdate = typ.traits.from_json("/Date(253402300799999)/") assert testdate.year == 9999 From 77885dadaa84816daa81b8200a86cd545aeafb05 Mon Sep 17 00:00:00 2001 From: Timo S Date: Fri, 19 Mar 2021 13:18:50 +0000 Subject: [PATCH 2/2] fix elif after return --- pyodata/v2/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 55c4d610..67cc1ad3 100644 --- a/pyodata/v2/model.py +++ b/pyodata/v2/model.py @@ -418,12 +418,12 @@ def from_json(self, value): value = datetime.datetime(1970, 1, 1, tzinfo=current_timezone()) + datetime.timedelta(milliseconds=int(value)) except (ValueError, OverflowError): if FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE and int(value) < -62135596800000: - # Some service providers return false minimal date values. + # Some service providers return false minimal date values. # -62135596800000 is the lowest value PyOData could read. # This workaroud fixes this issue and returns 0001-01-01 00:00:00+00:00 in such a case. - return datetime.datetime(year=1, day=1, month=1, tzinfo=current_timezone()) + value = datetime.datetime(year=1, day=1, month=1, tzinfo=current_timezone()) elif FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE and int(value) > 253402300799999: - return datetime.datetime(year=9999, day=31, month=12, tzinfo=current_timezone()) + value = datetime.datetime(year=9999, day=31, month=12, tzinfo=current_timezone()) else: raise PyODataModelError(f'Cannot decode datetime from value {value}. ' f'Possible value range: -62135596800000 to 253402300799999. '