3
3
import pandas
4
4
import pytz
5
5
6
+ import tarantool .msgpack_ext .types .timezones as tt_timezones
7
+ from tarantool .error import MsgpackError
8
+
6
9
# https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
7
10
#
8
11
# The datetime MessagePack representation looks like this:
@@ -63,6 +66,17 @@ def compute_offset(timestamp):
63
66
# There is no precision loss since offset is in minutes
64
67
return int (utc_offset .total_seconds ()) // SEC_IN_MIN
65
68
69
+ def get_python_tzinfo (tz , error_class ):
70
+ if tz in pytz .all_timezones :
71
+ return pytz .timezone (tz )
72
+
73
+ # Checked with timezones/validate_timezones.py
74
+ tt_tzinfo = tt_timezones .timezoneAbbrevInfo [tz ]
75
+ if (tt_tzinfo ['category' ] & tt_timezones .TZ_AMBIGUOUS ) != 0 :
76
+ raise error_class (f'Failed to create datetime with ambiguous timezone "{ tz } "' )
77
+
78
+ return pytz .FixedOffset (tt_tzinfo ['offset' ])
79
+
66
80
def msgpack_decode (data ):
67
81
cursor = 0
68
82
seconds , cursor = get_bytes_as_int (data , cursor , SECONDS_SIZE_BYTES )
@@ -84,23 +98,29 @@ def msgpack_decode(data):
84
98
datetime = pandas .to_datetime (total_nsec , unit = 'ns' )
85
99
86
100
if tzindex != 0 :
87
- raise NotImplementedError
101
+ if tzindex not in tt_timezones .indexToTimezone :
102
+ raise MsgpackError (f'Failed to decode datetime with unknown tzindex "{ tzindex } "' )
103
+ tz = tt_timezones .indexToTimezone [tzindex ]
104
+ tzinfo = get_python_tzinfo (tz , MsgpackError )
105
+ return datetime .replace (tzinfo = pytz .UTC ).tz_convert (tzinfo ), tz
88
106
elif tzoffset != 0 :
89
107
tzinfo = pytz .FixedOffset (tzoffset )
90
- return datetime .replace (tzinfo = pytz .UTC ).tz_convert (tzinfo )
108
+ return datetime .replace (tzinfo = pytz .UTC ).tz_convert (tzinfo ), ''
91
109
else :
92
- return datetime
110
+ return datetime , ''
93
111
94
112
class Datetime ():
95
113
def __init__ (self , data = None , * , timestamp = None , year = None , month = None ,
96
114
day = None , hour = None , minute = None , sec = None , nsec = None ,
97
- tzoffset = 0 ):
115
+ tzoffset = 0 , tz = '' ):
98
116
if data is not None :
99
117
if not isinstance (data , bytes ):
100
118
raise ValueError ('data argument (first positional argument) ' +
101
119
'expected to be a "bytes" instance' )
102
120
103
- self ._datetime = msgpack_decode (data )
121
+ datetime , tz = msgpack_decode (data )
122
+ self ._datetime = datetime
123
+ self ._tz = tz
104
124
return
105
125
106
126
# The logic is same as in Tarantool, refer to datetime API.
@@ -133,11 +153,20 @@ def __init__(self, data=None, *, timestamp=None, year=None, month=None,
133
153
microsecond = microsecond ,
134
154
nanosecond = nanosecond )
135
155
136
- if tzoffset != 0 :
137
- tzinfo = pytz . FixedOffset ( tzoffset )
138
- datetime = datetime . replace ( tzinfo = tzinfo )
156
+ if tz != '' :
157
+ if tz not in tt_timezones . timezoneToIndex :
158
+ raise ValueError ( f'Unknown Tarantool timezone " { tz } "' )
139
159
140
- self ._datetime = datetime
160
+ tzinfo = get_python_tzinfo (tz , ValueError )
161
+ self ._datetime = datetime .replace (tzinfo = tzinfo )
162
+ self ._tz = tz
163
+ elif tzoffset != 0 :
164
+ tzinfo = pytz .FixedOffset (tzoffset )
165
+ self ._datetime = datetime .replace (tzinfo = tzinfo )
166
+ self ._tz = ''
167
+ else :
168
+ self ._datetime = datetime
169
+ self ._tz = ''
141
170
142
171
def __eq__ (self , other ):
143
172
if isinstance (other , Datetime ):
@@ -151,7 +180,7 @@ def __str__(self):
151
180
return self ._datetime .__str__ ()
152
181
153
182
def __repr__ (self ):
154
- return f'datetime: { self ._datetime .__repr__ ()} '
183
+ return f'datetime: { self ._datetime .__repr__ ()} , tz: " { self . tz } " '
155
184
156
185
def __copy__ (self ):
157
186
cls = self .__class__
@@ -206,11 +235,20 @@ def tzoffset(self):
206
235
return compute_offset (self ._datetime )
207
236
return 0
208
237
238
+ @property
239
+ def tz (self ):
240
+ return self ._tz
241
+
209
242
def msgpack_encode (self ):
210
243
seconds = self ._datetime .value // NSEC_IN_SEC
211
244
nsec = self .nsec
212
245
tzoffset = self .tzoffset
213
- tzindex = 0
246
+
247
+ tz = self .tz
248
+ if tz != '' :
249
+ tzindex = tt_timezones .timezoneToIndex [tz ]
250
+ else :
251
+ tzindex = 0
214
252
215
253
buf = get_int_as_bytes (seconds , SECONDS_SIZE_BYTES )
216
254
0 commit comments