Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e51a5b3

Browse files
committedAug 4, 2022
datetime: add TZ support
The patch adds timezone support [1] for datetime. For the purpose of compatibility we got constants for timezones from Tarantool [2]. The patch adds a script to generate the data according to the instructions [3]. 1. https://github.com/tarantool/tarantool/wiki/Datetime-Internals#timezone-support 2. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/timezones.h 3. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/gen-zone-abbrevs.pl#L35-L39 Closes #163
1 parent 65a6de4 commit e51a5b3

File tree

8 files changed

+1838
-68
lines changed

8 files changed

+1838
-68
lines changed
 

‎CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1111
### Added
1212

1313
- Optional msgpack.v5 usage (#124)
14+
- TZ support for datetime (#163)
1415

1516
### Changed
1617

‎Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ clean:
2424
deps: clean
2525
( cd ./queue; tarantoolctl rocks install queue 1.1.0 )
2626

27+
.PHONY: datetime-timezones
28+
datetime-timezones:
29+
(cd ./datetime; ./gen-timezones.sh)
30+
2731
.PHONY: format
2832
format:
2933
goimports -l -w .

‎datetime/datetime.go

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package datetime
1212
import (
1313
"encoding/binary"
1414
"fmt"
15+
"math"
1516
"time"
1617
)
1718

@@ -47,13 +48,11 @@ type datetime struct {
4748
// Nanoseconds, fractional part of seconds. Tarantool uses int32_t, see
4849
// a definition in src/lib/core/datetime.h.
4950
nsec int32
50-
// Timezone offset in minutes from UTC (not implemented in Tarantool,
51-
// see gh-163). Tarantool uses a int16_t type, see a structure
52-
// definition in src/lib/core/datetime.h.
51+
// Timezone offset in minutes from UTC. Tarantool uses a int16_t type,
52+
// see a structure definition in src/lib/core/datetime.h.
5353
tzOffset int16
54-
// Olson timezone id (not implemented in Tarantool, see gh-163).
55-
// Tarantool uses a int16_t type, see a structure definition in
56-
// src/lib/core/datetime.h.
54+
// Olson timezone id. Tarantool uses a int16_t type, see a structure
55+
// definition in src/lib/core/datetime.h.
5756
tzIndex int16
5857
}
5958

@@ -79,16 +78,40 @@ type Datetime struct {
7978
time time.Time
8079
}
8180

81+
const (
82+
noTimezoneZone = ""
83+
noTimezoneOffset = 0
84+
)
85+
86+
// NoTimezone allows to create a datetime without UTC timezone for
87+
// Tarantool. The problem is that Golang by default creates a time value with
88+
// UTC timezone. So it is a way to create a datetime without timezone.
89+
var NoTimezone = time.FixedZone(noTimezoneZone, noTimezoneOffset)
90+
8291
// NewDatetime returns a pointer to a new datetime.Datetime that contains a
83-
// specified time.Time. It may returns an error if the Time value is out of
84-
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z]
92+
// specified time.Time. It may return an error if the Time value is out of
93+
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] or
94+
// an invalid timezone or offset value is out of supported range:
95+
// [math.MinInt16, math.MaxInt16]
8596
func NewDatetime(t time.Time) (*Datetime, error) {
8697
seconds := t.Unix()
8798

8899
if seconds < minSeconds || seconds > maxSeconds {
89100
return nil, fmt.Errorf("Time %s is out of supported range.", t)
90101
}
91102

103+
zone, offset := t.Zone()
104+
if zone != noTimezoneZone {
105+
if _, ok := timezoneToIndex[zone]; !ok {
106+
return nil, fmt.Errorf("Unknown timezone %s with offset %d",
107+
zone, offset)
108+
}
109+
}
110+
offset /= 60
111+
if offset < math.MinInt16 || offset > math.MaxInt16 {
112+
return nil, fmt.Errorf("Offset must be between %d and %d", math.MinInt16, math.MaxInt16)
113+
}
114+
92115
dt := new(Datetime)
93116
dt.time = t
94117
return dt, nil
@@ -105,8 +128,12 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
105128
var dt datetime
106129
dt.seconds = tm.Unix()
107130
dt.nsec = int32(tm.Nanosecond())
108-
dt.tzIndex = 0 // It is not implemented, see gh-163.
109-
dt.tzOffset = 0 // It is not implemented, see gh-163.
131+
132+
zone, offset := tm.Zone()
133+
if zone != noTimezoneZone {
134+
dt.tzIndex = int16(timezoneToIndex[zone])
135+
}
136+
dt.tzOffset = int16(offset / 60)
110137

111138
var bytesSize = secondsSize
112139
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 {
@@ -127,7 +154,7 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
127154
func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
128155
l := len(b)
129156
if l != maxSize && l != secondsSize {
130-
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
157+
return fmt.Errorf("Invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
131158
}
132159

133160
var dt datetime
@@ -140,7 +167,23 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
140167
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
141168
}
142169

143-
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
170+
tt := time.Unix(dt.seconds, int64(dt.nsec))
171+
172+
loc := NoTimezone
173+
if dt.tzIndex != 0 || dt.tzOffset != 0 {
174+
zone := noTimezoneZone
175+
offset := int(dt.tzOffset) * 60
176+
177+
if dt.tzIndex != 0 {
178+
if _, ok := indexToTimezone[int(dt.tzIndex)]; !ok {
179+
return fmt.Errorf("Unknown timezone index %d", dt.tzIndex)
180+
}
181+
zone = indexToTimezone[int(dt.tzIndex)]
182+
}
183+
loc = time.FixedZone(zone, offset)
184+
}
185+
tt = tt.In(loc)
186+
144187
dtp, err := NewDatetime(tt)
145188
if dtp != nil {
146189
*tm = *dtp

‎datetime/datetime_test.go

Lines changed: 214 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/hex"
55
"fmt"
66
"log"
7+
"math"
78
"os"
89
"reflect"
910
"testing"
@@ -73,6 +74,125 @@ func assertDatetimeIsEqual(t *testing.T, tuples []interface{}, tm time.Time) {
7374
}
7475
}
7576

77+
func TestTimezonesIndexMapping(t *testing.T) {
78+
for _, index := range TimezoneToIndex {
79+
if _, ok := IndexToTimezone[index]; !ok {
80+
t.Errorf("index %d not found", index)
81+
}
82+
}
83+
}
84+
85+
func TestTimezonesZonesMapping(t *testing.T) {
86+
for _, zone := range IndexToTimezone {
87+
if _, ok := TimezoneToIndex[zone]; !ok {
88+
t.Errorf("zone %s not found", zone)
89+
}
90+
}
91+
}
92+
93+
func TestInvalidTimezone(t *testing.T) {
94+
invalidLoc := time.FixedZone("AnyInvalid", 0)
95+
tm, err := time.Parse(time.RFC3339, "2010-08-12T11:39:14Z")
96+
if err != nil {
97+
t.Fatalf("Time parse failed: %s", err)
98+
}
99+
tm = tm.In(invalidLoc)
100+
dt, err := NewDatetime(tm)
101+
if err == nil {
102+
t.Fatalf("Unexpected success: %v", dt)
103+
}
104+
}
105+
106+
func TestInvalidOffset(t *testing.T) {
107+
tests := []struct {
108+
ok bool
109+
offset int
110+
}{
111+
{ok: true, offset: math.MinInt16 * 60},
112+
{ok: true, offset: (math.MinInt16 + 1) * 60},
113+
{ok: true, offset: (math.MaxInt16 - 1) * 60},
114+
{ok: true, offset: math.MaxInt16 * 60},
115+
{ok: false, offset: (math.MinInt16 - 1) * 60},
116+
{ok: false, offset: (math.MaxInt16 + 1) * 60},
117+
}
118+
119+
for _, testcase := range tests {
120+
name := ""
121+
if testcase.ok {
122+
name = fmt.Sprintf("in_boundary_%d", testcase.offset)
123+
} else {
124+
name = fmt.Sprintf("out_of_boundary_%d", testcase.offset)
125+
}
126+
t.Run(name, func(t *testing.T) {
127+
loc := time.FixedZone("MSK", testcase.offset)
128+
tm, err := time.Parse(time.RFC3339, "2010-08-12T11:39:14Z")
129+
if err != nil {
130+
t.Fatalf("Time parse failed: %s", err)
131+
}
132+
tm = tm.In(loc)
133+
dt, err := NewDatetime(tm)
134+
if testcase.ok && err != nil {
135+
t.Fatalf("Unexpected error: %s", err.Error())
136+
}
137+
if !testcase.ok && err == nil {
138+
t.Fatalf("Unexpected success: %v", dt)
139+
}
140+
if testcase.ok && isDatetimeSupported {
141+
conn := test_helpers.ConnectWithValidation(t, server, opts)
142+
defer conn.Close()
143+
144+
tupleInsertSelectDelete(t, conn, tm)
145+
}
146+
})
147+
}
148+
}
149+
150+
func TestCustomTimezone(t *testing.T) {
151+
skipIfDatetimeUnsupported(t)
152+
153+
conn := test_helpers.ConnectWithValidation(t, server, opts)
154+
defer conn.Close()
155+
156+
customZone := "Europe/Moscow"
157+
customOffset := 180 * 60
158+
159+
customLoc := time.FixedZone(customZone, customOffset)
160+
tm, err := time.Parse(time.RFC3339, "2010-08-12T11:44:14Z")
161+
if err != nil {
162+
t.Fatalf("Time parse failed: %s", err)
163+
}
164+
tm = tm.In(customLoc)
165+
dt, err := NewDatetime(tm)
166+
if err != nil {
167+
t.Fatalf("Unable to create datetime: %s", err.Error())
168+
}
169+
170+
resp, err := conn.Replace(spaceTuple1, []interface{}{dt, "payload"})
171+
if err != nil {
172+
t.Fatalf("Datetime replace failed %s", err.Error())
173+
}
174+
assertDatetimeIsEqual(t, resp.Data, tm)
175+
176+
tpl := resp.Data[0].([]interface{})
177+
if respDt, ok := toDatetime(tpl[0]); ok {
178+
zone, offset := respDt.ToTime().Zone()
179+
if zone != customZone {
180+
t.Fatalf("Expected zone %s instead of %s", customZone, zone)
181+
}
182+
if offset != customOffset {
183+
t.Fatalf("Expected offset %d instead of %d", customOffset, offset)
184+
}
185+
186+
_, err = conn.Delete(spaceTuple1, 0, []interface{}{dt})
187+
if err != nil {
188+
t.Fatalf("Datetime delete failed: %s", err.Error())
189+
}
190+
} else {
191+
t.Fatalf("datetime doesn't match")
192+
}
193+
194+
}
195+
76196
func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) {
77197
t.Helper()
78198

@@ -111,61 +231,65 @@ func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) {
111231
}
112232

113233
var datetimeSample = []struct {
234+
fmt string
114235
dt string
115236
mpBuf string // MessagePack buffer.
116237
}{
117-
{"2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"},
118-
{"1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"},
119-
{"2010-08-12T11:39:14Z", "d70462dd634c00000000"},
120-
{"1984-03-24T18:04:05Z", "d7041530c31a00000000"},
121-
{"2010-01-12T00:00:00Z", "d70480bb4b4b00000000"},
122-
{"1970-01-01T00:00:00Z", "d7040000000000000000"},
123-
{"1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"},
124-
{"1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"},
125-
{"1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"},
126-
{"1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"},
127-
{"1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"},
128-
{"1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"},
129-
{"1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"},
130-
{"1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"},
131-
{"1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"},
132-
{"1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"},
133-
{"1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"},
134-
{"1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"},
135-
{"1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"},
136-
{"1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"},
137-
{"1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"},
138-
{"1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"},
139-
{"1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"},
140-
{"1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"},
141-
{"1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"},
142-
{"1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"},
143-
{"1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"},
144-
{"1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"},
145-
{"1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"},
146-
{"1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"},
147-
{"1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"},
148-
{"1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"},
149-
{"1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"},
150-
{"1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"},
151-
{"1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"},
152-
{"1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"},
153-
{"1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"},
154-
{"1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"},
155-
{"1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"},
156-
{"1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"},
157-
{"1970-01-01T00:00:00.0Z", "d7040000000000000000"},
158-
{"1970-01-01T00:00:00.00Z", "d7040000000000000000"},
159-
{"1970-01-01T00:00:00.000Z", "d7040000000000000000"},
160-
{"1970-01-01T00:00:00.0000Z", "d7040000000000000000"},
161-
{"1970-01-01T00:00:00.00000Z", "d7040000000000000000"},
162-
{"1970-01-01T00:00:00.000000Z", "d7040000000000000000"},
163-
{"1970-01-01T00:00:00.0000000Z", "d7040000000000000000"},
164-
{"1970-01-01T00:00:00.00000000Z", "d7040000000000000000"},
165-
{"1970-01-01T00:00:00.000000000Z", "d7040000000000000000"},
166-
{"1973-11-29T21:33:09Z", "d70415cd5b0700000000"},
167-
{"2013-10-28T17:51:56Z", "d7043ca46e5200000000"},
168-
{"9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"},
238+
/* Cases for base encoding without a timezone. */
239+
{time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"},
240+
{time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"},
241+
{time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000"},
242+
{time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000"},
243+
{time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000"},
244+
{time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000"},
245+
{time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"},
246+
{time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"},
247+
{time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"},
248+
{time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"},
249+
{time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"},
250+
{time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"},
251+
{time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"},
252+
{time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"},
253+
{time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"},
254+
{time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"},
255+
{time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"},
256+
{time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"},
257+
{time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"},
258+
{time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"},
259+
{time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"},
260+
{time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"},
261+
{time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"},
262+
{time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"},
263+
{time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"},
264+
{time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"},
265+
{time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"},
266+
{time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"},
267+
{time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"},
268+
{time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"},
269+
{time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"},
270+
{time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"},
271+
{time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"},
272+
{time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"},
273+
{time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"},
274+
{time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"},
275+
{time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"},
276+
{time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"},
277+
{time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"},
278+
{time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"},
279+
{time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000"},
280+
{time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000"},
281+
{time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000"},
282+
{time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000"},
283+
{time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000"},
284+
{time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000"},
285+
{time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000"},
286+
{time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000"},
287+
{time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000"},
288+
{time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000"},
289+
{time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000"},
290+
{time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"},
291+
/* Cases for encoding with a timezone. */
292+
{time.RFC3339 + " MST", "2006-01-02T15:04:00+03:00 MSK", "d804b016b9430000000000000000b400ee00"},
169293
}
170294

171295
func TestDatetimeInsertSelectDelete(t *testing.T) {
@@ -176,7 +300,10 @@ func TestDatetimeInsertSelectDelete(t *testing.T) {
176300

177301
for _, testcase := range datetimeSample {
178302
t.Run(testcase.dt, func(t *testing.T) {
179-
tm, err := time.Parse(time.RFC3339, testcase.dt)
303+
tm, err := time.Parse(testcase.fmt, testcase.dt)
304+
if testcase.fmt == time.RFC3339 {
305+
tm = tm.In(NoTimezone)
306+
}
180307
if err != nil {
181308
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
182309
}
@@ -519,7 +646,10 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) {
519646
func TestMPEncode(t *testing.T) {
520647
for _, testcase := range datetimeSample {
521648
t.Run(testcase.dt, func(t *testing.T) {
522-
tm, err := time.Parse(time.RFC3339, testcase.dt)
649+
tm, err := time.Parse(testcase.fmt, testcase.dt)
650+
if testcase.fmt == time.RFC3339 {
651+
tm = tm.In(NoTimezone)
652+
}
523653
if err != nil {
524654
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
525655
}
@@ -533,7 +663,7 @@ func TestMPEncode(t *testing.T) {
533663
}
534664
refBuf, _ := hex.DecodeString(testcase.mpBuf)
535665
if reflect.DeepEqual(buf, refBuf) != true {
536-
t.Fatalf("Failed to encode datetime '%s', actual %v, expected %v",
666+
t.Fatalf("Failed to encode datetime '%s', actual %x, expected %x",
537667
testcase.dt,
538668
buf,
539669
refBuf)
@@ -545,7 +675,10 @@ func TestMPEncode(t *testing.T) {
545675
func TestMPDecode(t *testing.T) {
546676
for _, testcase := range datetimeSample {
547677
t.Run(testcase.dt, func(t *testing.T) {
548-
tm, err := time.Parse(time.RFC3339, testcase.dt)
678+
tm, err := time.Parse(testcase.fmt, testcase.dt)
679+
if testcase.fmt == time.RFC3339 {
680+
tm = tm.In(NoTimezone)
681+
}
549682
if err != nil {
550683
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
551684
}
@@ -565,6 +698,31 @@ func TestMPDecode(t *testing.T) {
565698
}
566699
}
567700

701+
func TestUnmarshalMsgpackInvalidLength(t *testing.T) {
702+
var v Datetime
703+
704+
err := v.UnmarshalMsgpack([]byte{0x04})
705+
if err == nil {
706+
t.Fatalf("Unexpected success %v.", v)
707+
}
708+
if err.Error() != "Invalid data length: got 1, wanted 8 or 16" {
709+
t.Fatalf("Unexpected error: %s", err.Error())
710+
}
711+
}
712+
713+
func TestUnmarshalMsgpackInvalidZone(t *testing.T) {
714+
var v Datetime
715+
716+
buf, _ := hex.DecodeString("b016b9430000000000000000b400ee01")
717+
err := v.UnmarshalMsgpack(buf)
718+
if err == nil {
719+
t.Fatalf("Unexpected success %v.", v)
720+
}
721+
if err.Error() != "Unknown timezone index 494" {
722+
t.Fatalf("Unexpected error: %s", err.Error())
723+
}
724+
}
725+
568726
// runTestMain is a body of TestMain function
569727
// (see https://pkg.go.dev/testing#hdr-Main).
570728
// Using defer + os.Exit is not works so TestMain body

‎datetime/example_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,24 @@ func Example() {
7979
fmt.Printf("Code: %d\n", resp.Code)
8080
fmt.Printf("Data: %v\n", respDt.ToTime())
8181
}
82+
83+
// Example demonstrates how to create a datetime for Tarantool without UTC
84+
// timezone in datetime.
85+
func ExampleNewDatetime_noTimezone() {
86+
var datetime = "2013-10-28T17:51:56.000000009Z"
87+
tm, err := time.Parse(time.RFC3339, datetime)
88+
if err != nil {
89+
fmt.Printf("error in time.Parse() is %v", err)
90+
return
91+
}
92+
93+
tm = tm.In(NoTimezone)
94+
95+
dt, err := NewDatetime(tm)
96+
if err != nil {
97+
fmt.Printf("Unable to create Datetime from %s: %s", tm, err)
98+
return
99+
}
100+
101+
fmt.Printf("Time value: %v\n", dt.ToTime())
102+
}

‎datetime/export_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package datetime
2+
3+
/* It's kind of an integration test data from an external data source. */
4+
var IndexToTimezone = indexToTimezone
5+
var TimezoneToIndex = timezoneToIndex

‎datetime/gen-timezones.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
set -xeuo pipefail
3+
4+
SRC_COMMIT="9ee45289e01232b8df1413efea11db170ae3b3b4"
5+
SRC_FILE=timezones.h
6+
DST_FILE=timezones.go
7+
8+
[ -e ${SRC_FILE} ] && rm ${SRC_FILE}
9+
wget -O ${SRC_FILE} \
10+
https://raw.githubusercontent.com/tarantool/tarantool/${SRC_COMMIT}/src/lib/tzcode/timezones.h
11+
12+
# We don't need aliases in indexToTimezone because Tarantool always replace it:
13+
#
14+
# tarantool> T = date.parse '2022-01-01T00:00 Pacific/Enderbury'
15+
# ---
16+
# ...
17+
# tarantool> T
18+
# ---
19+
# - 2022-01-01T00:00:00 Pacific/Kanton
20+
# ...
21+
#
22+
# So we can do the same and don't worry, be happy.
23+
24+
cat <<EOF > ${DST_FILE}
25+
package datetime
26+
27+
/* Automatically generated by gen-timezones.sh */
28+
29+
var indexToTimezone = map[int]string{
30+
EOF
31+
32+
grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \
33+
| awk '{printf("\t%s : %s,\n", $1, $3)}' >> ${DST_FILE}
34+
grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \
35+
| awk '{printf("\t%s : %s,\n", $1, $2)}' >> ${DST_FILE}
36+
37+
cat <<EOF >> ${DST_FILE}
38+
}
39+
40+
var timezoneToIndex = map[string]int{
41+
EOF
42+
43+
grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \
44+
| awk '{printf("\t%s : %s,\n", $3, $1)}' >> ${DST_FILE}
45+
grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \
46+
| awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE}
47+
grep ZONE_ALIAS ${SRC_FILE} | sed "s/ZONE_ALIAS( *//g" | sed "s/[),]//g" \
48+
| awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE}
49+
50+
echo "}" >> ${DST_FILE}
51+
52+
rm timezones.h
53+
54+
gofmt -s -w ${DST_FILE}

‎datetime/timezones.go

Lines changed: 1484 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.