Repository: tarantool/tarantool-python (language: python)

Issue:
Support datetime
It is not offered ATM (2021-12-14), but AFAIK there is agreement on the representation in msgpack. See https://github.com/tarantool/tarantool/pull/6660.

Python documentation: https://docs.python.org/3/library/datetime.html.
Support datetime
It is not offered ATM (2021-12-14), but AFAIK there is agreement on the representation in msgpack. See https://github.com/tarantool/tarantool/pull/6660.

Python documentation: https://docs.python.org/3/library/datetime.html.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 226909d8..ad7c2cb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - Decimal type support (#203).
 - UUID type support (#202).
+- Datetime type support and tarantool.Datetime type (#204).
+
+  Tarantool datetime objects are decoded to `tarantool.Datetime`
+  type. `tarantool.Datetime` may be encoded to Tarantool datetime
+  objects.
+
+  You can create `tarantool.Datetime` objects either from msgpack
+  data or by using the same API as in Tarantool:
+
+  ```python
+  dt1 = tarantool.Datetime(year=2022, month=8, day=31,
+                           hour=18, minute=7, sec=54,
+                           nsec=308543321)
+
+  dt2 = tarantool.Datetime(timestamp=1661969274)
+
+  dt3 = tarantool.Datetime(timestamp=1661969274, nsec=308543321)
+  ```
+
+  `tarantool.Datetime` exposes `year`, `month`, `day`, `hour`,
+  `minute`, `sec`, `nsec`, `timestamp` and `value` (integer epoch time
+  with nanoseconds precision) properties if you need to convert
+  `tarantool.Datetime` to any other kind of datetime object:
+
+  ```python
+  pdt = pandas.Timestamp(year=dt.year, month=dt.month, day=dt.day,
+                         hour=dt.hour, minute=dt.minute, second=dt.sec,
+                         microsecond=(dt.nsec // 1000),
+                         nanosecond=(dt.nsec % 1000))
+  ```
+
+- Offset in datetime type support (#204).
+
+  Use `tzoffset` parameter to set up offset timezone:
+
+  ```python
+  dt = tarantool.Datetime(year=2022, month=8, day=31,
+                          hour=18, minute=7, sec=54,
+                          nsec=308543321, tzoffset=180)
+  ```
+
+  You may use `tzoffset` property to get timezone offset of a datetime
+  object.
+
+- Timezone in datetime type support (#204).
+
+  Use `tz` parameter to set up timezone name:
+
+  ```python
+  dt = tarantool.Datetime(year=2022, month=8, day=31,
+                          hour=18, minute=7, sec=54,
+                          nsec=308543321, tz='Europe/Moscow')
+  ```
+
+  If both `tz` and `tzoffset` is specified, `tz` is used.
+
+  You may use `tz` property to get timezone name of a datetime object.
 
 ### Changed
 - Bump msgpack requirement to 1.0.4 (PR #223).
diff --git a/requirements.txt b/requirements.txt
index 46dff380..2a9c9c38 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,3 @@
 msgpack>=1.0.4
+pandas
+pytz
diff --git a/tarantool/__init__.py b/tarantool/__init__.py
index 3d4a19a8..6625b4eb 100644
--- a/tarantool/__init__.py
+++ b/tarantool/__init__.py
@@ -32,6 +32,10 @@
     ENCODING_DEFAULT,
 )
 
+from tarantool.msgpack_ext.types.datetime import (
+    Datetime,
+)
+
 __version__ = "0.9.0"
 
 
@@ -91,7 +95,7 @@ def connectmesh(addrs=({'host': 'localhost', 'port': 3301},), user=None,
 
 __all__ = ['connect', 'Connection', 'connectmesh', 'MeshConnection', 'Schema',
            'Error', 'DatabaseError', 'NetworkError', 'NetworkWarning',
-           'SchemaError', 'dbapi']
+           'SchemaError', 'dbapi', 'Datetime']
 
 # ConnectionPool is supported only for Python 3.7 or newer.
 if sys.version_info.major >= 3 and sys.version_info.minor >= 7:
diff --git a/tarantool/msgpack_ext/datetime.py b/tarantool/msgpack_ext/datetime.py
new file mode 100644
index 00000000..70f56dc9
--- /dev/null
+++ b/tarantool/msgpack_ext/datetime.py
@@ -0,0 +1,9 @@
+from tarantool.msgpack_ext.types.datetime import Datetime
+
+EXT_ID = 4
+
+def encode(obj):
+    return obj.msgpack_encode()
+
+def decode(data):
+    return Datetime(data)
diff --git a/tarantool/msgpack_ext/packer.py b/tarantool/msgpack_ext/packer.py
index e8dd74db..bff2b821 100644
--- a/tarantool/msgpack_ext/packer.py
+++ b/tarantool/msgpack_ext/packer.py
@@ -2,12 +2,16 @@
 from uuid import UUID
 from msgpack import ExtType
 
+from tarantool.msgpack_ext.types.datetime import Datetime
+
 import tarantool.msgpack_ext.decimal as ext_decimal
 import tarantool.msgpack_ext.uuid as ext_uuid
+import tarantool.msgpack_ext.datetime as ext_datetime
 
 encoders = [
-    {'type': Decimal, 'ext': ext_decimal},
-    {'type': UUID,    'ext': ext_uuid   },
+    {'type': Decimal,  'ext': ext_decimal },
+    {'type': UUID,     'ext': ext_uuid    },
+    {'type': Datetime, 'ext': ext_datetime},
 ]
 
 def default(obj):
diff --git a/tarantool/msgpack_ext/types/datetime.py b/tarantool/msgpack_ext/types/datetime.py
new file mode 100644
index 00000000..62774217
--- /dev/null
+++ b/tarantool/msgpack_ext/types/datetime.py
@@ -0,0 +1,264 @@
+from copy import deepcopy
+
+import pandas
+import pytz
+
+import tarantool.msgpack_ext.types.timezones as tt_timezones
+from tarantool.error import MsgpackError
+
+# https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
+#
+# The datetime MessagePack representation looks like this:
+# +---------+----------------+==========+-----------------+
+# | MP_EXT  | MP_DATETIME    | seconds  | nsec; tzoffset; |
+# | = d7/d8 | = 4            |          | tzindex;        |
+# +---------+----------------+==========+-----------------+
+# MessagePack data contains:
+#
+# * Seconds (8 bytes) as an unencoded 64-bit signed integer stored in the
+#   little-endian order.
+# * The optional fields (8 bytes), if any of them have a non-zero value.
+#   The fields include nsec (4 bytes), tzoffset (2 bytes), and
+#   tzindex (2 bytes) packed in the little-endian order.
+#
+# seconds is seconds since Epoch, where the epoch is the point where the time
+# starts, and is platform dependent. For Unix, the epoch is January 1,
+# 1970, 00:00:00 (UTC). Tarantool uses a double type, see a structure
+# definition in src/lib/core/datetime.h and reasons in
+# https://github.com/tarantool/tarantool/wiki/Datetime-internals#intervals-in-c
+#
+# nsec is nanoseconds, fractional part of seconds. Tarantool uses int32_t, see
+# a definition in src/lib/core/datetime.h.
+#
+# tzoffset is timezone offset in minutes from UTC. Tarantool uses a int16_t type,
+# see a structure definition in src/lib/core/datetime.h.
+#
+# tzindex is Olson timezone id. Tarantool uses a int16_t type, see a structure
+# definition in src/lib/core/datetime.h. If both tzoffset and tzindex are
+# specified, tzindex has the preference and the tzoffset value is ignored.
+
+SECONDS_SIZE_BYTES  = 8
+NSEC_SIZE_BYTES     = 4
+TZOFFSET_SIZE_BYTES = 2
+TZINDEX_SIZE_BYTES  = 2
+
+BYTEORDER = 'little'
+
+NSEC_IN_SEC = 1000000000
+NSEC_IN_MKSEC = 1000
+SEC_IN_MIN = 60
+
+def get_bytes_as_int(data, cursor, size):
+    part = data[cursor:cursor + size]
+    return int.from_bytes(part, BYTEORDER, signed=True), cursor + size
+
+def get_int_as_bytes(data, size):
+    return data.to_bytes(size, byteorder=BYTEORDER, signed=True)
+
+def compute_offset(timestamp):
+    utc_offset = timestamp.tzinfo.utcoffset(timestamp)
+
+    # `None` offset is a valid utcoffset implementation,
+    # but it seems that pytz timezones never return `None`:
+    # https://github.com/pandas-dev/pandas/issues/15986
+    assert utc_offset is not None
+
+    # There is no precision loss since offset is in minutes
+    return int(utc_offset.total_seconds()) // SEC_IN_MIN
+
+def get_python_tzinfo(tz, error_class):
+    if tz in pytz.all_timezones:
+        return pytz.timezone(tz)
+
+    # Checked with timezones/validate_timezones.py
+    tt_tzinfo = tt_timezones.timezoneAbbrevInfo[tz]
+    if (tt_tzinfo['category'] & tt_timezones.TZ_AMBIGUOUS) != 0:
+        raise error_class(f'Failed to create datetime with ambiguous timezone "{tz}"')
+
+    return pytz.FixedOffset(tt_tzinfo['offset'])
+
+def msgpack_decode(data):
+    cursor = 0
+    seconds, cursor = get_bytes_as_int(data, cursor, SECONDS_SIZE_BYTES)
+
+    data_len = len(data)
+    if data_len == (SECONDS_SIZE_BYTES + NSEC_SIZE_BYTES + \
+                    TZOFFSET_SIZE_BYTES + TZINDEX_SIZE_BYTES):
+        nsec, cursor     = get_bytes_as_int(data, cursor, NSEC_SIZE_BYTES)
+        tzoffset, cursor = get_bytes_as_int(data, cursor, TZOFFSET_SIZE_BYTES)
+        tzindex, cursor  = get_bytes_as_int(data, cursor, TZINDEX_SIZE_BYTES)
+    elif data_len == SECONDS_SIZE_BYTES:
+        nsec = 0
+        tzoffset = 0
+        tzindex = 0
+    else:
+        raise MsgpackError(f'Unexpected datetime payload length {data_len}')
+
+    total_nsec = seconds * NSEC_IN_SEC + nsec
+    datetime = pandas.to_datetime(total_nsec, unit='ns')
+
+    if tzindex != 0:
+        if tzindex not in tt_timezones.indexToTimezone:
+            raise MsgpackError(f'Failed to decode datetime with unknown tzindex "{tzindex}"')
+        tz = tt_timezones.indexToTimezone[tzindex]
+        tzinfo = get_python_tzinfo(tz, MsgpackError)
+        return datetime.replace(tzinfo=pytz.UTC).tz_convert(tzinfo), tz
+    elif tzoffset != 0:
+        tzinfo = pytz.FixedOffset(tzoffset)
+        return datetime.replace(tzinfo=pytz.UTC).tz_convert(tzinfo), ''
+    else:
+        return datetime, ''
+
+class Datetime():
+    def __init__(self, data=None, *, timestamp=None, year=None, month=None,
+                 day=None, hour=None, minute=None, sec=None, nsec=None,
+                 tzoffset=0, tz=''):
+        if data is not None:
+            if not isinstance(data, bytes):
+                raise ValueError('data argument (first positional argument) ' +
+                                 'expected to be a "bytes" instance')
+
+            datetime, tz = msgpack_decode(data)
+            self._datetime = datetime
+            self._tz = tz
+            return
+
+        # The logic is same as in Tarantool, refer to datetime API.
+        # https://www.tarantool.io/en/doc/latest/reference/reference_lua/datetime/new/
+        if timestamp is not None:
+            if ((year is not None) or (month is not None) or \
+                    (day is not None) or (hour is not None) or \
+                    (minute is not None) or (sec is not None)):
+                raise ValueError('Cannot provide both timestamp and year, month, ' +
+                                 'day, hour, minute, sec')
+
+            if nsec is not None:
+                if not isinstance(timestamp, int):
+                    raise ValueError('timestamp must be int if nsec provided')
+
+                total_nsec = timestamp * NSEC_IN_SEC + nsec
+                datetime = pandas.to_datetime(total_nsec, unit='ns')
+            else:
+                datetime = pandas.to_datetime(timestamp, unit='s')
+        else:
+            if nsec is not None:
+                microsecond = nsec // NSEC_IN_MKSEC
+                nanosecond = nsec % NSEC_IN_MKSEC
+            else:
+                microsecond = 0
+                nanosecond = 0
+
+            datetime = pandas.Timestamp(year=year, month=month, day=day,
+                                        hour=hour, minute=minute, second=sec,
+                                        microsecond=microsecond,
+                                        nanosecond=nanosecond)
+
+        if tz != '':
+            if tz not in tt_timezones.timezoneToIndex:
+                raise ValueError(f'Unknown Tarantool timezone "{tz}"')
+
+            tzinfo = get_python_tzinfo(tz, ValueError)
+            self._datetime = datetime.replace(tzinfo=tzinfo)
+            self._tz = tz
+        elif tzoffset != 0:
+            tzinfo = pytz.FixedOffset(tzoffset)
+            self._datetime = datetime.replace(tzinfo=tzinfo)
+            self._tz = ''
+        else:
+            self._datetime = datetime
+            self._tz = ''
+
+    def __eq__(self, other):
+        if isinstance(other, Datetime):
+            return self._datetime == other._datetime
+        elif isinstance(other, pandas.Timestamp):
+            return self._datetime == other
+        else:
+            return False
+
+    def __str__(self):
+        return self._datetime.__str__()
+
+    def __repr__(self):
+        return f'datetime: {self._datetime.__repr__()}, tz: "{self.tz}"'
+
+    def __copy__(self):
+        cls = self.__class__
+        result = cls.__new__(cls)
+        result.__dict__.update(self.__dict__)
+        return result
+
+    def __deepcopy__(self, memo):
+        cls = self.__class__
+        result = cls.__new__(cls)
+        memo[id(self)] = result
+        for k, v in self.__dict__.items():
+            setattr(result, k, deepcopy(v, memo))
+        return result
+
+    @property
+    def year(self):
+        return self._datetime.year
+
+    @property
+    def month(self):
+        return self._datetime.month
+
+    @property
+    def day(self):
+        return self._datetime.day
+
+    @property
+    def hour(self):
+        return self._datetime.hour
+
+    @property
+    def minute(self):
+        return self._datetime.minute
+
+    @property
+    def sec(self):
+        return self._datetime.second
+
+    @property
+    def nsec(self):
+        # microseconds + nanoseconds
+        return self._datetime.value % NSEC_IN_SEC
+
+    @property
+    def timestamp(self):
+        return self._datetime.timestamp()
+
+    @property
+    def tzoffset(self):
+        if self._datetime.tzinfo is not None:
+            return compute_offset(self._datetime)
+        return 0
+
+    @property
+    def tz(self):
+        return self._tz
+
+    @property
+    def value(self):
+        return self._datetime.value
+
+    def msgpack_encode(self):
+        seconds = self.value // NSEC_IN_SEC
+        nsec = self.nsec
+        tzoffset = self.tzoffset
+
+        tz = self.tz
+        if tz != '':
+            tzindex = tt_timezones.timezoneToIndex[tz]
+        else:
+            tzindex = 0
+
+        buf = get_int_as_bytes(seconds, SECONDS_SIZE_BYTES)
+
+        if (nsec != 0) or (tzoffset != 0) or (tzindex != 0):
+            buf = buf + get_int_as_bytes(nsec, NSEC_SIZE_BYTES)
+            buf = buf + get_int_as_bytes(tzoffset, TZOFFSET_SIZE_BYTES)
+            buf = buf + get_int_as_bytes(tzindex, TZINDEX_SIZE_BYTES)
+
+        return buf
diff --git a/tarantool/msgpack_ext/types/timezones/__init__.py b/tarantool/msgpack_ext/types/timezones/__init__.py
new file mode 100644
index 00000000..b5f2faf1
--- /dev/null
+++ b/tarantool/msgpack_ext/types/timezones/__init__.py
@@ -0,0 +1,9 @@
+from tarantool.msgpack_ext.types.timezones.timezones import (
+	TZ_AMBIGUOUS,
+	indexToTimezone,
+	timezoneToIndex,
+	timezoneAbbrevInfo,
+)
+
+__all__ = ['TZ_AMBIGUOUS', 'indexToTimezone', 'timezoneToIndex',
+           'timezoneAbbrevInfo']
diff --git a/tarantool/msgpack_ext/types/timezones/gen-timezones.sh b/tarantool/msgpack_ext/types/timezones/gen-timezones.sh
new file mode 100755
index 00000000..66610c4c
--- /dev/null
+++ b/tarantool/msgpack_ext/types/timezones/gen-timezones.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+set -xeuo pipefail
+
+SRC_COMMIT="9ee45289e01232b8df1413efea11db170ae3b3b4"
+SRC_FILE=timezones.h
+DST_FILE=timezones.py
+
+[ -e ${SRC_FILE} ] && rm ${SRC_FILE}
+wget -O ${SRC_FILE} \
+	https://raw.githubusercontent.com/tarantool/tarantool/${SRC_COMMIT}/src/lib/tzcode/timezones.h
+
+# We don't need aliases in indexToTimezone because Tarantool always replace it:
+#
+# tarantool> T = date.parse '2022-01-01T00:00 Pacific/Enderbury'
+# ---
+# ...
+# tarantool> T
+# ---
+# - 2022-01-01T00:00:00 Pacific/Kanton
+# ...
+#
+# So we can do the same and don't worry, be happy.
+
+cat <<EOF > ${DST_FILE}
+# Automatically generated by gen-timezones.sh
+
+TZ_UTC       = 0x01
+TZ_RFC       = 0x02
+TZ_MILITARY  = 0x04
+TZ_AMBIGUOUS = 0x08
+TZ_NYI       = 0x10
+TZ_OLSON     = 0x20
+TZ_ALIAS     = 0x40
+TZ_DST       = 0x80
+
+indexToTimezone = {
+EOF
+
+grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \
+	| awk '{printf("\t%s : %s,\n", $1, $3)}' >> ${DST_FILE}
+grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \
+	| awk '{printf("\t%s : %s,\n", $1, $2)}' >> ${DST_FILE}
+
+cat <<EOF >> ${DST_FILE}
+}
+
+timezoneToIndex = {
+EOF
+
+grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \
+	| awk '{printf("\t%s : %s,\n", $3, $1)}' >> ${DST_FILE}
+grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \
+	| awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE}
+grep ZONE_ALIAS  ${SRC_FILE} | sed "s/ZONE_ALIAS( *//g"  | sed "s/[),]//g" \
+	| awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE}
+
+cat <<EOF >> ${DST_FILE}
+}
+
+timezoneAbbrevInfo = {
+EOF
+
+grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \
+	| awk '{printf("\t%s : {\"offset\" : %d, \"category\" : %s},\n", $3, $2, $4)}' >> ${DST_FILE}
+echo "}" >> ${DST_FILE}
+
+rm timezones.h
+
+python validate_timezones.py
diff --git a/tarantool/msgpack_ext/types/timezones/timezones.py b/tarantool/msgpack_ext/types/timezones/timezones.py
new file mode 100644
index 00000000..bbb5df5c
--- /dev/null
+++ b/tarantool/msgpack_ext/types/timezones/timezones.py
@@ -0,0 +1,1784 @@
+# Automatically generated by gen-timezones.sh
+
+TZ_UTC       = 0x01
+TZ_RFC       = 0x02
+TZ_MILITARY  = 0x04
+TZ_AMBIGUOUS = 0x08
+TZ_NYI       = 0x10
+TZ_OLSON     = 0x20
+TZ_ALIAS     = 0x40
+TZ_DST       = 0x80
+
+indexToTimezone = {
+	1 : "A",
+	2 : "B",
+	3 : "C",
+	4 : "D",
+	5 : "E",
+	6 : "F",
+	7 : "G",
+	8 : "H",
+	9 : "I",
+	10 : "K",
+	11 : "L",
+	12 : "M",
+	13 : "N",
+	14 : "O",
+	15 : "P",
+	16 : "Q",
+	17 : "R",
+	18 : "S",
+	19 : "T",
+	20 : "U",
+	21 : "V",
+	22 : "W",
+	23 : "X",
+	24 : "Y",
+	25 : "Z",
+	32 : "AT",
+	40 : "BT",
+	48 : "CT",
+	56 : "ET",
+	64 : "GT",
+	72 : "IT",
+	80 : "KT",
+	88 : "MT",
+	96 : "PT",
+	104 : "ST",
+	112 : "UT",
+	120 : "WT",
+	128 : "ACT",
+	129 : "ADT",
+	130 : "AET",
+	131 : "AFT",
+	132 : "AMT",
+	133 : "AoE",
+	134 : "ART",
+	135 : "AST",
+	136 : "AZT",
+	144 : "BDT",
+	145 : "BNT",
+	146 : "BOT",
+	147 : "BRT",
+	148 : "BST",
+	149 : "BTT",
+	152 : "CAT",
+	153 : "CCT",
+	154 : "CDT",
+	155 : "CET",
+	156 : "CIT",
+	157 : "CKT",
+	158 : "CLT",
+	159 : "COT",
+	160 : "CST",
+	161 : "CVT",
+	162 : "CXT",
+	168 : "EAT",
+	169 : "ECT",
+	170 : "EDT",
+	171 : "EET",
+	172 : "EGT",
+	173 : "EST",
+	176 : "FET",
+	177 : "FJT",
+	178 : "FKT",
+	179 : "FNT",
+	184 : "GET",
+	185 : "GFT",
+	186 : "GMT",
+	187 : "GST",
+	188 : "GYT",
+	192 : "HAA",
+	193 : "HAC",
+	194 : "HAE",
+	195 : "HAP",
+	196 : "HAR",
+	197 : "HAT",
+	198 : "HDT",
+	199 : "HKT",
+	200 : "HLV",
+	201 : "HNA",
+	202 : "HNC",
+	203 : "HNE",
+	204 : "HNP",
+	205 : "HNR",
+	206 : "HNT",
+	207 : "HST",
+	208 : "ICT",
+	209 : "IDT",
+	210 : "IOT",
+	211 : "IST",
+	216 : "JST",
+	224 : "KGT",
+	225 : "KIT",
+	226 : "KST",
+	232 : "MCK",
+	233 : "MDT",
+	234 : "MEZ",
+	235 : "MHT",
+	236 : "MMT",
+	237 : "MSD",
+	238 : "MSK",
+	239 : "MST",
+	240 : "MUT",
+	241 : "MVT",
+	242 : "MYT",
+	248 : "NCT",
+	249 : "NDT",
+	250 : "NFT",
+	251 : "NPT",
+	252 : "NRT",
+	253 : "NST",
+	254 : "NUT",
+	256 : "OEZ",
+	264 : "PDT",
+	265 : "PET",
+	266 : "PGT",
+	267 : "PHT",
+	268 : "PKT",
+	269 : "PST",
+	270 : "PWT",
+	271 : "PYT",
+	272 : "RET",
+	280 : "SBT",
+	281 : "SCT",
+	282 : "SGT",
+	283 : "SRT",
+	284 : "SST",
+	288 : "TFT",
+	289 : "TJT",
+	290 : "TKT",
+	291 : "TLT",
+	292 : "TMT",
+	293 : "TOT",
+	294 : "TRT",
+	295 : "TVT",
+	296 : "UTC",
+	297 : "UYT",
+	298 : "UZT",
+	304 : "VET",
+	305 : "VUT",
+	312 : "WAT",
+	313 : "WDT",
+	314 : "WET",
+	315 : "WEZ",
+	316 : "WFT",
+	317 : "WGT",
+	318 : "WIB",
+	319 : "WIT",
+	320 : "WST",
+	328 : "ACDT",
+	329 : "ACST",
+	330 : "ADST",
+	331 : "AEDT",
+	332 : "AEST",
+	333 : "AKDT",
+	334 : "AKST",
+	335 : "ALMT",
+	336 : "AMDT",
+	337 : "AMST",
+	338 : "ANAT",
+	339 : "AQTT",
+	340 : "AWDT",
+	341 : "AWST",
+	342 : "AZOT",
+	343 : "AZST",
+	344 : "BDST",
+	345 : "BRST",
+	352 : "CAST",
+	353 : "CDST",
+	354 : "CEDT",
+	355 : "CEST",
+	356 : "CHOT",
+	357 : "ChST",
+	358 : "CHUT",
+	359 : "CIST",
+	360 : "CLDT",
+	361 : "CLST",
+	368 : "DAVT",
+	369 : "DDUT",
+	376 : "EADT",
+	377 : "EAST",
+	378 : "ECST",
+	379 : "EDST",
+	380 : "EEDT",
+	381 : "EEST",
+	382 : "EGST",
+	384 : "FJDT",
+	385 : "FJST",
+	386 : "FKDT",
+	387 : "FKST",
+	392 : "GALT",
+	393 : "GAMT",
+	394 : "GILT",
+	400 : "HADT",
+	401 : "HAST",
+	402 : "HOVT",
+	408 : "IRDT",
+	409 : "IRKT",
+	410 : "IRST",
+	416 : "KOST",
+	417 : "KRAT",
+	418 : "KUYT",
+	424 : "LHDT",
+	425 : "LHST",
+	426 : "LINT",
+	432 : "MAGT",
+	433 : "MART",
+	434 : "MAWT",
+	435 : "MDST",
+	436 : "MESZ",
+	440 : "NFDT",
+	441 : "NOVT",
+	442 : "NZDT",
+	443 : "NZST",
+	448 : "OESZ",
+	449 : "OMST",
+	450 : "ORAT",
+	456 : "PDST",
+	457 : "PETT",
+	458 : "PHOT",
+	459 : "PMDT",
+	460 : "PMST",
+	461 : "PONT",
+	462 : "PYST",
+	464 : "QYZT",
+	472 : "ROTT",
+	480 : "SAKT",
+	481 : "SAMT",
+	482 : "SAST",
+	483 : "SRET",
+	484 : "SYOT",
+	488 : "TAHT",
+	489 : "TOST",
+	496 : "ULAT",
+	497 : "UYST",
+	504 : "VLAT",
+	505 : "VOST",
+	512 : "WAKT",
+	513 : "WAST",
+	514 : "WEDT",
+	515 : "WEST",
+	516 : "WESZ",
+	517 : "WGST",
+	518 : "WITA",
+	520 : "YAKT",
+	521 : "YAPT",
+	522 : "YEKT",
+	528 : "ACWST",
+	529 : "ANAST",
+	530 : "AZODT",
+	531 : "AZOST",
+	536 : "CHADT",
+	537 : "CHAST",
+	538 : "CHODT",
+	539 : "CHOST",
+	540 : "CIDST",
+	544 : "EASST",
+	545 : "EFATE",
+	552 : "HOVDT",
+	553 : "HOVST",
+	560 : "IRKST",
+	568 : "KRAST",
+	576 : "MAGST",
+	584 : "NACDT",
+	585 : "NACST",
+	586 : "NAEDT",
+	587 : "NAEST",
+	588 : "NAMDT",
+	589 : "NAMST",
+	590 : "NAPDT",
+	591 : "NAPST",
+	592 : "NOVST",
+	600 : "OMSST",
+	608 : "PETST",
+	616 : "SAMST",
+	624 : "ULAST",
+	632 : "VLAST",
+	640 : "WARST",
+	648 : "YAKST",
+	649 : "YEKST",
+	656 : "CHODST",
+	664 : "HOVDST",
+	672 : "Africa/Abidjan",
+	673 : "Africa/Algiers",
+	674 : "Africa/Bissau",
+	675 : "Africa/Cairo",
+	676 : "Africa/Casablanca",
+	677 : "Africa/Ceuta",
+	678 : "Africa/El_Aaiun",
+	679 : "Africa/Johannesburg",
+	680 : "Africa/Juba",
+	681 : "Africa/Khartoum",
+	682 : "Africa/Lagos",
+	683 : "Africa/Maputo",
+	684 : "Africa/Monrovia",
+	685 : "Africa/Nairobi",
+	686 : "Africa/Ndjamena",
+	687 : "Africa/Sao_Tome",
+	688 : "Africa/Tripoli",
+	689 : "Africa/Tunis",
+	690 : "Africa/Windhoek",
+	691 : "America/Adak",
+	692 : "America/Anchorage",
+	693 : "America/Araguaina",
+	694 : "America/Argentina/Buenos_Aires",
+	695 : "America/Argentina/Catamarca",
+	696 : "America/Argentina/Cordoba",
+	697 : "America/Argentina/Jujuy",
+	698 : "America/Argentina/La_Rioja",
+	699 : "America/Argentina/Mendoza",
+	700 : "America/Argentina/Rio_Gallegos",
+	701 : "America/Argentina/Salta",
+	702 : "America/Argentina/San_Juan",
+	703 : "America/Argentina/San_Luis",
+	704 : "America/Argentina/Tucuman",
+	705 : "America/Argentina/Ushuaia",
+	706 : "America/Asuncion",
+	707 : "America/Bahia",
+	708 : "America/Bahia_Banderas",
+	709 : "America/Barbados",
+	710 : "America/Belem",
+	711 : "America/Belize",
+	712 : "America/Boa_Vista",
+	713 : "America/Bogota",
+	714 : "America/Boise",
+	715 : "America/Cambridge_Bay",
+	716 : "America/Campo_Grande",
+	717 : "America/Cancun",
+	718 : "America/Caracas",
+	719 : "America/Cayenne",
+	720 : "America/Chicago",
+	721 : "America/Chihuahua",
+	722 : "America/Costa_Rica",
+	723 : "America/Cuiaba",
+	724 : "America/Danmarkshavn",
+	725 : "America/Dawson",
+	726 : "America/Dawson_Creek",
+	727 : "America/Denver",
+	728 : "America/Detroit",
+	729 : "America/Edmonton",
+	730 : "America/Eirunepe",
+	731 : "America/El_Salvador",
+	732 : "America/Fort_Nelson",
+	733 : "America/Fortaleza",
+	734 : "America/Glace_Bay",
+	735 : "America/Goose_Bay",
+	736 : "America/Grand_Turk",
+	737 : "America/Guatemala",
+	738 : "America/Guayaquil",
+	739 : "America/Guyana",
+	740 : "America/Halifax",
+	741 : "America/Havana",
+	742 : "America/Hermosillo",
+	743 : "America/Indiana/Indianapolis",
+	744 : "America/Indiana/Knox",
+	745 : "America/Indiana/Marengo",
+	746 : "America/Indiana/Petersburg",
+	747 : "America/Indiana/Tell_City",
+	748 : "America/Indiana/Vevay",
+	749 : "America/Indiana/Vincennes",
+	750 : "America/Indiana/Winamac",
+	751 : "America/Inuvik",
+	752 : "America/Iqaluit",
+	753 : "America/Jamaica",
+	754 : "America/Juneau",
+	755 : "America/Kentucky/Louisville",
+	756 : "America/Kentucky/Monticello",
+	757 : "America/La_Paz",
+	758 : "America/Lima",
+	759 : "America/Los_Angeles",
+	760 : "America/Maceio",
+	761 : "America/Managua",
+	762 : "America/Manaus",
+	763 : "America/Martinique",
+	764 : "America/Matamoros",
+	765 : "America/Mazatlan",
+	766 : "America/Menominee",
+	767 : "America/Merida",
+	768 : "America/Metlakatla",
+	769 : "America/Mexico_City",
+	770 : "America/Miquelon",
+	771 : "America/Moncton",
+	772 : "America/Monterrey",
+	773 : "America/Montevideo",
+	774 : "America/New_York",
+	775 : "America/Nipigon",
+	776 : "America/Nome",
+	777 : "America/Noronha",
+	778 : "America/North_Dakota/Beulah",
+	779 : "America/North_Dakota/Center",
+	780 : "America/North_Dakota/New_Salem",
+	781 : "America/Nuuk",
+	782 : "America/Ojinaga",
+	783 : "America/Panama",
+	784 : "America/Pangnirtung",
+	785 : "America/Paramaribo",
+	786 : "America/Phoenix",
+	787 : "America/Port-au-Prince",
+	788 : "America/Porto_Velho",
+	789 : "America/Puerto_Rico",
+	790 : "America/Punta_Arenas",
+	791 : "America/Rainy_River",
+	792 : "America/Rankin_Inlet",
+	793 : "America/Recife",
+	794 : "America/Regina",
+	795 : "America/Resolute",
+	796 : "America/Rio_Branco",
+	797 : "America/Santarem",
+	798 : "America/Santiago",
+	799 : "America/Santo_Domingo",
+	800 : "America/Sao_Paulo",
+	801 : "America/Scoresbysund",
+	802 : "America/Sitka",
+	803 : "America/St_Johns",
+	804 : "America/Swift_Current",
+	805 : "America/Tegucigalpa",
+	806 : "America/Thule",
+	807 : "America/Thunder_Bay",
+	808 : "America/Tijuana",
+	809 : "America/Toronto",
+	810 : "America/Vancouver",
+	811 : "America/Whitehorse",
+	812 : "America/Winnipeg",
+	813 : "America/Yakutat",
+	814 : "America/Yellowknife",
+	815 : "Antarctica/Casey",
+	816 : "Antarctica/Davis",
+	817 : "Antarctica/Macquarie",
+	818 : "Antarctica/Mawson",
+	819 : "Antarctica/Palmer",
+	820 : "Antarctica/Rothera",
+	821 : "Antarctica/Troll",
+	822 : "Antarctica/Vostok",
+	823 : "Asia/Almaty",
+	824 : "Asia/Amman",
+	825 : "Asia/Anadyr",
+	826 : "Asia/Aqtau",
+	827 : "Asia/Aqtobe",
+	828 : "Asia/Ashgabat",
+	829 : "Asia/Atyrau",
+	830 : "Asia/Baghdad",
+	831 : "Asia/Baku",
+	832 : "Asia/Bangkok",
+	833 : "Asia/Barnaul",
+	834 : "Asia/Beirut",
+	835 : "Asia/Bishkek",
+	836 : "Asia/Brunei",
+	837 : "Asia/Chita",
+	838 : "Asia/Choibalsan",
+	839 : "Asia/Colombo",
+	840 : "Asia/Damascus",
+	841 : "Asia/Dhaka",
+	842 : "Asia/Dili",
+	843 : "Asia/Dubai",
+	844 : "Asia/Dushanbe",
+	845 : "Asia/Famagusta",
+	846 : "Asia/Gaza",
+	847 : "Asia/Hebron",
+	848 : "Asia/Ho_Chi_Minh",
+	849 : "Asia/Hong_Kong",
+	850 : "Asia/Hovd",
+	851 : "Asia/Irkutsk",
+	852 : "Asia/Jakarta",
+	853 : "Asia/Jayapura",
+	854 : "Asia/Jerusalem",
+	855 : "Asia/Kabul",
+	856 : "Asia/Kamchatka",
+	857 : "Asia/Karachi",
+	858 : "Asia/Kathmandu",
+	859 : "Asia/Khandyga",
+	860 : "Asia/Kolkata",
+	861 : "Asia/Krasnoyarsk",
+	862 : "Asia/Kuala_Lumpur",
+	863 : "Asia/Kuching",
+	864 : "Asia/Macau",
+	865 : "Asia/Magadan",
+	866 : "Asia/Makassar",
+	867 : "Asia/Manila",
+	868 : "Asia/Nicosia",
+	869 : "Asia/Novokuznetsk",
+	870 : "Asia/Novosibirsk",
+	871 : "Asia/Omsk",
+	872 : "Asia/Oral",
+	873 : "Asia/Pontianak",
+	874 : "Asia/Pyongyang",
+	875 : "Asia/Qatar",
+	876 : "Asia/Qostanay",
+	877 : "Asia/Qyzylorda",
+	878 : "Asia/Riyadh",
+	879 : "Asia/Sakhalin",
+	880 : "Asia/Samarkand",
+	881 : "Asia/Seoul",
+	882 : "Asia/Shanghai",
+	883 : "Asia/Singapore",
+	884 : "Asia/Srednekolymsk",
+	885 : "Asia/Taipei",
+	886 : "Asia/Tashkent",
+	887 : "Asia/Tbilisi",
+	888 : "Asia/Tehran",
+	889 : "Asia/Thimphu",
+	890 : "Asia/Tokyo",
+	891 : "Asia/Tomsk",
+	892 : "Asia/Ulaanbaatar",
+	893 : "Asia/Urumqi",
+	894 : "Asia/Ust-Nera",
+	895 : "Asia/Vladivostok",
+	896 : "Asia/Yakutsk",
+	897 : "Asia/Yangon",
+	898 : "Asia/Yekaterinburg",
+	899 : "Asia/Yerevan",
+	900 : "Atlantic/Azores",
+	901 : "Atlantic/Bermuda",
+	902 : "Atlantic/Canary",
+	903 : "Atlantic/Cape_Verde",
+	904 : "Atlantic/Faroe",
+	905 : "Atlantic/Madeira",
+	906 : "Atlantic/Reykjavik",
+	907 : "Atlantic/South_Georgia",
+	908 : "Atlantic/Stanley",
+	909 : "Australia/Adelaide",
+	910 : "Australia/Brisbane",
+	911 : "Australia/Broken_Hill",
+	912 : "Australia/Darwin",
+	913 : "Australia/Eucla",
+	914 : "Australia/Hobart",
+	915 : "Australia/Lindeman",
+	916 : "Australia/Lord_Howe",
+	917 : "Australia/Melbourne",
+	918 : "Australia/Perth",
+	919 : "Australia/Sydney",
+	920 : "Etc/GMT",
+	921 : "Etc/UTC",
+	922 : "Europe/Amsterdam",
+	923 : "Europe/Andorra",
+	924 : "Europe/Astrakhan",
+	925 : "Europe/Athens",
+	926 : "Europe/Belgrade",
+	927 : "Europe/Berlin",
+	928 : "Europe/Brussels",
+	929 : "Europe/Bucharest",
+	930 : "Europe/Budapest",
+	931 : "Europe/Chisinau",
+	932 : "Europe/Copenhagen",
+	933 : "Europe/Dublin",
+	934 : "Europe/Gibraltar",
+	935 : "Europe/Helsinki",
+	936 : "Europe/Istanbul",
+	937 : "Europe/Kaliningrad",
+	938 : "Europe/Kiev",
+	939 : "Europe/Kirov",
+	940 : "Europe/Lisbon",
+	941 : "Europe/London",
+	942 : "Europe/Luxembourg",
+	943 : "Europe/Madrid",
+	944 : "Europe/Malta",
+	945 : "Europe/Minsk",
+	946 : "Europe/Monaco",
+	947 : "Europe/Moscow",
+	948 : "Europe/Oslo",
+	949 : "Europe/Paris",
+	950 : "Europe/Prague",
+	951 : "Europe/Riga",
+	952 : "Europe/Rome",
+	953 : "Europe/Samara",
+	954 : "Europe/Saratov",
+	955 : "Europe/Simferopol",
+	956 : "Europe/Sofia",
+	957 : "Europe/Stockholm",
+	958 : "Europe/Tallinn",
+	959 : "Europe/Tirane",
+	960 : "Europe/Ulyanovsk",
+	961 : "Europe/Uzhgorod",
+	962 : "Europe/Vienna",
+	963 : "Europe/Vilnius",
+	964 : "Europe/Volgograd",
+	965 : "Europe/Warsaw",
+	966 : "Europe/Zaporozhye",
+	967 : "Europe/Zurich",
+	968 : "Indian/Chagos",
+	969 : "Indian/Christmas",
+	970 : "Indian/Cocos",
+	971 : "Indian/Kerguelen",
+	972 : "Indian/Mahe",
+	973 : "Indian/Maldives",
+	974 : "Indian/Mauritius",
+	975 : "Indian/Reunion",
+	976 : "Pacific/Apia",
+	977 : "Pacific/Auckland",
+	978 : "Pacific/Bougainville",
+	979 : "Pacific/Chatham",
+	980 : "Pacific/Chuuk",
+	981 : "Pacific/Easter",
+	982 : "Pacific/Efate",
+	983 : "Pacific/Fakaofo",
+	984 : "Pacific/Fiji",
+	985 : "Pacific/Funafuti",
+	986 : "Pacific/Galapagos",
+	987 : "Pacific/Gambier",
+	988 : "Pacific/Guadalcanal",
+	989 : "Pacific/Guam",
+	990 : "Pacific/Honolulu",
+	991 : "Pacific/Kanton",
+	992 : "Pacific/Kiritimati",
+	993 : "Pacific/Kosrae",
+	994 : "Pacific/Kwajalein",
+	995 : "Pacific/Majuro",
+	996 : "Pacific/Marquesas",
+	997 : "Pacific/Nauru",
+	998 : "Pacific/Niue",
+	999 : "Pacific/Norfolk",
+	1000 : "Pacific/Noumea",
+	1001 : "Pacific/Pago_Pago",
+	1002 : "Pacific/Palau",
+	1003 : "Pacific/Pitcairn",
+	1004 : "Pacific/Pohnpei",
+	1005 : "Pacific/Port_Moresby",
+	1006 : "Pacific/Rarotonga",
+	1007 : "Pacific/Tahiti",
+	1008 : "Pacific/Tarawa",
+	1009 : "Pacific/Tongatapu",
+	1010 : "Pacific/Wake",
+	1011 : "Pacific/Wallis",
+}
+
+timezoneToIndex = {
+	"A" : 1,
+	"B" : 2,
+	"C" : 3,
+	"D" : 4,
+	"E" : 5,
+	"F" : 6,
+	"G" : 7,
+	"H" : 8,
+	"I" : 9,
+	"K" : 10,
+	"L" : 11,
+	"M" : 12,
+	"N" : 13,
+	"O" : 14,
+	"P" : 15,
+	"Q" : 16,
+	"R" : 17,
+	"S" : 18,
+	"T" : 19,
+	"U" : 20,
+	"V" : 21,
+	"W" : 22,
+	"X" : 23,
+	"Y" : 24,
+	"Z" : 25,
+	"AT" : 32,
+	"BT" : 40,
+	"CT" : 48,
+	"ET" : 56,
+	"GT" : 64,
+	"IT" : 72,
+	"KT" : 80,
+	"MT" : 88,
+	"PT" : 96,
+	"ST" : 104,
+	"UT" : 112,
+	"WT" : 120,
+	"ACT" : 128,
+	"ADT" : 129,
+	"AET" : 130,
+	"AFT" : 131,
+	"AMT" : 132,
+	"AoE" : 133,
+	"ART" : 134,
+	"AST" : 135,
+	"AZT" : 136,
+	"BDT" : 144,
+	"BNT" : 145,
+	"BOT" : 146,
+	"BRT" : 147,
+	"BST" : 148,
+	"BTT" : 149,
+	"CAT" : 152,
+	"CCT" : 153,
+	"CDT" : 154,
+	"CET" : 155,
+	"CIT" : 156,
+	"CKT" : 157,
+	"CLT" : 158,
+	"COT" : 159,
+	"CST" : 160,
+	"CVT" : 161,
+	"CXT" : 162,
+	"EAT" : 168,
+	"ECT" : 169,
+	"EDT" : 170,
+	"EET" : 171,
+	"EGT" : 172,
+	"EST" : 173,
+	"FET" : 176,
+	"FJT" : 177,
+	"FKT" : 178,
+	"FNT" : 179,
+	"GET" : 184,
+	"GFT" : 185,
+	"GMT" : 186,
+	"GST" : 187,
+	"GYT" : 188,
+	"HAA" : 192,
+	"HAC" : 193,
+	"HAE" : 194,
+	"HAP" : 195,
+	"HAR" : 196,
+	"HAT" : 197,
+	"HDT" : 198,
+	"HKT" : 199,
+	"HLV" : 200,
+	"HNA" : 201,
+	"HNC" : 202,
+	"HNE" : 203,
+	"HNP" : 204,
+	"HNR" : 205,
+	"HNT" : 206,
+	"HST" : 207,
+	"ICT" : 208,
+	"IDT" : 209,
+	"IOT" : 210,
+	"IST" : 211,
+	"JST" : 216,
+	"KGT" : 224,
+	"KIT" : 225,
+	"KST" : 226,
+	"MCK" : 232,
+	"MDT" : 233,
+	"MEZ" : 234,
+	"MHT" : 235,
+	"MMT" : 236,
+	"MSD" : 237,
+	"MSK" : 238,
+	"MST" : 239,
+	"MUT" : 240,
+	"MVT" : 241,
+	"MYT" : 242,
+	"NCT" : 248,
+	"NDT" : 249,
+	"NFT" : 250,
+	"NPT" : 251,
+	"NRT" : 252,
+	"NST" : 253,
+	"NUT" : 254,
+	"OEZ" : 256,
+	"PDT" : 264,
+	"PET" : 265,
+	"PGT" : 266,
+	"PHT" : 267,
+	"PKT" : 268,
+	"PST" : 269,
+	"PWT" : 270,
+	"PYT" : 271,
+	"RET" : 272,
+	"SBT" : 280,
+	"SCT" : 281,
+	"SGT" : 282,
+	"SRT" : 283,
+	"SST" : 284,
+	"TFT" : 288,
+	"TJT" : 289,
+	"TKT" : 290,
+	"TLT" : 291,
+	"TMT" : 292,
+	"TOT" : 293,
+	"TRT" : 294,
+	"TVT" : 295,
+	"UTC" : 296,
+	"UYT" : 297,
+	"UZT" : 298,
+	"VET" : 304,
+	"VUT" : 305,
+	"WAT" : 312,
+	"WDT" : 313,
+	"WET" : 314,
+	"WEZ" : 315,
+	"WFT" : 316,
+	"WGT" : 317,
+	"WIB" : 318,
+	"WIT" : 319,
+	"WST" : 320,
+	"ACDT" : 328,
+	"ACST" : 329,
+	"ADST" : 330,
+	"AEDT" : 331,
+	"AEST" : 332,
+	"AKDT" : 333,
+	"AKST" : 334,
+	"ALMT" : 335,
+	"AMDT" : 336,
+	"AMST" : 337,
+	"ANAT" : 338,
+	"AQTT" : 339,
+	"AWDT" : 340,
+	"AWST" : 341,
+	"AZOT" : 342,
+	"AZST" : 343,
+	"BDST" : 344,
+	"BRST" : 345,
+	"CAST" : 352,
+	"CDST" : 353,
+	"CEDT" : 354,
+	"CEST" : 355,
+	"CHOT" : 356,
+	"ChST" : 357,
+	"CHUT" : 358,
+	"CIST" : 359,
+	"CLDT" : 360,
+	"CLST" : 361,
+	"DAVT" : 368,
+	"DDUT" : 369,
+	"EADT" : 376,
+	"EAST" : 377,
+	"ECST" : 378,
+	"EDST" : 379,
+	"EEDT" : 380,
+	"EEST" : 381,
+	"EGST" : 382,
+	"FJDT" : 384,
+	"FJST" : 385,
+	"FKDT" : 386,
+	"FKST" : 387,
+	"GALT" : 392,
+	"GAMT" : 393,
+	"GILT" : 394,
+	"HADT" : 400,
+	"HAST" : 401,
+	"HOVT" : 402,
+	"IRDT" : 408,
+	"IRKT" : 409,
+	"IRST" : 410,
+	"KOST" : 416,
+	"KRAT" : 417,
+	"KUYT" : 418,
+	"LHDT" : 424,
+	"LHST" : 425,
+	"LINT" : 426,
+	"MAGT" : 432,
+	"MART" : 433,
+	"MAWT" : 434,
+	"MDST" : 435,
+	"MESZ" : 436,
+	"NFDT" : 440,
+	"NOVT" : 441,
+	"NZDT" : 442,
+	"NZST" : 443,
+	"OESZ" : 448,
+	"OMST" : 449,
+	"ORAT" : 450,
+	"PDST" : 456,
+	"PETT" : 457,
+	"PHOT" : 458,
+	"PMDT" : 459,
+	"PMST" : 460,
+	"PONT" : 461,
+	"PYST" : 462,
+	"QYZT" : 464,
+	"ROTT" : 472,
+	"SAKT" : 480,
+	"SAMT" : 481,
+	"SAST" : 482,
+	"SRET" : 483,
+	"SYOT" : 484,
+	"TAHT" : 488,
+	"TOST" : 489,
+	"ULAT" : 496,
+	"UYST" : 497,
+	"VLAT" : 504,
+	"VOST" : 505,
+	"WAKT" : 512,
+	"WAST" : 513,
+	"WEDT" : 514,
+	"WEST" : 515,
+	"WESZ" : 516,
+	"WGST" : 517,
+	"WITA" : 518,
+	"YAKT" : 520,
+	"YAPT" : 521,
+	"YEKT" : 522,
+	"ACWST" : 528,
+	"ANAST" : 529,
+	"AZODT" : 530,
+	"AZOST" : 531,
+	"CHADT" : 536,
+	"CHAST" : 537,
+	"CHODT" : 538,
+	"CHOST" : 539,
+	"CIDST" : 540,
+	"EASST" : 544,
+	"EFATE" : 545,
+	"HOVDT" : 552,
+	"HOVST" : 553,
+	"IRKST" : 560,
+	"KRAST" : 568,
+	"MAGST" : 576,
+	"NACDT" : 584,
+	"NACST" : 585,
+	"NAEDT" : 586,
+	"NAEST" : 587,
+	"NAMDT" : 588,
+	"NAMST" : 589,
+	"NAPDT" : 590,
+	"NAPST" : 591,
+	"NOVST" : 592,
+	"OMSST" : 600,
+	"PETST" : 608,
+	"SAMST" : 616,
+	"ULAST" : 624,
+	"VLAST" : 632,
+	"WARST" : 640,
+	"YAKST" : 648,
+	"YEKST" : 649,
+	"CHODST" : 656,
+	"HOVDST" : 664,
+	"Africa/Abidjan" : 672,
+	"Africa/Algiers" : 673,
+	"Africa/Bissau" : 674,
+	"Africa/Cairo" : 675,
+	"Africa/Casablanca" : 676,
+	"Africa/Ceuta" : 677,
+	"Africa/El_Aaiun" : 678,
+	"Africa/Johannesburg" : 679,
+	"Africa/Juba" : 680,
+	"Africa/Khartoum" : 681,
+	"Africa/Lagos" : 682,
+	"Africa/Maputo" : 683,
+	"Africa/Monrovia" : 684,
+	"Africa/Nairobi" : 685,
+	"Africa/Ndjamena" : 686,
+	"Africa/Sao_Tome" : 687,
+	"Africa/Tripoli" : 688,
+	"Africa/Tunis" : 689,
+	"Africa/Windhoek" : 690,
+	"America/Adak" : 691,
+	"America/Anchorage" : 692,
+	"America/Araguaina" : 693,
+	"America/Argentina/Buenos_Aires" : 694,
+	"America/Argentina/Catamarca" : 695,
+	"America/Argentina/Cordoba" : 696,
+	"America/Argentina/Jujuy" : 697,
+	"America/Argentina/La_Rioja" : 698,
+	"America/Argentina/Mendoza" : 699,
+	"America/Argentina/Rio_Gallegos" : 700,
+	"America/Argentina/Salta" : 701,
+	"America/Argentina/San_Juan" : 702,
+	"America/Argentina/San_Luis" : 703,
+	"America/Argentina/Tucuman" : 704,
+	"America/Argentina/Ushuaia" : 705,
+	"America/Asuncion" : 706,
+	"America/Bahia" : 707,
+	"America/Bahia_Banderas" : 708,
+	"America/Barbados" : 709,
+	"America/Belem" : 710,
+	"America/Belize" : 711,
+	"America/Boa_Vista" : 712,
+	"America/Bogota" : 713,
+	"America/Boise" : 714,
+	"America/Cambridge_Bay" : 715,
+	"America/Campo_Grande" : 716,
+	"America/Cancun" : 717,
+	"America/Caracas" : 718,
+	"America/Cayenne" : 719,
+	"America/Chicago" : 720,
+	"America/Chihuahua" : 721,
+	"America/Costa_Rica" : 722,
+	"America/Cuiaba" : 723,
+	"America/Danmarkshavn" : 724,
+	"America/Dawson" : 725,
+	"America/Dawson_Creek" : 726,
+	"America/Denver" : 727,
+	"America/Detroit" : 728,
+	"America/Edmonton" : 729,
+	"America/Eirunepe" : 730,
+	"America/El_Salvador" : 731,
+	"America/Fort_Nelson" : 732,
+	"America/Fortaleza" : 733,
+	"America/Glace_Bay" : 734,
+	"America/Goose_Bay" : 735,
+	"America/Grand_Turk" : 736,
+	"America/Guatemala" : 737,
+	"America/Guayaquil" : 738,
+	"America/Guyana" : 739,
+	"America/Halifax" : 740,
+	"America/Havana" : 741,
+	"America/Hermosillo" : 742,
+	"America/Indiana/Indianapolis" : 743,
+	"America/Indiana/Knox" : 744,
+	"America/Indiana/Marengo" : 745,
+	"America/Indiana/Petersburg" : 746,
+	"America/Indiana/Tell_City" : 747,
+	"America/Indiana/Vevay" : 748,
+	"America/Indiana/Vincennes" : 749,
+	"America/Indiana/Winamac" : 750,
+	"America/Inuvik" : 751,
+	"America/Iqaluit" : 752,
+	"America/Jamaica" : 753,
+	"America/Juneau" : 754,
+	"America/Kentucky/Louisville" : 755,
+	"America/Kentucky/Monticello" : 756,
+	"America/La_Paz" : 757,
+	"America/Lima" : 758,
+	"America/Los_Angeles" : 759,
+	"America/Maceio" : 760,
+	"America/Managua" : 761,
+	"America/Manaus" : 762,
+	"America/Martinique" : 763,
+	"America/Matamoros" : 764,
+	"America/Mazatlan" : 765,
+	"America/Menominee" : 766,
+	"America/Merida" : 767,
+	"America/Metlakatla" : 768,
+	"America/Mexico_City" : 769,
+	"America/Miquelon" : 770,
+	"America/Moncton" : 771,
+	"America/Monterrey" : 772,
+	"America/Montevideo" : 773,
+	"America/New_York" : 774,
+	"America/Nipigon" : 775,
+	"America/Nome" : 776,
+	"America/Noronha" : 777,
+	"America/North_Dakota/Beulah" : 778,
+	"America/North_Dakota/Center" : 779,
+	"America/North_Dakota/New_Salem" : 780,
+	"America/Nuuk" : 781,
+	"America/Ojinaga" : 782,
+	"America/Panama" : 783,
+	"America/Pangnirtung" : 784,
+	"America/Paramaribo" : 785,
+	"America/Phoenix" : 786,
+	"America/Port-au-Prince" : 787,
+	"America/Porto_Velho" : 788,
+	"America/Puerto_Rico" : 789,
+	"America/Punta_Arenas" : 790,
+	"America/Rainy_River" : 791,
+	"America/Rankin_Inlet" : 792,
+	"America/Recife" : 793,
+	"America/Regina" : 794,
+	"America/Resolute" : 795,
+	"America/Rio_Branco" : 796,
+	"America/Santarem" : 797,
+	"America/Santiago" : 798,
+	"America/Santo_Domingo" : 799,
+	"America/Sao_Paulo" : 800,
+	"America/Scoresbysund" : 801,
+	"America/Sitka" : 802,
+	"America/St_Johns" : 803,
+	"America/Swift_Current" : 804,
+	"America/Tegucigalpa" : 805,
+	"America/Thule" : 806,
+	"America/Thunder_Bay" : 807,
+	"America/Tijuana" : 808,
+	"America/Toronto" : 809,
+	"America/Vancouver" : 810,
+	"America/Whitehorse" : 811,
+	"America/Winnipeg" : 812,
+	"America/Yakutat" : 813,
+	"America/Yellowknife" : 814,
+	"Antarctica/Casey" : 815,
+	"Antarctica/Davis" : 816,
+	"Antarctica/Macquarie" : 817,
+	"Antarctica/Mawson" : 818,
+	"Antarctica/Palmer" : 819,
+	"Antarctica/Rothera" : 820,
+	"Antarctica/Troll" : 821,
+	"Antarctica/Vostok" : 822,
+	"Asia/Almaty" : 823,
+	"Asia/Amman" : 824,
+	"Asia/Anadyr" : 825,
+	"Asia/Aqtau" : 826,
+	"Asia/Aqtobe" : 827,
+	"Asia/Ashgabat" : 828,
+	"Asia/Atyrau" : 829,
+	"Asia/Baghdad" : 830,
+	"Asia/Baku" : 831,
+	"Asia/Bangkok" : 832,
+	"Asia/Barnaul" : 833,
+	"Asia/Beirut" : 834,
+	"Asia/Bishkek" : 835,
+	"Asia/Brunei" : 836,
+	"Asia/Chita" : 837,
+	"Asia/Choibalsan" : 838,
+	"Asia/Colombo" : 839,
+	"Asia/Damascus" : 840,
+	"Asia/Dhaka" : 841,
+	"Asia/Dili" : 842,
+	"Asia/Dubai" : 843,
+	"Asia/Dushanbe" : 844,
+	"Asia/Famagusta" : 845,
+	"Asia/Gaza" : 846,
+	"Asia/Hebron" : 847,
+	"Asia/Ho_Chi_Minh" : 848,
+	"Asia/Hong_Kong" : 849,
+	"Asia/Hovd" : 850,
+	"Asia/Irkutsk" : 851,
+	"Asia/Jakarta" : 852,
+	"Asia/Jayapura" : 853,
+	"Asia/Jerusalem" : 854,
+	"Asia/Kabul" : 855,
+	"Asia/Kamchatka" : 856,
+	"Asia/Karachi" : 857,
+	"Asia/Kathmandu" : 858,
+	"Asia/Khandyga" : 859,
+	"Asia/Kolkata" : 860,
+	"Asia/Krasnoyarsk" : 861,
+	"Asia/Kuala_Lumpur" : 862,
+	"Asia/Kuching" : 863,
+	"Asia/Macau" : 864,
+	"Asia/Magadan" : 865,
+	"Asia/Makassar" : 866,
+	"Asia/Manila" : 867,
+	"Asia/Nicosia" : 868,
+	"Asia/Novokuznetsk" : 869,
+	"Asia/Novosibirsk" : 870,
+	"Asia/Omsk" : 871,
+	"Asia/Oral" : 872,
+	"Asia/Pontianak" : 873,
+	"Asia/Pyongyang" : 874,
+	"Asia/Qatar" : 875,
+	"Asia/Qostanay" : 876,
+	"Asia/Qyzylorda" : 877,
+	"Asia/Riyadh" : 878,
+	"Asia/Sakhalin" : 879,
+	"Asia/Samarkand" : 880,
+	"Asia/Seoul" : 881,
+	"Asia/Shanghai" : 882,
+	"Asia/Singapore" : 883,
+	"Asia/Srednekolymsk" : 884,
+	"Asia/Taipei" : 885,
+	"Asia/Tashkent" : 886,
+	"Asia/Tbilisi" : 887,
+	"Asia/Tehran" : 888,
+	"Asia/Thimphu" : 889,
+	"Asia/Tokyo" : 890,
+	"Asia/Tomsk" : 891,
+	"Asia/Ulaanbaatar" : 892,
+	"Asia/Urumqi" : 893,
+	"Asia/Ust-Nera" : 894,
+	"Asia/Vladivostok" : 895,
+	"Asia/Yakutsk" : 896,
+	"Asia/Yangon" : 897,
+	"Asia/Yekaterinburg" : 898,
+	"Asia/Yerevan" : 899,
+	"Atlantic/Azores" : 900,
+	"Atlantic/Bermuda" : 901,
+	"Atlantic/Canary" : 902,
+	"Atlantic/Cape_Verde" : 903,
+	"Atlantic/Faroe" : 904,
+	"Atlantic/Madeira" : 905,
+	"Atlantic/Reykjavik" : 906,
+	"Atlantic/South_Georgia" : 907,
+	"Atlantic/Stanley" : 908,
+	"Australia/Adelaide" : 909,
+	"Australia/Brisbane" : 910,
+	"Australia/Broken_Hill" : 911,
+	"Australia/Darwin" : 912,
+	"Australia/Eucla" : 913,
+	"Australia/Hobart" : 914,
+	"Australia/Lindeman" : 915,
+	"Australia/Lord_Howe" : 916,
+	"Australia/Melbourne" : 917,
+	"Australia/Perth" : 918,
+	"Australia/Sydney" : 919,
+	"Etc/GMT" : 920,
+	"Etc/UTC" : 921,
+	"Europe/Amsterdam" : 922,
+	"Europe/Andorra" : 923,
+	"Europe/Astrakhan" : 924,
+	"Europe/Athens" : 925,
+	"Europe/Belgrade" : 926,
+	"Europe/Berlin" : 927,
+	"Europe/Brussels" : 928,
+	"Europe/Bucharest" : 929,
+	"Europe/Budapest" : 930,
+	"Europe/Chisinau" : 931,
+	"Europe/Copenhagen" : 932,
+	"Europe/Dublin" : 933,
+	"Europe/Gibraltar" : 934,
+	"Europe/Helsinki" : 935,
+	"Europe/Istanbul" : 936,
+	"Europe/Kaliningrad" : 937,
+	"Europe/Kiev" : 938,
+	"Europe/Kirov" : 939,
+	"Europe/Lisbon" : 940,
+	"Europe/London" : 941,
+	"Europe/Luxembourg" : 942,
+	"Europe/Madrid" : 943,
+	"Europe/Malta" : 944,
+	"Europe/Minsk" : 945,
+	"Europe/Monaco" : 946,
+	"Europe/Moscow" : 947,
+	"Europe/Oslo" : 948,
+	"Europe/Paris" : 949,
+	"Europe/Prague" : 950,
+	"Europe/Riga" : 951,
+	"Europe/Rome" : 952,
+	"Europe/Samara" : 953,
+	"Europe/Saratov" : 954,
+	"Europe/Simferopol" : 955,
+	"Europe/Sofia" : 956,
+	"Europe/Stockholm" : 957,
+	"Europe/Tallinn" : 958,
+	"Europe/Tirane" : 959,
+	"Europe/Ulyanovsk" : 960,
+	"Europe/Uzhgorod" : 961,
+	"Europe/Vienna" : 962,
+	"Europe/Vilnius" : 963,
+	"Europe/Volgograd" : 964,
+	"Europe/Warsaw" : 965,
+	"Europe/Zaporozhye" : 966,
+	"Europe/Zurich" : 967,
+	"Indian/Chagos" : 968,
+	"Indian/Christmas" : 969,
+	"Indian/Cocos" : 970,
+	"Indian/Kerguelen" : 971,
+	"Indian/Mahe" : 972,
+	"Indian/Maldives" : 973,
+	"Indian/Mauritius" : 974,
+	"Indian/Reunion" : 975,
+	"Pacific/Apia" : 976,
+	"Pacific/Auckland" : 977,
+	"Pacific/Bougainville" : 978,
+	"Pacific/Chatham" : 979,
+	"Pacific/Chuuk" : 980,
+	"Pacific/Easter" : 981,
+	"Pacific/Efate" : 982,
+	"Pacific/Fakaofo" : 983,
+	"Pacific/Fiji" : 984,
+	"Pacific/Funafuti" : 985,
+	"Pacific/Galapagos" : 986,
+	"Pacific/Gambier" : 987,
+	"Pacific/Guadalcanal" : 988,
+	"Pacific/Guam" : 989,
+	"Pacific/Honolulu" : 990,
+	"Pacific/Kanton" : 991,
+	"Pacific/Kiritimati" : 992,
+	"Pacific/Kosrae" : 993,
+	"Pacific/Kwajalein" : 994,
+	"Pacific/Majuro" : 995,
+	"Pacific/Marquesas" : 996,
+	"Pacific/Nauru" : 997,
+	"Pacific/Niue" : 998,
+	"Pacific/Norfolk" : 999,
+	"Pacific/Noumea" : 1000,
+	"Pacific/Pago_Pago" : 1001,
+	"Pacific/Palau" : 1002,
+	"Pacific/Pitcairn" : 1003,
+	"Pacific/Pohnpei" : 1004,
+	"Pacific/Port_Moresby" : 1005,
+	"Pacific/Rarotonga" : 1006,
+	"Pacific/Tahiti" : 1007,
+	"Pacific/Tarawa" : 1008,
+	"Pacific/Tongatapu" : 1009,
+	"Pacific/Wake" : 1010,
+	"Pacific/Wallis" : 1011,
+	"Africa/Accra" : 672,
+	"Africa/Addis_Ababa" : 685,
+	"Africa/Asmara" : 685,
+	"Africa/Asmera" : 685,
+	"Africa/Bamako" : 672,
+	"Africa/Bangui" : 682,
+	"Africa/Banjul" : 672,
+	"Africa/Blantyre" : 683,
+	"Africa/Brazzaville" : 682,
+	"Africa/Bujumbura" : 683,
+	"Africa/Conakry" : 672,
+	"Africa/Dakar" : 672,
+	"Africa/Dar_es_Salaam" : 685,
+	"Africa/Djibouti" : 685,
+	"Africa/Douala" : 682,
+	"Africa/Freetown" : 672,
+	"Africa/Gaborone" : 683,
+	"Africa/Harare" : 683,
+	"Africa/Kampala" : 685,
+	"Africa/Kigali" : 683,
+	"Africa/Kinshasa" : 682,
+	"Africa/Libreville" : 682,
+	"Africa/Lome" : 672,
+	"Africa/Luanda" : 682,
+	"Africa/Lubumbashi" : 683,
+	"Africa/Lusaka" : 683,
+	"Africa/Malabo" : 682,
+	"Africa/Maseru" : 679,
+	"Africa/Mbabane" : 679,
+	"Africa/Mogadishu" : 685,
+	"Africa/Niamey" : 682,
+	"Africa/Nouakchott" : 672,
+	"Africa/Ouagadougou" : 672,
+	"Africa/Porto-Novo" : 682,
+	"Africa/Timbuktu" : 672,
+	"America/Anguilla" : 789,
+	"America/Antigua" : 789,
+	"America/Argentina/ComodRivadavia" : 695,
+	"America/Aruba" : 789,
+	"America/Atikokan" : 783,
+	"America/Atka" : 691,
+	"America/Blanc-Sablon" : 789,
+	"America/Buenos_Aires" : 694,
+	"America/Catamarca" : 695,
+	"America/Cayman" : 783,
+	"America/Coral_Harbour" : 783,
+	"America/Cordoba" : 696,
+	"America/Creston" : 786,
+	"America/Curacao" : 789,
+	"America/Dominica" : 789,
+	"America/Ensenada" : 808,
+	"America/Fort_Wayne" : 743,
+	"America/Godthab" : 781,
+	"America/Grenada" : 789,
+	"America/Guadeloupe" : 789,
+	"America/Indianapolis" : 743,
+	"America/Jujuy" : 697,
+	"America/Knox_IN" : 744,
+	"America/Kralendijk" : 789,
+	"America/Louisville" : 755,
+	"America/Lower_Princes" : 789,
+	"America/Marigot" : 789,
+	"America/Mendoza" : 699,
+	"America/Montreal" : 809,
+	"America/Montserrat" : 789,
+	"America/Nassau" : 809,
+	"America/Port_of_Spain" : 789,
+	"America/Porto_Acre" : 796,
+	"America/Rosario" : 696,
+	"America/Santa_Isabel" : 808,
+	"America/Shiprock" : 727,
+	"America/St_Barthelemy" : 789,
+	"America/St_Kitts" : 789,
+	"America/St_Lucia" : 789,
+	"America/St_Thomas" : 789,
+	"America/St_Vincent" : 789,
+	"America/Tortola" : 789,
+	"America/Virgin" : 789,
+	"Antarctica/DumontDUrville" : 1005,
+	"Antarctica/McMurdo" : 977,
+	"Antarctica/South_Pole" : 977,
+	"Antarctica/Syowa" : 878,
+	"Arctic/Longyearbyen" : 948,
+	"Asia/Aden" : 878,
+	"Asia/Ashkhabad" : 828,
+	"Asia/Bahrain" : 875,
+	"Asia/Calcutta" : 860,
+	"Asia/Chongqing" : 882,
+	"Asia/Chungking" : 882,
+	"Asia/Dacca" : 841,
+	"Asia/Harbin" : 882,
+	"Asia/Istanbul" : 936,
+	"Asia/Kashgar" : 893,
+	"Asia/Katmandu" : 858,
+	"Asia/Kuwait" : 878,
+	"Asia/Macao" : 864,
+	"Asia/Muscat" : 843,
+	"Asia/Phnom_Penh" : 832,
+	"Asia/Rangoon" : 897,
+	"Asia/Saigon" : 848,
+	"Asia/Tel_Aviv" : 854,
+	"Asia/Thimbu" : 889,
+	"Asia/Ujung_Pandang" : 866,
+	"Asia/Ulan_Bator" : 892,
+	"Asia/Vientiane" : 832,
+	"Atlantic/Faeroe" : 904,
+	"Atlantic/Jan_Mayen" : 948,
+	"Atlantic/St_Helena" : 672,
+	"Australia/ACT" : 919,
+	"Australia/Canberra" : 919,
+	"Australia/Currie" : 914,
+	"Australia/LHI" : 916,
+	"Australia/NSW" : 919,
+	"Australia/North" : 912,
+	"Australia/Queensland" : 910,
+	"Australia/South" : 909,
+	"Australia/Tasmania" : 914,
+	"Australia/Victoria" : 917,
+	"Australia/West" : 918,
+	"Australia/Yancowinna" : 911,
+	"Brazil/Acre" : 796,
+	"Brazil/DeNoronha" : 777,
+	"Brazil/East" : 800,
+	"Brazil/West" : 762,
+	"Canada/Atlantic" : 740,
+	"Canada/Central" : 812,
+	"Canada/Eastern" : 809,
+	"Canada/Mountain" : 729,
+	"Canada/Newfoundland" : 803,
+	"Canada/Pacific" : 810,
+	"Canada/Saskatchewan" : 794,
+	"Canada/Yukon" : 811,
+	"Chile/Continental" : 798,
+	"Chile/EasterIsland" : 981,
+	"Cuba" : 741,
+	"Egypt" : 675,
+	"Eire" : 933,
+	"Etc/GMT+0" : 920,
+	"Etc/GMT-0" : 920,
+	"Etc/GMT0" : 920,
+	"Etc/Greenwich" : 920,
+	"Etc/UCT" : 921,
+	"Etc/Universal" : 921,
+	"Etc/Zulu" : 921,
+	"Europe/Belfast" : 941,
+	"Europe/Bratislava" : 950,
+	"Europe/Busingen" : 967,
+	"Europe/Guernsey" : 941,
+	"Europe/Isle_of_Man" : 941,
+	"Europe/Jersey" : 941,
+	"Europe/Ljubljana" : 926,
+	"Europe/Mariehamn" : 935,
+	"Europe/Nicosia" : 868,
+	"Europe/Podgorica" : 926,
+	"Europe/San_Marino" : 952,
+	"Europe/Sarajevo" : 926,
+	"Europe/Skopje" : 926,
+	"Europe/Tiraspol" : 931,
+	"Europe/Vaduz" : 967,
+	"Europe/Vatican" : 952,
+	"Europe/Zagreb" : 926,
+	"GB" : 941,
+	"GB-Eire" : 941,
+	"GMT+0" : 920,
+	"GMT-0" : 920,
+	"GMT0" : 920,
+	"Greenwich" : 920,
+	"Hongkong" : 849,
+	"Iceland" : 906,
+	"Indian/Antananarivo" : 685,
+	"Indian/Comoro" : 685,
+	"Indian/Mayotte" : 685,
+	"Iran" : 888,
+	"Israel" : 854,
+	"Jamaica" : 753,
+	"Japan" : 890,
+	"Kwajalein" : 994,
+	"Libya" : 688,
+	"Mexico/BajaNorte" : 808,
+	"Mexico/BajaSur" : 765,
+	"Mexico/General" : 769,
+	"NZ" : 977,
+	"NZ-CHAT" : 979,
+	"Navajo" : 727,
+	"PRC" : 882,
+	"Pacific/Enderbury" : 991,
+	"Pacific/Johnston" : 990,
+	"Pacific/Midway" : 1001,
+	"Pacific/Ponape" : 1004,
+	"Pacific/Saipan" : 989,
+	"Pacific/Samoa" : 1001,
+	"Pacific/Truk" : 980,
+	"Pacific/Yap" : 980,
+	"Poland" : 965,
+	"Portugal" : 940,
+	"ROC" : 885,
+	"ROK" : 881,
+	"Singapore" : 883,
+	"Turkey" : 936,
+	"UCT" : 921,
+	"US/Alaska" : 692,
+	"US/Aleutian" : 691,
+	"US/Arizona" : 786,
+	"US/Central" : 720,
+	"US/East-Indiana" : 743,
+	"US/Eastern" : 774,
+	"US/Hawaii" : 990,
+	"US/Indiana-Starke" : 744,
+	"US/Michigan" : 728,
+	"US/Mountain" : 727,
+	"US/Pacific" : 759,
+	"US/Samoa" : 1001,
+	"Universal" : 921,
+	"W-SU" : 947,
+	"Zulu" : 921,
+}
+
+timezoneAbbrevInfo = {
+	"A" : {"offset" : 60, "category" : TZ_MILITARY},
+	"B" : {"offset" : 120, "category" : TZ_MILITARY},
+	"C" : {"offset" : 180, "category" : TZ_MILITARY},
+	"D" : {"offset" : 240, "category" : TZ_MILITARY},
+	"E" : {"offset" : 300, "category" : TZ_MILITARY},
+	"F" : {"offset" : 360, "category" : TZ_MILITARY},
+	"G" : {"offset" : 420, "category" : TZ_MILITARY},
+	"H" : {"offset" : 480, "category" : TZ_MILITARY},
+	"I" : {"offset" : 540, "category" : TZ_MILITARY},
+	"K" : {"offset" : 600, "category" : TZ_MILITARY},
+	"L" : {"offset" : 660, "category" : TZ_MILITARY},
+	"M" : {"offset" : 720, "category" : TZ_MILITARY},
+	"N" : {"offset" : -60, "category" : TZ_MILITARY},
+	"O" : {"offset" : -120, "category" : TZ_MILITARY},
+	"P" : {"offset" : -180, "category" : TZ_MILITARY},
+	"Q" : {"offset" : -240, "category" : TZ_MILITARY},
+	"R" : {"offset" : -300, "category" : TZ_MILITARY},
+	"S" : {"offset" : -360, "category" : TZ_MILITARY},
+	"T" : {"offset" : -420, "category" : TZ_MILITARY},
+	"U" : {"offset" : -480, "category" : TZ_MILITARY},
+	"V" : {"offset" : -540, "category" : TZ_MILITARY},
+	"W" : {"offset" : -600, "category" : TZ_MILITARY},
+	"X" : {"offset" : -660, "category" : TZ_MILITARY},
+	"Y" : {"offset" : -720, "category" : TZ_MILITARY},
+	"Z" : {"offset" : 0, "category" : TZ_MILITARY},
+	"AT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"BT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"CT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"ET" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"GT" : {"offset" : 0, "category" : 0},
+	"IT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"KT" : {"offset" : 540, "category" : 0},
+	"MT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"PT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"ST" : {"offset" : 780, "category" : 0},
+	"UT" : {"offset" : 0, "category" : TZ_UTC|TZ_RFC},
+	"WT" : {"offset" : 0, "category" : 0},
+	"ACT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"ADT" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"AET" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"AFT" : {"offset" : 270, "category" : 0},
+	"AMT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"AoE" : {"offset" : -720, "category" : 0},
+	"ART" : {"offset" : -180, "category" : 0},
+	"AST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"AZT" : {"offset" : 240, "category" : 0},
+	"BDT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"BNT" : {"offset" : 480, "category" : 0},
+	"BOT" : {"offset" : -240, "category" : 0},
+	"BRT" : {"offset" : -180, "category" : 0},
+	"BST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"BTT" : {"offset" : 360, "category" : 0},
+	"CAT" : {"offset" : 120, "category" : 0},
+	"CCT" : {"offset" : 390, "category" : 0},
+	"CDT" : {"offset" : -300, "category" : TZ_RFC|TZ_AMBIGUOUS|TZ_DST},
+	"CET" : {"offset" : 60, "category" : 0},
+	"CIT" : {"offset" : -300, "category" : 0},
+	"CKT" : {"offset" : -600, "category" : 0},
+	"CLT" : {"offset" : -180, "category" : 0},
+	"COT" : {"offset" : -300, "category" : 0},
+	"CST" : {"offset" : -360, "category" : TZ_RFC|TZ_AMBIGUOUS},
+	"CVT" : {"offset" : -60, "category" : 0},
+	"CXT" : {"offset" : 420, "category" : 0},
+	"EAT" : {"offset" : 180, "category" : 0},
+	"ECT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"EDT" : {"offset" : -240, "category" : TZ_RFC|TZ_AMBIGUOUS|TZ_DST},
+	"EET" : {"offset" : 120, "category" : 0},
+	"EGT" : {"offset" : -60, "category" : 0},
+	"EST" : {"offset" : -300, "category" : TZ_RFC|TZ_AMBIGUOUS},
+	"FET" : {"offset" : 180, "category" : 0},
+	"FJT" : {"offset" : 720, "category" : 0},
+	"FKT" : {"offset" : -240, "category" : 0},
+	"FNT" : {"offset" : -120, "category" : 0},
+	"GET" : {"offset" : 240, "category" : 0},
+	"GFT" : {"offset" : -180, "category" : 0},
+	"GMT" : {"offset" : 0, "category" : TZ_UTC|TZ_RFC},
+	"GST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"GYT" : {"offset" : -240, "category" : 0},
+	"HAA" : {"offset" : -180, "category" : 0},
+	"HAC" : {"offset" : -300, "category" : 0},
+	"HAE" : {"offset" : -240, "category" : 0},
+	"HAP" : {"offset" : -420, "category" : 0},
+	"HAR" : {"offset" : -360, "category" : 0},
+	"HAT" : {"offset" : -90, "category" : 0},
+	"HDT" : {"offset" : -540, "category" : TZ_DST},
+	"HKT" : {"offset" : 480, "category" : 0},
+	"HLV" : {"offset" : -210, "category" : 0},
+	"HNA" : {"offset" : -240, "category" : 0},
+	"HNC" : {"offset" : -360, "category" : 0},
+	"HNE" : {"offset" : -300, "category" : 0},
+	"HNP" : {"offset" : -480, "category" : 0},
+	"HNR" : {"offset" : -420, "category" : 0},
+	"HNT" : {"offset" : -150, "category" : 0},
+	"HST" : {"offset" : -600, "category" : 0},
+	"ICT" : {"offset" : 420, "category" : 0},
+	"IDT" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"IOT" : {"offset" : 360, "category" : 0},
+	"IST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"JST" : {"offset" : 540, "category" : 0},
+	"KGT" : {"offset" : 360, "category" : 0},
+	"KIT" : {"offset" : 300, "category" : 0},
+	"KST" : {"offset" : 540, "category" : 0},
+	"MCK" : {"offset" : 180, "category" : 0},
+	"MDT" : {"offset" : -360, "category" : TZ_RFC|TZ_DST},
+	"MEZ" : {"offset" : 60, "category" : 0},
+	"MHT" : {"offset" : 720, "category" : 0},
+	"MMT" : {"offset" : 390, "category" : 0},
+	"MSD" : {"offset" : 240, "category" : TZ_DST},
+	"MSK" : {"offset" : 180, "category" : 0},
+	"MST" : {"offset" : -420, "category" : TZ_RFC|TZ_AMBIGUOUS},
+	"MUT" : {"offset" : 240, "category" : 0},
+	"MVT" : {"offset" : 300, "category" : 0},
+	"MYT" : {"offset" : 480, "category" : 0},
+	"NCT" : {"offset" : 660, "category" : 0},
+	"NDT" : {"offset" : -90, "category" : TZ_DST},
+	"NFT" : {"offset" : 660, "category" : 0},
+	"NPT" : {"offset" : 345, "category" : 0},
+	"NRT" : {"offset" : 720, "category" : 0},
+	"NST" : {"offset" : -150, "category" : 0},
+	"NUT" : {"offset" : -660, "category" : 0},
+	"OEZ" : {"offset" : 120, "category" : 0},
+	"PDT" : {"offset" : -420, "category" : TZ_RFC|TZ_DST},
+	"PET" : {"offset" : -300, "category" : 0},
+	"PGT" : {"offset" : 600, "category" : 0},
+	"PHT" : {"offset" : 480, "category" : 0},
+	"PKT" : {"offset" : 300, "category" : 0},
+	"PST" : {"offset" : -480, "category" : TZ_RFC|TZ_AMBIGUOUS},
+	"PWT" : {"offset" : 540, "category" : 0},
+	"PYT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"RET" : {"offset" : 240, "category" : 0},
+	"SBT" : {"offset" : 660, "category" : 0},
+	"SCT" : {"offset" : 240, "category" : 0},
+	"SGT" : {"offset" : 480, "category" : 0},
+	"SRT" : {"offset" : -180, "category" : 0},
+	"SST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"TFT" : {"offset" : 300, "category" : 0},
+	"TJT" : {"offset" : 300, "category" : 0},
+	"TKT" : {"offset" : 780, "category" : 0},
+	"TLT" : {"offset" : 540, "category" : 0},
+	"TMT" : {"offset" : 300, "category" : 0},
+	"TOT" : {"offset" : 780, "category" : 0},
+	"TRT" : {"offset" : 180, "category" : 0},
+	"TVT" : {"offset" : 720, "category" : 0},
+	"UTC" : {"offset" : 0, "category" : TZ_UTC|TZ_RFC},
+	"UYT" : {"offset" : -180, "category" : 0},
+	"UZT" : {"offset" : 300, "category" : 0},
+	"VET" : {"offset" : -210, "category" : 0},
+	"VUT" : {"offset" : 660, "category" : 0},
+	"WAT" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"WDT" : {"offset" : 540, "category" : TZ_DST},
+	"WET" : {"offset" : 0, "category" : 0},
+	"WEZ" : {"offset" : 0, "category" : 0},
+	"WFT" : {"offset" : 720, "category" : 0},
+	"WGT" : {"offset" : -180, "category" : 0},
+	"WIB" : {"offset" : 420, "category" : 0},
+	"WIT" : {"offset" : 540, "category" : 0},
+	"WST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"ACDT" : {"offset" : 630, "category" : TZ_DST},
+	"ACST" : {"offset" : 570, "category" : 0},
+	"ADST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"AEDT" : {"offset" : 660, "category" : TZ_DST},
+	"AEST" : {"offset" : 600, "category" : 0},
+	"AKDT" : {"offset" : -480, "category" : TZ_DST},
+	"AKST" : {"offset" : -540, "category" : 0},
+	"ALMT" : {"offset" : 360, "category" : 0},
+	"AMDT" : {"offset" : 300, "category" : TZ_DST},
+	"AMST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"ANAT" : {"offset" : 720, "category" : 0},
+	"AQTT" : {"offset" : 300, "category" : 0},
+	"AWDT" : {"offset" : 540, "category" : TZ_DST},
+	"AWST" : {"offset" : 480, "category" : 0},
+	"AZOT" : {"offset" : -60, "category" : 0},
+	"AZST" : {"offset" : 300, "category" : TZ_DST},
+	"BDST" : {"offset" : 60, "category" : TZ_DST},
+	"BRST" : {"offset" : -120, "category" : TZ_DST},
+	"CAST" : {"offset" : 480, "category" : 0},
+	"CDST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"CEDT" : {"offset" : 120, "category" : TZ_DST},
+	"CEST" : {"offset" : 120, "category" : TZ_DST},
+	"CHOT" : {"offset" : 480, "category" : 0},
+	"ChST" : {"offset" : 600, "category" : 0},
+	"CHUT" : {"offset" : 600, "category" : 0},
+	"CIST" : {"offset" : -300, "category" : 0},
+	"CLDT" : {"offset" : -180, "category" : TZ_DST},
+	"CLST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"DAVT" : {"offset" : 420, "category" : 0},
+	"DDUT" : {"offset" : 600, "category" : 0},
+	"EADT" : {"offset" : -300, "category" : TZ_DST},
+	"EAST" : {"offset" : -300, "category" : 0},
+	"ECST" : {"offset" : 120, "category" : TZ_DST},
+	"EDST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"EEDT" : {"offset" : 180, "category" : TZ_DST},
+	"EEST" : {"offset" : 180, "category" : TZ_DST},
+	"EGST" : {"offset" : 0, "category" : TZ_DST},
+	"FJDT" : {"offset" : 780, "category" : TZ_DST},
+	"FJST" : {"offset" : 780, "category" : TZ_DST},
+	"FKDT" : {"offset" : -180, "category" : TZ_DST},
+	"FKST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"GALT" : {"offset" : -360, "category" : 0},
+	"GAMT" : {"offset" : -540, "category" : 0},
+	"GILT" : {"offset" : 720, "category" : 0},
+	"HADT" : {"offset" : -540, "category" : TZ_DST},
+	"HAST" : {"offset" : -600, "category" : 0},
+	"HOVT" : {"offset" : 420, "category" : 0},
+	"IRDT" : {"offset" : 270, "category" : TZ_DST},
+	"IRKT" : {"offset" : 480, "category" : 0},
+	"IRST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"KOST" : {"offset" : 660, "category" : 0},
+	"KRAT" : {"offset" : 420, "category" : 0},
+	"KUYT" : {"offset" : 240, "category" : 0},
+	"LHDT" : {"offset" : 660, "category" : TZ_DST},
+	"LHST" : {"offset" : 630, "category" : 0},
+	"LINT" : {"offset" : 840, "category" : 0},
+	"MAGT" : {"offset" : 600, "category" : 0},
+	"MART" : {"offset" : -510, "category" : 0},
+	"MAWT" : {"offset" : 300, "category" : 0},
+	"MDST" : {"offset" : -360, "category" : TZ_DST},
+	"MESZ" : {"offset" : 120, "category" : 0},
+	"NFDT" : {"offset" : 720, "category" : TZ_DST},
+	"NOVT" : {"offset" : 360, "category" : 0},
+	"NZDT" : {"offset" : 780, "category" : TZ_DST},
+	"NZST" : {"offset" : 720, "category" : 0},
+	"OESZ" : {"offset" : 180, "category" : 0},
+	"OMST" : {"offset" : 360, "category" : 0},
+	"ORAT" : {"offset" : 300, "category" : 0},
+	"PDST" : {"offset" : -420, "category" : TZ_DST},
+	"PETT" : {"offset" : 720, "category" : 0},
+	"PHOT" : {"offset" : 780, "category" : 0},
+	"PMDT" : {"offset" : -120, "category" : TZ_DST},
+	"PMST" : {"offset" : -180, "category" : 0},
+	"PONT" : {"offset" : 660, "category" : 0},
+	"PYST" : {"offset" : 0, "category" : TZ_AMBIGUOUS},
+	"QYZT" : {"offset" : 360, "category" : 0},
+	"ROTT" : {"offset" : -180, "category" : 0},
+	"SAKT" : {"offset" : 600, "category" : 0},
+	"SAMT" : {"offset" : 240, "category" : 0},
+	"SAST" : {"offset" : 120, "category" : 0},
+	"SRET" : {"offset" : 660, "category" : 0},
+	"SYOT" : {"offset" : 180, "category" : 0},
+	"TAHT" : {"offset" : -600, "category" : 0},
+	"TOST" : {"offset" : 840, "category" : TZ_DST},
+	"ULAT" : {"offset" : 480, "category" : 0},
+	"UYST" : {"offset" : -120, "category" : TZ_DST},
+	"VLAT" : {"offset" : 600, "category" : 0},
+	"VOST" : {"offset" : 360, "category" : 0},
+	"WAKT" : {"offset" : 720, "category" : 0},
+	"WAST" : {"offset" : 120, "category" : TZ_DST},
+	"WEDT" : {"offset" : 60, "category" : TZ_DST},
+	"WEST" : {"offset" : 60, "category" : TZ_DST},
+	"WESZ" : {"offset" : 60, "category" : 0},
+	"WGST" : {"offset" : -120, "category" : TZ_DST},
+	"WITA" : {"offset" : 480, "category" : 0},
+	"YAKT" : {"offset" : 540, "category" : 0},
+	"YAPT" : {"offset" : 600, "category" : 0},
+	"YEKT" : {"offset" : 300, "category" : 0},
+	"ACWST" : {"offset" : 525, "category" : 0},
+	"ANAST" : {"offset" : 720, "category" : TZ_DST},
+	"AZODT" : {"offset" : 0, "category" : TZ_DST},
+	"AZOST" : {"offset" : 0, "category" : TZ_AMBIGUOUS|TZ_DST},
+	"CHADT" : {"offset" : 825, "category" : TZ_DST},
+	"CHAST" : {"offset" : 765, "category" : 0},
+	"CHODT" : {"offset" : 540, "category" : TZ_DST},
+	"CHOST" : {"offset" : 540, "category" : TZ_DST},
+	"CIDST" : {"offset" : -240, "category" : TZ_DST},
+	"EASST" : {"offset" : -300, "category" : TZ_DST},
+	"EFATE" : {"offset" : 660, "category" : 0},
+	"HOVDT" : {"offset" : 480, "category" : TZ_DST},
+	"HOVST" : {"offset" : 480, "category" : TZ_DST},
+	"IRKST" : {"offset" : 540, "category" : TZ_DST},
+	"KRAST" : {"offset" : 480, "category" : TZ_DST},
+	"MAGST" : {"offset" : 720, "category" : TZ_DST},
+	"NACDT" : {"offset" : -300, "category" : TZ_DST},
+	"NACST" : {"offset" : -360, "category" : 0},
+	"NAEDT" : {"offset" : -240, "category" : TZ_DST},
+	"NAEST" : {"offset" : -300, "category" : 0},
+	"NAMDT" : {"offset" : -360, "category" : TZ_DST},
+	"NAMST" : {"offset" : -420, "category" : 0},
+	"NAPDT" : {"offset" : -420, "category" : TZ_DST},
+	"NAPST" : {"offset" : -480, "category" : 0},
+	"NOVST" : {"offset" : 420, "category" : TZ_DST},
+	"OMSST" : {"offset" : 420, "category" : TZ_DST},
+	"PETST" : {"offset" : 720, "category" : TZ_DST},
+	"SAMST" : {"offset" : 240, "category" : TZ_DST},
+	"ULAST" : {"offset" : 540, "category" : TZ_DST},
+	"VLAST" : {"offset" : 660, "category" : TZ_DST},
+	"WARST" : {"offset" : -180, "category" : TZ_DST},
+	"YAKST" : {"offset" : 600, "category" : TZ_DST},
+	"YEKST" : {"offset" : 360, "category" : TZ_DST},
+	"CHODST" : {"offset" : 540, "category" : TZ_DST},
+	"HOVDST" : {"offset" : 480, "category" : TZ_DST},
+}
diff --git a/tarantool/msgpack_ext/types/timezones/validate_timezones.py b/tarantool/msgpack_ext/types/timezones/validate_timezones.py
new file mode 100644
index 00000000..0626d8c3
--- /dev/null
+++ b/tarantool/msgpack_ext/types/timezones/validate_timezones.py
@@ -0,0 +1,12 @@
+import pytz
+from timezones import timezoneToIndex, timezoneAbbrevInfo
+
+if __name__ != '__main__':
+    raise Error('Import not expected')
+
+for timezone in timezoneToIndex.keys():
+    if timezone in pytz.all_timezones:
+        continue
+
+    if not timezone in timezoneAbbrevInfo:
+        raise Exception(f'Unknown Tarantool timezone {timezone}')
diff --git a/tarantool/msgpack_ext/unpacker.py b/tarantool/msgpack_ext/unpacker.py
index 44bfdb63..b303e18d 100644
--- a/tarantool/msgpack_ext/unpacker.py
+++ b/tarantool/msgpack_ext/unpacker.py
@@ -1,9 +1,11 @@
 import tarantool.msgpack_ext.decimal as ext_decimal
 import tarantool.msgpack_ext.uuid as ext_uuid
+import tarantool.msgpack_ext.datetime as ext_datetime
 
 decoders = {
-    ext_decimal.EXT_ID: ext_decimal.decode,
-    ext_uuid.EXT_ID   : ext_uuid.decode   ,
+    ext_decimal.EXT_ID : ext_decimal.decode ,
+    ext_uuid.EXT_ID    : ext_uuid.decode    ,
+    ext_datetime.EXT_ID: ext_datetime.decode,
 }
 
 def ext_hook(code, data):


Test patch:
diff --git a/test/suites/__init__.py b/test/suites/__init__.py
index 94357c8e..c5792bdd 100644
--- a/test/suites/__init__.py
+++ b/test/suites/__init__.py
@@ -17,13 +17,14 @@
 from .test_ssl import TestSuite_Ssl
 from .test_decimal import TestSuite_Decimal
 from .test_uuid import TestSuite_UUID
+from .test_datetime import TestSuite_Datetime
 
 test_cases = (TestSuite_Schema_UnicodeConnection,
               TestSuite_Schema_BinaryConnection,
               TestSuite_Request, TestSuite_Protocol, TestSuite_Reconnect,
               TestSuite_Mesh, TestSuite_Execute, TestSuite_DBAPI,
               TestSuite_Encoding, TestSuite_Pool, TestSuite_Ssl,
-              TestSuite_Decimal, TestSuite_UUID)
+              TestSuite_Decimal, TestSuite_UUID, TestSuite_Datetime)
 
 def load_tests(loader, tests, pattern):
     suite = unittest.TestSuite()
diff --git a/test/suites/lib/skip.py b/test/suites/lib/skip.py
index 9ce76991..71bfce13 100644
--- a/test/suites/lib/skip.py
+++ b/test/suites/lib/skip.py
@@ -154,3 +154,14 @@ def skip_or_run_UUID_test(func):
 
     return skip_or_run_test_tarantool(func, '2.4.1',
                                       'does not support UUID type')
+
+def skip_or_run_datetime_test(func):
+    """Decorator to skip or run datetime-related tests depending on
+    the tarantool version.
+
+    Tarantool supports datetime type only since 2.10.0 version.
+    See https://github.com/tarantool/tarantool/issues/5941
+    """
+
+    return skip_or_run_test_pcall_require(func, 'datetime',
+                                      'does not support datetime type')
diff --git a/test/suites/test_datetime.py b/test/suites/test_datetime.py
new file mode 100644
index 00000000..c952ec2a
--- /dev/null
+++ b/test/suites/test_datetime.py
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function
+
+import sys
+import re
+import unittest
+import msgpack
+import warnings
+import tarantool
+import pandas
+
+from tarantool.msgpack_ext.packer import default as packer_default
+from tarantool.msgpack_ext.unpacker import ext_hook as unpacker_ext_hook
+
+from .lib.tarantool_server import TarantoolServer
+from .lib.skip import skip_or_run_datetime_test
+from tarantool.error import MsgpackError, MsgpackWarning
+
+class TestSuite_Datetime(unittest.TestCase):
+    @classmethod
+    def setUpClass(self):
+        print(' DATETIME EXT TYPE '.center(70, '='), file=sys.stderr)
+        print('-' * 70, file=sys.stderr)
+        self.srv = TarantoolServer()
+        self.srv.script = 'test/suites/box.lua'
+        self.srv.start()
+
+        self.adm = self.srv.admin
+        self.adm(r"""
+            _, datetime = pcall(require, 'datetime')
+
+            box.schema.space.create('test')
+            box.space['test']:create_index('primary', {
+                type = 'tree',
+                parts = {1, 'string'},
+                unique = true})
+
+            box.schema.user.create('test', {password = 'test', if_not_exists = true})
+            box.schema.user.grant('test', 'read,write,execute', 'universe')
+        """)
+
+        self.con = tarantool.Connection(self.srv.host, self.srv.args['primary'],
+                                        user='test', password='test')
+
+    def setUp(self):
+        # prevent a remote tarantool from clean our session
+        if self.srv.is_started():
+            self.srv.touch_lock()
+
+        self.adm("box.space['test']:truncate()")
+
+
+    def test_Datetime_class_API(self):
+        dt = tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                nsec=308543321, tzoffset=180)
+
+        self.assertEqual(dt.year, 2022)
+        self.assertEqual(dt.month, 8)
+        self.assertEqual(dt.day, 31)
+        self.assertEqual(dt.hour, 18)
+        self.assertEqual(dt.minute, 7)
+        self.assertEqual(dt.sec, 54)
+        self.assertEqual(dt.nsec, 308543321)
+        # Both Tarantool and pandas prone to precision loss for timestamp() floats
+        self.assertEqual(dt.timestamp, 1661958474.308543)
+        self.assertEqual(dt.tzoffset, 180)
+        self.assertEqual(dt.tz, '')
+        self.assertEqual(dt.value, 1661958474308543321)
+
+    def test_Datetime_class_API_wth_tz(self):
+        dt = tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                nsec=308543321, tzoffset=123, tz='Europe/Moscow')
+
+        self.assertEqual(dt.year, 2022)
+        self.assertEqual(dt.month, 8)
+        self.assertEqual(dt.day, 31)
+        self.assertEqual(dt.hour, 18)
+        self.assertEqual(dt.minute, 7)
+        self.assertEqual(dt.sec, 54)
+        self.assertEqual(dt.nsec, 308543321)
+        # Both Tarantool and pandas prone to precision loss for timestamp() floats
+        self.assertEqual(dt.timestamp, 1661958474.308543)
+        self.assertEqual(dt.tzoffset, 180)
+        self.assertEqual(dt.tz, 'Europe/Moscow')
+        self.assertEqual(dt.value, 1661958474308543321)
+
+
+    datetime_class_invalid_init_cases = {
+        'positional_year': {
+            'args': [2022],
+            'kwargs': {},
+            'type': ValueError,
+            'msg': 'data argument (first positional argument) expected to be a "bytes" instance'
+        },
+        'positional_date': {
+            'args': [2022, 8, 31],
+            'kwargs': {},
+            'type': TypeError,
+            'msg': '__init__() takes from 1 to 2 positional arguments but 4 were given'
+        },
+        'mixing_date_and_timestamp': {
+            'args': [],
+            'kwargs': {'year': 2022, 'timestamp': 1661969274},
+            'type': ValueError,
+            'msg': 'Cannot provide both timestamp and year, month, day, hour, minute, sec'
+        },
+        'mixing_float_timestamp_and_nsec': {
+            'args': [],
+            'kwargs': {'timestamp': 1661969274.308543, 'nsec': 308543321},
+            'type': ValueError,
+            'msg': 'timestamp must be int if nsec provided'
+        },
+        'unknown_tz': {
+            'args': [],
+            'kwargs': {'year': 2022, 'month': 8, 'day': 31, 'tz': 'Moskva'},
+            'type': ValueError,
+            'msg': 'Unknown Tarantool timezone "Moskva"'
+        },
+        'abbrev_tz': {
+            'args': [],
+            'kwargs': {'year': 2022, 'month': 8, 'day': 31, 'tz': 'AET'},
+            'type': ValueError,
+            'msg': 'Failed to create datetime with ambiguous timezone "AET"'
+        },
+    }
+
+    def test_Datetime_class_invalid_init(self):
+        for name in self.datetime_class_invalid_init_cases.keys():
+            with self.subTest(msg=name):
+                case = self.datetime_class_invalid_init_cases[name]
+                self.assertRaisesRegex(
+                    case['type'], re.escape(case['msg']),
+                    lambda: tarantool.Datetime(*case['args'], **case['kwargs']))
+
+
+    integration_cases = {
+        'date': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31),
+            'msgpack': (b'\x80\xa4\x0e\x63\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31})",
+        },
+        'date_unix_start': {
+            'python': tarantool.Datetime(year=1970, month=1, day=1),
+            'msgpack': (b'\x00\x00\x00\x00\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=1970, month=1, day=1})",
+        },
+        'date_before_1970': {
+            'python': tarantool.Datetime(year=1900, month=1, day=1),
+            'msgpack': (b'\x80\x81\x55\x7c\xff\xff\xff\xff'),
+            'tarantool': r"datetime.new({year=1900, month=1, day=1})",
+        },
+        'datetime_with_minutes': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7),
+            'msgpack': (b'\x44\xa3\x0f\x63\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7})",
+        },
+        'datetime_with_seconds': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54),
+            'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54})",
+        },
+        'datetime_with_microseconds': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543000),
+            'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00\x18\xfe\x63\x12\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
+                         r"nsec=308543000})",
+        },
+        'datetime_with_nanoseconds': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321),
+            '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})",
+        },
+        'date_before_1970_with_nanoseconds': {
+            'python': tarantool.Datetime(year=1900, month=1, day=1, nsec=308543321),
+            'msgpack': (b'\x80\x81\x55\x7c\xff\xff\xff\xff\x59\xff\x63\x12\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({year=1900, month=1, day=1, nsec=308543321})",
+        },
+        'timestamp': {
+            'python': tarantool.Datetime(timestamp=1661969274),
+            'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({timestamp=1661969274})",
+        },
+        'timestamp_with_nanoseconds': {
+            'python': tarantool.Datetime(timestamp=1661969274, nsec=308543321),
+            'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\x00\x00\x00\x00'),
+            'tarantool': r"datetime.new({timestamp=1661969274, nsec=308543321})",
+        },
+        'datetime_with_positive_offset': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tzoffset=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})",
+        },
+        'datetime_with_negative_offset': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tzoffset=-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})",
+        },
+        'timestamp_with_positive_offset': {
+            'python': tarantool.Datetime(timestamp=1661969274, tzoffset=180),
+            'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x00\x00\x00'),
+            'tarantool': r"datetime.new({timestamp=1661969274, tzoffset=180})",
+        },
+        'timestamp_with_negative_offset': {
+            'python': tarantool.Datetime(timestamp=1661969274, tzoffset=-60),
+            'msgpack': (b'\x8a\xb1\x0f\x63\x00\x00\x00\x00\x00\x00\x00\x00\xc4\xff\x00\x00'),
+            'tarantool': r"datetime.new({timestamp=1661969274, tzoffset=-60})",
+        },
+        'date_with_utc_tz': {
+            'python': tarantool.Datetime(year=1970, month=1, day=1, tz='UTC'),
+            'msgpack': (b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x01'),
+            'tarantool': r"datetime.new({year=1970, month=1, day=1, tz='UTC'})",
+        },
+        'date_with_tz': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, tz='Europe/Moscow'),
+            'msgpack': (b'\x50\x7a\x0e\x63\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x00\xb3\x03'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, tz='Europe/Moscow'})",
+        },
+        'datetime_with_tz': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tz='Europe/Moscow'),
+            'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\xb3\x03'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
+                         r"nsec=308543321, tz='Europe/Moscow'})",
+        },
+        'datetime_with_tz_winter_time': {
+            'python': tarantool.Datetime(year=2008, month=8, day=1, tz='Europe/Moscow'),
+            'msgpack': (b'\xc0\x19\x92\x48\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\xb3\x03'),
+            'tarantool': r"datetime.new({year=2008, month=8, day=1, tz='Europe/Moscow'})",
+        },
+        'datetime_with_tz_and_offset': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tz='Europe/Moscow', tzoffset=123),
+            'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\xb3\x03'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
+                         r"nsec=308543321, tz='Europe/Moscow', tzoffset=123})",
+        },
+        'datetime_with_abbrev_tz': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tz='MSK'),
+            'msgpack': (b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\xee\x00'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
+                         r"nsec=308543321, tz='MSK'})",
+        },
+        'datetime_with_abbrev_tz_and_zero_offset': {
+            'python': tarantool.Datetime(year=2022, month=8, day=31, hour=18, minute=7, sec=54,
+                                         nsec=308543321, tz='AZODT'),
+            'msgpack': (b'\x7a\xa3\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\x00\x00\x12\x02'),
+            'tarantool': r"datetime.new({year=2022, month=8, day=31, hour=18, min=7, sec=54, " +
+                         r"nsec=308543321, tz='AZODT'})",
+        },
+    }
+
+    def test_msgpack_decode(self):
+        for name in self.integration_cases.keys():
+            with self.subTest(msg=name):
+                case = self.integration_cases[name]
+
+                self.assertEqual(unpacker_ext_hook(4, case['msgpack']),
+                                 case['python'])
+
+    @skip_or_run_datetime_test
+    def test_tarantool_decode(self):
+        for name in self.integration_cases.keys():
+            with self.subTest(msg=name):
+                case = self.integration_cases[name]
+
+                self.adm(f"box.space['test']:replace{{'{name}', {case['tarantool']}, 'field'}}")
+
+                self.assertSequenceEqual(self.con.select('test', name),
+                                         [[name, case['python'], 'field']])
+
+    def test_msgpack_encode(self):
+        for name in self.integration_cases.keys():
+            with self.subTest(msg=name):
+                case = self.integration_cases[name]
+
+                self.assertEqual(packer_default(case['python']),
+                                 msgpack.ExtType(code=4, data=case['msgpack']))
+
+    @skip_or_run_datetime_test
+    def test_tarantool_encode(self):
+        for name in self.integration_cases.keys():
+            with self.subTest(msg=name):
+                case = self.integration_cases[name]
+
+                self.con.insert('test', [name, case['python'], 'field'])
+
+                lua_eval = f"""
+                    local dt = {case['tarantool']}
+                    
+                    local tuple = box.space['test']:get('{name}')
+                    assert(tuple ~= nil)
+
+                    if tuple[2] == dt then
+                        return true
+                    else
+                        return nil, ('%s is not equal to expected %s'):format(
+                            tostring(tuple[2]), tostring(dt))
+                    end
+                """
+
+                self.assertSequenceEqual(self.adm(lua_eval), [True])
+
+    def test_msgpack_decode_unknown_tzindex(self):
+        case = b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\xb4\x00\xff\xff'
+        self.assertRaisesRegex(
+            MsgpackError, 'Failed to decode datetime with unknown tzindex "-1"',
+            lambda: unpacker_ext_hook(4, case))
+
+    def test_msgpack_decode_ambiguous_tzindex(self):
+        case = b'\x4a\x79\x0f\x63\x00\x00\x00\x00\x59\xff\x63\x12\x00\x00\x82\x00'
+        self.assertRaisesRegex(
+            MsgpackError, 'Failed to create datetime with ambiguous timezone "AET"',
+            lambda: unpacker_ext_hook(4, case))
+
+    @classmethod
+    def tearDownClass(self):
+        self.con.close()
+        self.srv.stop()
+        self.srv.clean()