Skip to content

Commit 7b9abe2

Browse files
datetime: support big values for some platforms
Before this patch, tarantool.Datetime constructor used datetime.fromtimestamp function to build a new datetime [1], except for negative timestamps for Windows platform. This constructor branch is used on each Tarantool datetime encoding or while building a tarantool.Datetime object from timestamp. datetime.fromtimestamp have some drawbacks: it "may raise OverflowError, if the timestamp is out of the range of values supported by the platform C localtime() or gmtime() functions, and OSError on localtime() or gmtime() failure. It’s common for this to be restricted to years in 1970 through 2038.". It had never happened on supported Unix platforms, but seem to be an issue for Windows ones. We already workaround this issue for years smaller than 1970 on Windows. After this patch, this workaround will be used for all platforms and timestamp values, allowing to provide similar behavior for platforms both restricted to years in 1970 through 2038 with localtime() or gmtime() or not. 1. https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp
1 parent fea0d5f commit 7b9abe2

File tree

3 files changed

+50
-8
lines changed

3 files changed

+50
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Fixed
1010
- Exception rethrow in crud API (PR #310).
11+
- Work with timestamps larger than year 2038 for some platforms (like Windows) (PR #311).
12+
It covers
13+
- building new tarantool.Datetime objects from timestamp,
14+
- parsing datetime objects received from Tarantool.
1115

1216
## 1.1.1 - 2023-07-19
1317

tarantool/msgpack_ext/types/datetime.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from calendar import monthrange
77
from copy import deepcopy
88
from datetime import datetime, timedelta
9-
import sys
109

1110
import pytz
1211

@@ -314,13 +313,12 @@ def __init__(self, *, timestamp=None, year=None, month=None,
314313
timestamp += nsec // NSEC_IN_SEC
315314
nsec = nsec % NSEC_IN_SEC
316315

317-
if (sys.platform.startswith("win")) and (timestamp < 0):
318-
# Fails to create a datetime from negative timestamp on Windows.
319-
_datetime = _EPOCH + timedelta(seconds=timestamp)
320-
else:
321-
# Timezone-naive datetime objects are treated by many datetime methods
322-
# as local times, so we represent time in UTC explicitly if not provided.
323-
_datetime = datetime.fromtimestamp(timestamp, pytz.UTC)
316+
# datetime.fromtimestamp may raise OverflowError, if the timestamp
317+
# is out of the range of values supported by the platform C localtime()
318+
# function, and OSError on localtime() failure. It’s common for this
319+
# to be restricted to years from 1970 through 2038, yet we want
320+
# to support a wider range.
321+
_datetime = _EPOCH + timedelta(seconds=timestamp)
324322

325323
if nsec is not None:
326324
_datetime = _datetime.replace(microsecond=nsec // NSEC_IN_MKSEC)

test/suites/test_datetime.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ def test_datetime_class_api_wth_tz(self):
153153
'type': ValueError,
154154
'msg': 'Failed to create datetime with ambiguous timezone "AET"'
155155
},
156+
'under_min_timestamp_1': {
157+
'args': [],
158+
'kwargs': {'timestamp': -62135596801},
159+
'type': OverflowError,
160+
'msg': 'date value out of range'
161+
},
162+
'under_min_timestamp_2': {
163+
'args': [],
164+
'kwargs': {'timestamp': -62135596800, 'nsec': -1},
165+
'type': OverflowError,
166+
'msg': 'date value out of range'
167+
},
168+
'over_max_timestamp': {
169+
'args': [],
170+
'kwargs': {'timestamp': 253402300800},
171+
'type': OverflowError,
172+
'msg': 'date value out of range'
173+
},
156174
}
157175

158176
def test_datetime_class_invalid_init(self):
@@ -293,6 +311,28 @@ def test_datetime_class_invalid_init(self):
293311
'tarantool': r"datetime.new({timestamp=1661969274, nsec=308543321, "
294312
r"tz='Europe/Moscow'})",
295313
},
314+
'min_datetime': { # Python datetime.MINYEAR is 1.
315+
'python': tarantool.Datetime(year=1, month=1, day=1, hour=0, minute=0, sec=0),
316+
'msgpack': (b'\x00\x09\x6e\x88\xf1\xff\xff\xff'),
317+
'tarantool': r"datetime.new({year=1, month=1, day=1, hour=0, min=0, sec=0})",
318+
},
319+
'max_datetime': { # Python datetime.MAXYEAR is 9999.
320+
'python': tarantool.Datetime(year=9999, month=12, day=31, hour=23, minute=59, sec=59,
321+
nsec=999999999),
322+
'msgpack': (b'\x7f\x41\xf4\xff\x3a\x00\x00\x00\xff\xc9\x9a\x3b\x00\x00\x00\x00'),
323+
'tarantool': r"datetime.new({year=9999, month=12, day=31, hour=23, min=59, sec=59,"
324+
r"nsec=999999999})",
325+
},
326+
'min_datetime_timestamp': { # Python datetime.MINYEAR is 1.
327+
'python': tarantool.Datetime(timestamp=-62135596800),
328+
'msgpack': (b'\x00\x09\x6e\x88\xf1\xff\xff\xff'),
329+
'tarantool': r"datetime.new({timestamp=-62135596800})",
330+
},
331+
'max_datetime_timestamp': { # Python datetime.MAXYEAR is 9999.
332+
'python': tarantool.Datetime(timestamp=253402300799, nsec=999999999),
333+
'msgpack': (b'\x7f\x41\xf4\xff\x3a\x00\x00\x00\xff\xc9\x9a\x3b\x00\x00\x00\x00'),
334+
'tarantool': r"datetime.new({timestamp=253402300799, nsec=999999999})",
335+
},
296336
}
297337

298338
def test_msgpack_decode(self):

0 commit comments

Comments
 (0)