Skip to content

Commit

Permalink
msgpack: support tzoffset in datetime
Browse files Browse the repository at this point in the history
Support non-zero tzoffset in datetime extended type. If tzoffset and
tzindex are not specified, return timezone-naive pandas.Timestamp. If
tzoffset is specified, return timezone-aware pandas.Timestamp with
pytz.FixedOffset timezone info.

Part of #204
  • Loading branch information
DifferentialOrange committed Sep 2, 2022
1 parent 77761ca commit 2e695b7
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 4 deletions.
29 changes: 25 additions & 4 deletions tarantool/msgpack_ext_types/datetime.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
import math
import pandas
import pytz

# https://www.tarantool.io/ru/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
#
Expand Down Expand Up @@ -45,15 +46,30 @@
NSEC_IN_SEC = 1000000000
assert isinstance(NSEC_IN_SEC, int)

SEC_IN_MIN = 60
assert isinstance(SEC_IN_MIN, int)

MIN_IN_DAY = 60 * 24
assert isinstance(MIN_IN_DAY, int)

def get_int_as_bytes(data, size):
return data.to_bytes(size, byteorder=BYTEORDER, signed=True)

def encode(obj):
seconds = obj.value // NSEC_IN_SEC
nsec = obj.value % NSEC_IN_SEC

tzoffset = 0
tzindex = 0

if obj.tz is not None:
if obj.tz.zone is not None:
raise NotImplementedError
else:
utc_offset = obj.tz.utcoffset(0)
# There is no precision loss since pytz.FixedOffset is in minutes
tzoffset = utc_offset.days * MIN_IN_DAY + utc_offset.seconds // SEC_IN_MIN

bytes_buffer = get_int_as_bytes(seconds, SECONDS_SIZE_BYTES)

if (nsec != 0) or (tzoffset != 0) or (tzindex != 0):
Expand All @@ -80,9 +96,14 @@ def decode(data):
tzoffset = 0
tzindex = 0

if (tzoffset != 0) or (tzindex != 0):
raise NotImplementedError

total_nsec = seconds * NSEC_IN_SEC + nsec

return pandas.to_datetime(total_nsec, unit='ns')
tzinfo = None
if (tzindex != 0):
raise NotImplementedError
elif (tzoffset != 0):
tzinfo = pytz.FixedOffset(tzoffset)
return pandas.to_datetime(total_nsec, unit='ns').replace(tzinfo=pytz.utc).tz_convert(tzinfo)
else:
# return timezone-naive pandas.Timestamp
return pandas.to_datetime(total_nsec, unit='ns')
24 changes: 24 additions & 0 deletions test/suites/test_msgpack_ext_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import warnings
import tarantool
import pandas
import pytz

from tarantool.msgpack_ext_types.packer import default as packer_default
from tarantool.msgpack_ext_types.unpacker import ext_hook as unpacker_ext_hook
Expand Down Expand Up @@ -544,6 +545,29 @@ def test_UUID_tarantool_encode(self):
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
r"nsec=308543321})",
},
{
'python': pandas.Timestamp(year=2022, month=8, day=31, hour=18, minute=7, second=54,
microsecond=308543, nanosecond=321),
'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\x00\x00\x00\x00'),
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
r"nsec=308543321})",
},
{
'python': pandas.Timestamp(year=2022, month=8, day=31, hour=18, minute=7, second=54,
microsecond=308543, nanosecond=321,
tzinfo=pytz.FixedOffset(180)),
'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\x00\x00'),
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
r"nsec=308543321, tzoffset=180})",
},
{
'python': pandas.Timestamp(year=2022, month=8, day=31, hour=18, minute=7, second=54,
microsecond=308543, nanosecond=321,
tzinfo=pytz.FixedOffset(-60)),
'msgpack': (b'\x8a\xb1\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xc4\xff\x00\x00'),
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
r"nsec=308543321, tzoffset=-60})",
},
]

def test_datetime_msgpack_decode(self):
Expand Down

0 comments on commit 2e695b7

Please sign in to comment.