diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 5412ca9c..67cc1ad3 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. + value = datetime.datetime(year=1, day=1, month=1, tzinfo=current_timezone()) + elif FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE and int(value) > 253402300799999: + 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. ' + 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