1
1
"""
2
2
Tarantool `datetime`_ extension type support module.
3
3
4
- Refer to :mod:`~tarantool.msgpack_ext.types.datetime`.
4
+ The datetime MessagePack representation looks like this:
5
5
6
- .. _datetime: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
6
+ .. code-block:: text
7
+
8
+ +---------+----------------+==========+-----------------+
9
+ | MP_EXT | MP_DATETIME | seconds | nsec; tzoffset; |
10
+ | = d7/d8 | = 4 | | tzindex; |
11
+ +---------+----------------+==========+-----------------+
12
+
13
+ MessagePack data contains:
14
+
15
+ * Seconds (8 bytes) as an unencoded 64-bit signed integer stored in the
16
+ little-endian order.
17
+ * The optional fields (8 bytes), if any of them have a non-zero value.
18
+ The fields include nsec (4 bytes), tzoffset (2 bytes), and
19
+ tzindex (2 bytes) packed in the little-endian order.
20
+
21
+ ``seconds`` is seconds since Epoch, where the epoch is the point where
22
+ the time starts, and is platform dependent. For Unix, the epoch is
23
+ January 1, 1970, 00:00:00 (UTC). Tarantool uses a ``double`` type, see a
24
+ structure definition in src/lib/core/datetime.h and reasons in
25
+ `datetime RFC`_.
26
+
27
+ ``nsec`` is nanoseconds, fractional part of seconds. Tarantool uses
28
+ ``int32_t``, see a definition in src/lib/core/datetime.h.
29
+
30
+ ``tzoffset`` is timezone offset in minutes from UTC. Tarantool uses
31
+ ``int16_t`` type, see a structure definition in src/lib/core/datetime.h.
32
+
33
+ ``tzindex`` is Olson timezone id. Tarantool uses ``int16_t`` type, see
34
+ a structure definition in src/lib/core/datetime.h. If both
35
+ ``tzoffset`` and ``tzindex`` are specified, ``tzindex`` has the
36
+ preference and the ``tzoffset`` value is ignored.
37
+
38
+ .. _datetime RFC: https://github.com/tarantool/tarantool/wiki/Datetime-internals#intervals-in-c
7
39
"""
8
40
9
- from tarantool .msgpack_ext .types .datetime import Datetime
41
+ from tarantool .msgpack_ext .types .datetime import (
42
+ NSEC_IN_SEC ,
43
+ SEC_IN_MIN ,
44
+ Datetime ,
45
+ )
46
+ import tarantool .msgpack_ext .types .timezones as tt_timezones
47
+
48
+ from tarantool .error import MsgpackError
10
49
11
50
EXT_ID = 4
12
51
"""
13
52
`datetime`_ type id.
14
53
"""
15
54
55
+ BYTEORDER = 'little'
56
+
57
+ SECONDS_SIZE_BYTES = 8
58
+ NSEC_SIZE_BYTES = 4
59
+ TZOFFSET_SIZE_BYTES = 2
60
+ TZINDEX_SIZE_BYTES = 2
61
+
62
+
63
+ def get_int_as_bytes (data , size ):
64
+ """
65
+ Get binary representation of integer value.
66
+
67
+ :param data: Integer value.
68
+ :type data: :obj:`int`
69
+
70
+ :param size: Integer size, in bytes.
71
+ :type size: :obj:`int`
72
+
73
+ :return: Encoded integer.
74
+ :rtype: :obj:`bytes`
75
+
76
+ :meta private:
77
+ """
78
+
79
+ return data .to_bytes (size , byteorder = BYTEORDER , signed = True )
80
+
16
81
def encode (obj ):
17
82
"""
18
83
Encode a datetime object.
@@ -26,7 +91,48 @@ def encode(obj):
26
91
:raise: :exc:`tarantool.Datetime.msgpack_encode` exceptions
27
92
"""
28
93
29
- return obj .msgpack_encode ()
94
+ seconds = obj .value // NSEC_IN_SEC
95
+ nsec = obj .nsec
96
+ tzoffset = obj .tzoffset
97
+
98
+ tz = obj .tz
99
+ if tz != '' :
100
+ tzindex = tt_timezones .timezoneToIndex [tz ]
101
+ else :
102
+ tzindex = 0
103
+
104
+ buf = get_int_as_bytes (seconds , SECONDS_SIZE_BYTES )
105
+
106
+ if (nsec != 0 ) or (tzoffset != 0 ) or (tzindex != 0 ):
107
+ buf = buf + get_int_as_bytes (nsec , NSEC_SIZE_BYTES )
108
+ buf = buf + get_int_as_bytes (tzoffset , TZOFFSET_SIZE_BYTES )
109
+ buf = buf + get_int_as_bytes (tzindex , TZINDEX_SIZE_BYTES )
110
+
111
+ return buf
112
+
113
+
114
+ def get_bytes_as_int (data , cursor , size ):
115
+ """
116
+ Get integer value from binary data.
117
+
118
+ :param data: MessagePack binary data.
119
+ :type data: :obj:`bytes`
120
+
121
+ :param cursor: Index after last parsed byte.
122
+ :type cursor: :obj:`int`
123
+
124
+ :param size: Integer size, in bytes.
125
+ :type size: :obj:`int`
126
+
127
+ :return: First value: parsed integer, second value: new cursor
128
+ position.
129
+ :rtype: first value: :obj:`int`, second value: :obj:`int`
130
+
131
+ :meta private:
132
+ """
133
+
134
+ part = data [cursor :cursor + size ]
135
+ return int .from_bytes (part , BYTEORDER , signed = True ), cursor + size
30
136
31
137
def decode (data ):
32
138
"""
@@ -38,7 +144,35 @@ def decode(data):
38
144
:return: Decoded datetime.
39
145
:rtype: :class:`tarantool.Datetime`
40
146
41
- :raise: :exc:`tarantool.Datetime` exceptions
147
+ :raise: :exc:`~tarantool.error.MsgpackError`,
148
+ :exc:`tarantool.Datetime` exceptions
42
149
"""
43
150
44
- return Datetime (data )
151
+ cursor = 0
152
+ seconds , cursor = get_bytes_as_int (data , cursor , SECONDS_SIZE_BYTES )
153
+
154
+ data_len = len (data )
155
+ if data_len == (SECONDS_SIZE_BYTES + NSEC_SIZE_BYTES + \
156
+ TZOFFSET_SIZE_BYTES + TZINDEX_SIZE_BYTES ):
157
+ nsec , cursor = get_bytes_as_int (data , cursor , NSEC_SIZE_BYTES )
158
+ tzoffset , cursor = get_bytes_as_int (data , cursor , TZOFFSET_SIZE_BYTES )
159
+ tzindex , cursor = get_bytes_as_int (data , cursor , TZINDEX_SIZE_BYTES )
160
+ elif data_len == SECONDS_SIZE_BYTES :
161
+ nsec = 0
162
+ tzoffset = 0
163
+ tzindex = 0
164
+ else :
165
+ raise MsgpackError (f'Unexpected datetime payload length { data_len } ' )
166
+
167
+ if tzindex != 0 :
168
+ if tzindex not in tt_timezones .indexToTimezone :
169
+ raise MsgpackError (f'Failed to decode datetime with unknown tzindex "{ tzindex } "' )
170
+ tz = tt_timezones .indexToTimezone [tzindex ]
171
+ return Datetime (timestamp = seconds , nsec = nsec , tz = tz ,
172
+ timestamp_since_utc_epoch = True )
173
+ elif tzoffset != 0 :
174
+ return Datetime (timestamp = seconds , nsec = nsec , tzoffset = tzoffset ,
175
+ timestamp_since_utc_epoch = True )
176
+ else :
177
+ return Datetime (timestamp = seconds , nsec = nsec ,
178
+ timestamp_since_utc_epoch = True )
0 commit comments