Skip to content

Commit ee1d565

Browse files
msgpack: support tzoffset in datetime
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 [1] timezone info. pytz module is already a dependency of pandas, but this patch adds it as a requirement just in case something will change in the future. 1. https://pypi.org/project/pytz/ Part of #204
1 parent 980fd2c commit ee1d565

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Decimal type support (#203).
1111
- UUID type support (#202).
1212
- Datetime type support (#204).
13+
- Offset in datetime type support (#204).
1314

1415
### Changed
1516
- Bump msgpack requirement to 1.0.4 (PR #223).

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
msgpack>=1.0.4
22
pandas
3+
pytz

tarantool/msgpack_ext/datetime.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import time
22
import math
33
import pandas
4+
import pytz
45

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

49+
SEC_IN_MIN = 60
50+
assert isinstance(SEC_IN_MIN, int)
51+
52+
MIN_IN_DAY = 60 * 24
53+
assert isinstance(MIN_IN_DAY, int)
54+
4855
def get_int_as_bytes(data, size):
4956
return data.to_bytes(size, byteorder=BYTEORDER, signed=True)
5057

5158
def encode(obj):
5259
seconds = obj.value // NSEC_IN_SEC
5360
nsec = obj.value % NSEC_IN_SEC
61+
5462
tzoffset = 0
5563
tzindex = 0
5664

65+
if obj.tz is not None:
66+
if obj.tz.zone is not None:
67+
raise NotImplementedError
68+
else:
69+
utc_offset = obj.tz.utcoffset(0)
70+
# There is no precision loss since pytz.FixedOffset is in minutes
71+
tzoffset = utc_offset.days * MIN_IN_DAY + utc_offset.seconds // SEC_IN_MIN
72+
5773
bytes_buffer = get_int_as_bytes(seconds, SECONDS_SIZE_BYTES)
5874

5975
if (nsec != 0) or (tzoffset != 0) or (tzindex != 0):
@@ -80,9 +96,14 @@ def decode(data):
8096
tzoffset = 0
8197
tzindex = 0
8298

83-
if (tzoffset != 0) or (tzindex != 0):
84-
raise NotImplementedError
85-
8699
total_nsec = seconds * NSEC_IN_SEC + nsec
87100

88-
return pandas.to_datetime(total_nsec, unit='ns')
101+
tzinfo = None
102+
if (tzindex != 0):
103+
raise NotImplementedError
104+
elif (tzoffset != 0):
105+
tzinfo = pytz.FixedOffset(tzoffset)
106+
return pandas.to_datetime(total_nsec, unit='ns').replace(tzinfo=pytz.utc).tz_convert(tzinfo)
107+
else:
108+
# return timezone-naive pandas.Timestamp
109+
return pandas.to_datetime(total_nsec, unit='ns')

test/suites/test_msgpack_ext.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import warnings
1111
import tarantool
1212
import pandas
13+
import pytz
1314

1415
from tarantool.msgpack_ext.packer import default as packer_default
1516
from tarantool.msgpack_ext.unpacker import ext_hook as unpacker_ext_hook
@@ -550,6 +551,22 @@ def test_UUID_tarantool_encode(self):
550551
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
551552
r"nsec=308543321})",
552553
},
554+
'datetime_with_positive_offset': {
555+
'python': pandas.Timestamp(year=2022, month=8, day=31, hour=18, minute=7, second=54,
556+
microsecond=308543, nanosecond=321,
557+
tzinfo=pytz.FixedOffset(180)),
558+
'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\x00\x00'),
559+
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
560+
r"nsec=308543321, tzoffset=180})",
561+
},
562+
'datetime_with_negative_offset': {
563+
'python': pandas.Timestamp(year=2022, month=8, day=31, hour=18, minute=7, second=54,
564+
microsecond=308543, nanosecond=321,
565+
tzinfo=pytz.FixedOffset(-60)),
566+
'msgpack': (b'\x8a\xb1\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xc4\xff\x00\x00'),
567+
'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
568+
r"nsec=308543321, tzoffset=-60})",
569+
},
553570
}
554571

555572
def test_datetime_msgpack_decode(self):

0 commit comments

Comments
 (0)