Skip to content

Commit 563a6d6

Browse files
liguriooleg-jukovec
authored andcommitted
datetime: add datetime type in msgpack
This patch provides datetime support for all space operations and as function return result. Datetime type was introduced in Tarantool 2.10. See more in issue [1]. Note that timezone's index and offset and intervals are not implemented in Tarantool, see [2] and [3]. This Lua snippet was quite useful for debugging encoding and decoding datetime in MessagePack: local msgpack = require('msgpack') local datetime = require('datetime') local dt = datetime.parse('2012-01-31T23:59:59.000000010Z') local mp_dt = msgpack.encode(dt):gsub('.', function (c) return string.format('%02x', string.byte(c)) end) print(mp_dt) -- d8047f80284f000000000a00000000000000 1. tarantool/tarantool#5946 2. #163 3. #165 Closes #118
1 parent 11c83c8 commit 563a6d6

File tree

7 files changed

+872
-1
lines changed

7 files changed

+872
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1414
- IPROTO_PUSH messages support (#67)
1515
- Public API with request object types (#126)
1616
- Support decimal type in msgpack (#96)
17+
- Support datetime type in msgpack (#118)
1718

1819
### Changed
1920

Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ test-connection-pool:
4646
go clean -testcache
4747
go test -tags "$(TAGS)" ./connection_pool/ -v -p 1
4848

49+
.PHONY: test-datetime
50+
test-datetime:
51+
@echo "Running tests in datetime package"
52+
go clean -testcache
53+
go test -tags "$(TAGS)" ./datetime/ -v -p 1
54+
4955
.PHONY: test-decimal
5056
test-decimal:
5157
@echo "Running tests in decimal package"

datetime/config.lua

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
local has_datetime, datetime = pcall(require, 'datetime')
2+
3+
if not has_datetime then
4+
error('Datetime unsupported, use Tarantool 2.10 or newer')
5+
end
6+
7+
-- Do not set listen for now so connector won't be
8+
-- able to send requests until everything is configured.
9+
box.cfg{
10+
work_dir = os.getenv("TEST_TNT_WORK_DIR"),
11+
}
12+
13+
box.schema.user.create('test', { password = 'test' , if_not_exists = true })
14+
box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true })
15+
16+
box.once("init", function()
17+
local s_1 = box.schema.space.create('testDatetime_1', {
18+
id = 524,
19+
if_not_exists = true,
20+
})
21+
s_1:create_index('primary', {
22+
type = 'TREE',
23+
parts = {
24+
{ field = 1, type = 'datetime' },
25+
},
26+
if_not_exists = true
27+
})
28+
s_1:truncate()
29+
30+
local s_3 = box.schema.space.create('testDatetime_2', {
31+
id = 526,
32+
if_not_exists = true,
33+
})
34+
s_3:create_index('primary', {
35+
type = 'tree',
36+
parts = {
37+
{1, 'uint'},
38+
},
39+
if_not_exists = true
40+
})
41+
s_3:truncate()
42+
43+
box.schema.func.create('call_datetime_testdata')
44+
box.schema.user.grant('test', 'read,write', 'space', 'testDatetime_1', { if_not_exists = true })
45+
box.schema.user.grant('test', 'read,write', 'space', 'testDatetime_2', { if_not_exists = true })
46+
end)
47+
48+
local function call_datetime_testdata()
49+
local dt1 = datetime.new({ year = 1934 })
50+
local dt2 = datetime.new({ year = 1961 })
51+
local dt3 = datetime.new({ year = 1968 })
52+
return {
53+
{
54+
5, "Go!", {
55+
{"Klushino", dt1},
56+
{"Baikonur", dt2},
57+
{"Novoselovo", dt3},
58+
},
59+
}
60+
}
61+
end
62+
rawset(_G, 'call_datetime_testdata', call_datetime_testdata)
63+
64+
-- Set listen only when every other thing is configured.
65+
box.cfg{
66+
listen = os.getenv("TEST_TNT_LISTEN"),
67+
}
68+
69+
require('console').start()

datetime/datetime.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Package with support of Tarantool's datetime data type.
2+
//
3+
// Datetime data type supported in Tarantool since 2.10.
4+
//
5+
// Since: 1.7.0
6+
//
7+
// See also:
8+
//
9+
// * Datetime Internals https://github.com/tarantool/tarantool/wiki/Datetime-Internals
10+
package datetime
11+
12+
import (
13+
"encoding/binary"
14+
"fmt"
15+
"time"
16+
17+
"gopkg.in/vmihailenco/msgpack.v2"
18+
)
19+
20+
// Datetime MessagePack serialization schema is an MP_EXT extension, which
21+
// creates container of 8 or 16 bytes long payload.
22+
//
23+
// +---------+--------+===============+-------------------------------+
24+
// |0xd7/0xd8|type (4)| seconds (8b) | nsec; tzoffset; tzindex; (8b) |
25+
// +---------+--------+===============+-------------------------------+
26+
//
27+
// MessagePack data encoded using fixext8 (0xd7) or fixext16 (0xd8), and may
28+
// contain:
29+
//
30+
// * [required] seconds parts as full, unencoded, signed 64-bit integer,
31+
// stored in little-endian order;
32+
//
33+
// * [optional] all the other fields (nsec, tzoffset, tzindex) if any of them
34+
// were having not 0 value. They are packed naturally in little-endian order;
35+
36+
// Datetime external type. Supported since Tarantool 2.10. See more details in
37+
// issue https://github.com/tarantool/tarantool/issues/5946.
38+
const datetime_extId = 4
39+
40+
// datetime structure keeps a number of seconds and nanoseconds since Unix Epoch.
41+
// Time is normalized by UTC, so time-zone offset is informative only.
42+
type datetime struct {
43+
// Seconds since Epoch, where the epoch is the point where the time
44+
// starts, and is platform dependent. For Unix, the epoch is January 1,
45+
// 1970, 00:00:00 (UTC). Tarantool uses a double type, see a structure
46+
// definition in src/lib/core/datetime.h and reasons in
47+
// https://github.com/tarantool/tarantool/wiki/Datetime-internals#intervals-in-c
48+
seconds int64
49+
// Nanoseconds, fractional part of seconds. Tarantool uses int32_t, see
50+
// a definition in src/lib/core/datetime.h.
51+
nsec int32
52+
// Timezone offset in minutes from UTC (not implemented in Tarantool,
53+
// see gh-163). Tarantool uses a int16_t type, see a structure
54+
// definition in src/lib/core/datetime.h.
55+
tzOffset int16
56+
// Olson timezone id (not implemented in Tarantool, see gh-163).
57+
// Tarantool uses a int16_t type, see a structure definition in
58+
// src/lib/core/datetime.h.
59+
tzIndex int16
60+
}
61+
62+
// Size of datetime fields in a MessagePack value.
63+
const (
64+
secondsSize = 8
65+
nsecSize = 4
66+
tzIndexSize = 2
67+
tzOffsetSize = 2
68+
)
69+
70+
const maxSize = secondsSize + nsecSize + tzIndexSize + tzOffsetSize
71+
72+
type Datetime struct {
73+
time time.Time
74+
}
75+
76+
// NewDatetime returns a pointer to a new datetime.Datetime that contains a
77+
// specified time.Time.
78+
func NewDatetime(t time.Time) *Datetime {
79+
dt := new(Datetime)
80+
dt.time = t
81+
return dt
82+
}
83+
84+
// ToTime returns a time.Time that Datetime contains.
85+
func (dtime *Datetime) ToTime() time.Time {
86+
return dtime.time
87+
}
88+
89+
var _ msgpack.Marshaler = (*Datetime)(nil)
90+
var _ msgpack.Unmarshaler = (*Datetime)(nil)
91+
92+
func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
93+
tm := dtime.ToTime()
94+
95+
var dt datetime
96+
dt.seconds = tm.Unix()
97+
dt.nsec = int32(tm.Nanosecond())
98+
dt.tzIndex = 0 // It is not implemented, see gh-163.
99+
dt.tzOffset = 0 // It is not implemented, see gh-163.
100+
101+
var bytesSize = secondsSize
102+
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 {
103+
bytesSize += nsecSize + tzIndexSize + tzOffsetSize
104+
}
105+
106+
buf := make([]byte, bytesSize)
107+
binary.LittleEndian.PutUint64(buf, uint64(dt.seconds))
108+
if bytesSize == maxSize {
109+
binary.LittleEndian.PutUint32(buf[secondsSize:], uint32(dt.nsec))
110+
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize:], uint16(dt.tzOffset))
111+
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize+tzOffsetSize:], uint16(dt.tzIndex))
112+
}
113+
114+
return buf, nil
115+
}
116+
117+
func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
118+
l := len(b)
119+
if l != maxSize && l != secondsSize {
120+
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
121+
}
122+
123+
var dt datetime
124+
sec := binary.LittleEndian.Uint64(b)
125+
dt.seconds = int64(sec)
126+
dt.nsec = 0
127+
if l == maxSize {
128+
dt.nsec = int32(binary.LittleEndian.Uint32(b[secondsSize:]))
129+
dt.tzOffset = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize:]))
130+
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
131+
}
132+
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
133+
*tm = *NewDatetime(tt)
134+
135+
return nil
136+
}
137+
138+
func init() {
139+
msgpack.RegisterExt(datetime_extId, &Datetime{})
140+
}

0 commit comments

Comments
 (0)