Skip to content

Commit 1cb237b

Browse files
committed
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 are not implemented in Tarantool, see [2]. 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(dt, mp_dt) ``` 1. tarantool/tarantool#5946 2. tarantool/tarantool#6751 Closes #118
1 parent 7897baf commit 1cb237b

File tree

7 files changed

+487
-1
lines changed

7 files changed

+487
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1616
- Support UUID type in msgpack (#90)
1717
- Go modules support (#91)
1818
- queue-utube handling (#85)
19+
- Support datetime type in msgpack (#118)
1920

2021
### Fixed
2122

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ test:
1515
go clean -testcache
1616
go test ./... -v -p 1
1717

18+
.PHONY: test-datetime
19+
test-datetime:
20+
@echo "Running tests in datetime package"
21+
go clean -testcache
22+
go test ./datetime/ -v -p 1
23+
1824
.PHONY: coverage
1925
coverage:
2026
go clean -testcache

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,9 +700,11 @@ and call
700700
```bash
701701
go clean -testcache && go test -v
702702
```
703-
Use the same for main `tarantool` package and `queue` and `uuid` subpackages.
703+
Use the same for main `tarantool` package, `queue`, `uuid` and `datetime` subpackages.
704704
`uuid` tests require
705705
[Tarantool 2.4.1 or newer](https://github.com/tarantool/tarantool/commit/d68fc29246714eee505bc9bbcd84a02de17972c5).
706+
`datetime` tests require
707+
[Tarantool 2.10 or newer](https://github.com/tarantool/tarantool/issues/5946).
706708

707709
## Alternative connectors
708710

datetime/config.lua

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
local has_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+
local s = box.schema.space.create('testDatetime', {
17+
id = 524,
18+
if_not_exists = true,
19+
})
20+
s:create_index('primary', {
21+
type = 'TREE',
22+
parts = {
23+
{
24+
field = 1,
25+
type = 'datetime',
26+
},
27+
},
28+
if_not_exists = true
29+
})
30+
s:truncate()
31+
32+
box.schema.user.grant('test', 'read,write', 'space', 'testDatetime', { if_not_exists = true })
33+
34+
-- Set listen only when every other thing is configured.
35+
box.cfg{
36+
listen = os.getenv("TEST_TNT_LISTEN"),
37+
}
38+
39+
require('console').start()

datetime/datetime.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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.6.0.
6+
package datetime
7+
8+
import (
9+
"fmt"
10+
"io"
11+
"reflect"
12+
"time"
13+
14+
"encoding/binary"
15+
16+
"gopkg.in/vmihailenco/msgpack.v2"
17+
)
18+
19+
// Datetime MessagePack serialization schema is an MP_EXT extension, which
20+
// creates container of 8 or 16 bytes long payload.
21+
//
22+
// +---------+--------+===============+-------------------------------+
23+
// |0xd7/0xd8|type (4)| seconds (8b) | nsec; tzoffset; tzindex; (8b) |
24+
// +---------+--------+===============+-------------------------------+
25+
//
26+
// MessagePack data encoded using fixext8 (0xd7) or fixext16 (0xd8), and may
27+
// contain:
28+
//
29+
// * [required] seconds parts as full, unencoded, signed 64-bit integer,
30+
// stored in little-endian order;
31+
//
32+
// * [optional] all the other fields (nsec, tzoffset, tzindex) if any of them
33+
// were having not 0 value. They are packed naturally in little-endian order;
34+
35+
// Datetime external type
36+
// Supported since Tarantool 2.10. See more details in issue
37+
// https://github.com/tarantool/tarantool/issues/5946
38+
const datetime_extId = 4
39+
40+
/**
41+
* datetime structure keeps number of seconds and
42+
* nanoseconds since Unix Epoch.
43+
* Time is normalized by UTC, so time-zone offset
44+
* is informative only.
45+
*/
46+
type datetime struct {
47+
// Seconds since Epoch
48+
seconds int64
49+
// Nanoseconds, fractional part of seconds
50+
nsec int32
51+
// Timezone offset in minutes from UTC
52+
// (not implemented in Tarantool, see gh-163)
53+
tzOffset int16
54+
// Olson timezone id
55+
// (not implemented in Tarantool, see gh-163)
56+
tzIndex int16
57+
}
58+
59+
const (
60+
secondsSize = 8
61+
nsecSize = 4
62+
tzIndexSize = 2
63+
tzOffsetSize = 2
64+
)
65+
66+
func encodeDatetime(e *msgpack.Encoder, v reflect.Value) error {
67+
var dt datetime
68+
69+
tm := v.Interface().(time.Time)
70+
dt.seconds = tm.Unix()
71+
dt.nsec = int32(tm.Nanosecond())
72+
dt.tzIndex = 0 /* not implemented, see gh-163 */
73+
dt.tzOffset = 0 /* not implemented, see gh-163 */
74+
75+
var bytesSize = secondsSize
76+
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 {
77+
bytesSize += nsecSize + tzIndexSize + tzOffsetSize
78+
}
79+
80+
buf := make([]byte, bytesSize)
81+
binary.LittleEndian.PutUint64(buf[0:], uint64(dt.seconds))
82+
if bytesSize == 16 {
83+
binary.LittleEndian.PutUint32(buf[secondsSize:], uint32(dt.nsec))
84+
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize:], uint16(dt.tzOffset))
85+
binary.LittleEndian.PutUint16(buf[secondsSize+nsecSize+tzOffsetSize:], uint16(dt.tzIndex))
86+
}
87+
88+
_, err := e.Writer().Write(buf)
89+
if err != nil {
90+
return fmt.Errorf("msgpack: can't write bytes to encoder writer: %w", err)
91+
}
92+
93+
return nil
94+
}
95+
96+
func decodeDatetime(d *msgpack.Decoder, v reflect.Value) error {
97+
var dt datetime
98+
secondsBytes := make([]byte, secondsSize)
99+
n, err := d.Buffered().Read(secondsBytes)
100+
if err != nil {
101+
return fmt.Errorf("msgpack: can't read bytes on datetime's seconds decode: %w", err)
102+
}
103+
if n < secondsSize {
104+
return fmt.Errorf("msgpack: unexpected end of stream after %d datetime bytes", n)
105+
}
106+
dt.seconds = int64(binary.LittleEndian.Uint64(secondsBytes))
107+
tailSize := nsecSize + tzOffsetSize + tzIndexSize
108+
tailBytes := make([]byte, tailSize)
109+
n, err = d.Buffered().Read(tailBytes)
110+
// Part with nanoseconds, tzoffset and tzindex is optional,
111+
// so we don't need to handle an error here.
112+
if err != nil && err != io.EOF {
113+
return fmt.Errorf("msgpack: can't read bytes on datetime's tail decode: %w", err)
114+
}
115+
dt.nsec = 0
116+
if err == nil {
117+
if n < tailSize {
118+
return fmt.Errorf("msgpack: can't read bytes on datetime's tail decode: %w", err)
119+
}
120+
dt.nsec = int32(binary.LittleEndian.Uint32(tailBytes[0:]))
121+
dt.tzOffset = int16(binary.LittleEndian.Uint16(tailBytes[nsecSize:]))
122+
dt.tzIndex = int16(binary.LittleEndian.Uint16(tailBytes[tzOffsetSize:]))
123+
}
124+
t := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
125+
v.Set(reflect.ValueOf(t))
126+
127+
return nil
128+
}
129+
130+
func init() {
131+
msgpack.Register(reflect.TypeOf((*time.Time)(nil)).Elem(), encodeDatetime, decodeDatetime)
132+
msgpack.RegisterExt(datetime_extId, (*time.Time)(nil))
133+
}

0 commit comments

Comments
 (0)