Skip to content

Commit acdc47c

Browse files
committed
datetime: sync datetime range with Tarantool
The patch adds check for supported Datetime values. The supported range comes from Tarantool implementation [1] and c-dt library [2]. 1. https://github.com/tarantool/tarantool/blob/a99ccce5f517d2a04670289d3d09a8cc2f5916f9/src/lib/core/datetime.h#L44-L61 2. https://github.com/tarantool/c-dt/blob/e6214325fe8d4336464ebae859ac2b456fd22b77/API.pod#introduction Closes #191
1 parent d04f8be commit acdc47c

File tree

3 files changed

+87
-25
lines changed

3 files changed

+87
-25
lines changed

datetime/datetime.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,33 @@ const (
6767
tzOffsetSize = 2
6868
)
6969

70+
// Limits are from c-dt library:
71+
// https://github.com/tarantool/c-dt/blob/e6214325fe8d4336464ebae859ac2b456fd22b77/API.pod#introduction
72+
// https://github.com/tarantool/tarantool/blob/a99ccce5f517d2a04670289d3d09a8cc2f5916f9/src/lib/core/datetime.h#L44-L61
73+
const (
74+
minSeconds = -185604722870400
75+
maxSeconds = 185480451417600
76+
)
77+
7078
const maxSize = secondsSize + nsecSize + tzIndexSize + tzOffsetSize
7179

7280
type Datetime struct {
7381
time time.Time
7482
}
7583

7684
// NewDatetime returns a pointer to a new datetime.Datetime that contains a
77-
// specified time.Time.
78-
func NewDatetime(t time.Time) *Datetime {
85+
// specified time.Time. It may returns an error if the Time value is out of
86+
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z]
87+
func NewDatetime(t time.Time) (*Datetime, error) {
88+
seconds := t.Unix()
89+
90+
if seconds < minSeconds || seconds > maxSeconds {
91+
return nil, fmt.Errorf("Time %s is out of supported range.", t)
92+
}
93+
7994
dt := new(Datetime)
8095
dt.time = t
81-
return dt
96+
return dt, nil
8297
}
8398

8499
// ToTime returns a time.Time that Datetime contains.
@@ -129,10 +144,13 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
129144
dt.tzOffset = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize:]))
130145
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
131146
}
132-
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
133-
*tm = *NewDatetime(tt)
134147

135-
return nil
148+
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
149+
dtp, err := NewDatetime(tt)
150+
if dtp != nil {
151+
*tm = *dtp
152+
}
153+
return err
136154
}
137155

138156
func init() {

datetime/datetime_test.go

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ import (
1515
"gopkg.in/vmihailenco/msgpack.v2"
1616
)
1717

18-
var (
19-
minTime = time.Unix(0, 0)
20-
maxTime = time.Unix(1<<63-1, 999999999)
21-
)
18+
var maxTimes = []time.Time{
19+
time.Date(-5879610, 06, 22, 0, 0, 0, 0, time.UTC),
20+
time.Date(5879611, 07, 11, 0, 0, 0, 999999999, time.UTC),
21+
time.Date(-5879610, 06, 21, 23, 59, 59, 1000000000, time.UTC),
22+
}
23+
24+
var outOfRangeTimes = []time.Time{
25+
time.Date(-5879610, 06, 21, 23, 59, 59, 999999999, time.UTC),
26+
time.Date(5879611, 07, 11, 0, 0, 1, 0, time.UTC),
27+
time.Date(5879611, 07, 11, 0, 0, 0, 1000000000, time.UTC),
28+
}
2229

2330
var isDatetimeSupported = false
2431

@@ -63,10 +70,15 @@ func assertDatetimeIsEqual(t *testing.T, tuples []interface{}, tm time.Time) {
6370
}
6471

6572
func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) {
66-
dt := NewDatetime(tm)
73+
t.Helper()
74+
75+
dt, err := NewDatetime(tm)
76+
if err != nil {
77+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
78+
}
6779

6880
// Insert tuple with datetime.
69-
_, err := conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
81+
_, err = conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
7082
if err != nil {
7183
t.Fatalf("Datetime insert failed: %s", err.Error())
7284
}
@@ -178,16 +190,27 @@ func TestDatetimeMax(t *testing.T) {
178190
conn := test_helpers.ConnectWithValidation(t, server, opts)
179191
defer conn.Close()
180192

181-
tupleInsertSelectDelete(t, conn, maxTime)
193+
for _, tm := range maxTimes {
194+
t.Run(tm.String(), func(t *testing.T) {
195+
tupleInsertSelectDelete(t, conn, tm)
196+
})
197+
}
182198
}
183199

184-
func TestDatetimeMin(t *testing.T) {
200+
func TestDatetimeOutOfRange(t *testing.T) {
185201
skipIfDatetimeUnsupported(t)
186202

187203
conn := test_helpers.ConnectWithValidation(t, server, opts)
188204
defer conn.Close()
189205

190-
tupleInsertSelectDelete(t, conn, minTime)
206+
for _, tm := range outOfRangeTimes {
207+
t.Run(tm.String(), func(t *testing.T) {
208+
_, err := NewDatetime(tm)
209+
if err == nil {
210+
t.Errorf("Time %s should be unsupported!", tm)
211+
}
212+
})
213+
}
191214
}
192215

193216
func TestDatetimeReplace(t *testing.T) {
@@ -201,7 +224,10 @@ func TestDatetimeReplace(t *testing.T) {
201224
t.Fatalf("Time parse failed: %s", err)
202225
}
203226

204-
dt := NewDatetime(tm)
227+
dt, err := NewDatetime(tm)
228+
if err != nil {
229+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
230+
}
205231
resp, err := conn.Replace(spaceTuple1, []interface{}{dt, "payload"})
206232
if err != nil {
207233
t.Fatalf("Datetime replace failed: %s", err)
@@ -346,16 +372,24 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
346372
conn := test_helpers.ConnectWithValidation(t, server, opts)
347373
defer conn.Close()
348374

349-
dt1, _ := time.Parse(time.RFC3339, "2010-05-24T17:51:56.000000009Z")
350-
dt2, _ := time.Parse(time.RFC3339, "2022-05-24T17:51:56.000000009Z")
375+
tm1, _ := time.Parse(time.RFC3339, "2010-05-24T17:51:56.000000009Z")
376+
tm2, _ := time.Parse(time.RFC3339, "2022-05-24T17:51:56.000000009Z")
377+
dt1, err := NewDatetime(tm1)
378+
if err != nil {
379+
t.Fatalf("Unable to create Datetime from %s: %s", tm1, err)
380+
}
381+
dt2, err := NewDatetime(tm2)
382+
if err != nil {
383+
t.Fatalf("Unable to create Datetime from %s: %s", tm2, err)
384+
}
351385
const cid = 13
352386
const orig = "orig"
353387

354388
tuple := Tuple2{Cid: cid,
355389
Orig: orig,
356390
Events: []Event{
357-
{*NewDatetime(dt1), "Minsk"},
358-
{*NewDatetime(dt2), "Moscow"},
391+
{*dt1, "Minsk"},
392+
{*dt2, "Moscow"},
359393
},
360394
}
361395
resp, err := conn.Replace(spaceTuple2, &tuple)
@@ -392,7 +426,7 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
392426
t.Fatalf("Unable to convert 2 field to []interface{}")
393427
}
394428

395-
for i, tv := range []time.Time{dt1, dt2} {
429+
for i, tv := range []time.Time{tm1, tm2} {
396430
dt := events[i].([]interface{})[1].(Datetime)
397431
if !dt.ToTime().Equal(tv) {
398432
t.Fatalf("%v != %v", dt.ToTime(), tv)
@@ -450,8 +484,11 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) {
450484
defer conn.Close()
451485

452486
tm := time.Unix(500, 1000)
453-
dt := NewDatetime(tm)
454-
_, err := conn.Insert(spaceTuple1, []interface{}{dt})
487+
dt, err := NewDatetime(tm)
488+
if err != nil {
489+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
490+
}
491+
_, err = conn.Insert(spaceTuple1, []interface{}{dt})
455492
if err != nil {
456493
t.Fatalf("Datetime insert failed: %s", err.Error())
457494
}
@@ -482,7 +519,10 @@ func TestMPEncode(t *testing.T) {
482519
if err != nil {
483520
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
484521
}
485-
dt := NewDatetime(tm)
522+
dt, err := NewDatetime(tm)
523+
if err != nil {
524+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
525+
}
486526
buf, err := msgpack.Marshal(dt)
487527
if err != nil {
488528
t.Fatalf("Marshalling failed: %s", err.Error())

datetime/example_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ func Example() {
3535
fmt.Printf("error in time.Parse() is %v", err)
3636
return
3737
}
38-
dt := NewDatetime(tm)
38+
dt, err := NewDatetime(tm)
39+
if err != nil {
40+
fmt.Printf("Unable to create Datetime from %s: %s", tm, err)
41+
return
42+
}
3943

4044
space := "testDatetime_1"
4145
index := "primary"

0 commit comments

Comments
 (0)