Skip to content

Commit

Permalink
Fix decoding of fractional timestamps before Postgres epoch
Browse files Browse the repository at this point in the history
The datetime decoder currently incorrectly uses an unsigned integer type
for microseconds, which leads to incorrect decoding of timestamps with
fractional seconds which represent dates before Jan 1 2000.  Same issue
affects negative intervals with fractional seconds.

Fixes: #363
  • Loading branch information
elprans committed Sep 18, 2018
1 parent 687127e commit a7eaf2b
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 14 deletions.
22 changes: 10 additions & 12 deletions asyncpg/protocol/codecs/datetime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,17 @@ cdef inline _encode_time(WriteBuffer buf, int64_t seconds,


cdef inline int32_t _decode_time(FastReadBuffer buf, int64_t *seconds,
uint32_t *microseconds):
# XXX: add support for double timestamps
# int64 timestamps,
int32_t *microseconds):
cdef int64_t ts = hton.unpack_int64(buf.read(8))

if ts == pg_time64_infinity:
return 1
elif ts == pg_time64_negative_infinity:
return -1

seconds[0] = <int64_t>(ts / 1000000)
microseconds[0] = <uint32_t>(ts % 1000000)

return 0
else:
seconds[0] = ts // 1000000
microseconds[0] = <int32_t>(ts % 1000000)
return 0


cdef date_encode(ConnectionSettings settings, WriteBuffer buf, obj):
Expand Down Expand Up @@ -181,7 +178,7 @@ cdef timestamp_encode_tuple(ConnectionSettings settings, WriteBuffer buf, obj):
cdef timestamp_decode(ConnectionSettings settings, FastReadBuffer buf):
cdef:
int64_t seconds = 0
uint32_t microseconds = 0
int32_t microseconds = 0
int32_t inf = _decode_time(buf, &seconds, &microseconds)

if inf > 0:
Expand Down Expand Up @@ -242,7 +239,7 @@ cdef timestamptz_encode(ConnectionSettings settings, WriteBuffer buf, obj):
cdef timestamptz_decode(ConnectionSettings settings, FastReadBuffer buf):
cdef:
int64_t seconds = 0
uint32_t microseconds = 0
int32_t microseconds = 0
int32_t inf = _decode_time(buf, &seconds, &microseconds)

if inf > 0:
Expand Down Expand Up @@ -285,7 +282,7 @@ cdef time_encode_tuple(ConnectionSettings settings, WriteBuffer buf, obj):
cdef time_decode(ConnectionSettings settings, FastReadBuffer buf):
cdef:
int64_t seconds = 0
uint32_t microseconds = 0
int32_t microseconds = 0

_decode_time(buf, &seconds, &microseconds)

Expand Down Expand Up @@ -400,9 +397,10 @@ cdef interval_decode(ConnectionSettings settings, FastReadBuffer buf):
int32_t months
int32_t years
int64_t seconds = 0
uint32_t microseconds = 0
int32_t microseconds = 0

_decode_time(buf, &seconds, &microseconds)

days = hton.unpack_int32(buf.read(4))
months = hton.unpack_int32(buf.read(4))

Expand Down
28 changes: 26 additions & 2 deletions tests/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ def _system_timezone():
{'textinput': 'infinity', 'output': infinity_datetime},
{'textinput': '-infinity', 'output': negative_infinity_datetime},
{'input': datetime.date(2000, 1, 1),
'output': datetime.datetime(2000, 1, 1)}
'output': datetime.datetime(2000, 1, 1)},
{'textinput': '1970-01-01 20:31:23.648',
'output': datetime.datetime(1970, 1, 1, 20, 31, 23, 648000)},
{'input': datetime.datetime(1970, 1, 1, 20, 31, 23, 648000),
'textoutput': '1970-01-01 20:31:23.648'},
]),
('date', 'date', [
datetime.date(3000, 5, 20),
Expand Down Expand Up @@ -215,12 +219,29 @@ def _system_timezone():
datetime.time(22, 30, 0, tzinfo=_timezone(0)),
]),
('interval', 'interval', [
# no months :(
datetime.timedelta(40, 10, 1234),
datetime.timedelta(0, 0, 4321),
datetime.timedelta(0, 0),
datetime.timedelta(-100, 0),
datetime.timedelta(-100, -400),
{
'textinput': '-2 years -11 months -10 days '
'-2 hours -800 milliseconds',
'output': datetime.timedelta(
days=(-2 * 365) + (-11 * 30) - 10,
seconds=(-2 * 3600),
milliseconds=-800
),
},
{
'query': 'SELECT justify_hours($1::interval)::text',
'input': datetime.timedelta(
days=(-2 * 365) + (-11 * 30) - 10,
seconds=(-2 * 3600),
milliseconds=-800
),
'textoutput': '-1070 days -02:00:00.8',
},
]),
('uuid', 'uuid', [
uuid.UUID('38a4ff5a-3a56-11e6-a6c2-c8f73323c6d4'),
Expand Down Expand Up @@ -458,6 +479,9 @@ async def test_standard_codecs(self):
stmt = text_out
else:
outputval = sample['output']

if sample.get('query'):
stmt = await self.con.prepare(sample['query'])
else:
inputval = outputval = sample

Expand Down

0 comments on commit a7eaf2b

Please sign in to comment.