Skip to content

Commit f4c64a0

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 3b98329 commit f4c64a0

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 tooBigTimes = []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

@@ -76,10 +83,15 @@ func assertDatetimeIsEqual(t *testing.T, tuples []interface{}, tm time.Time) {
7683
}
7784

7885
func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) {
79-
dt := NewDatetime(tm)
86+
t.Helper()
87+
88+
dt, err := NewDatetime(tm)
89+
if err != nil {
90+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
91+
}
8092

8193
// Insert tuple with datetime.
82-
_, err := conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
94+
_, err = conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
8395
if err != nil {
8496
t.Fatalf("Datetime insert failed: %s", err.Error())
8597
}
@@ -191,16 +203,27 @@ func TestDatetimeMax(t *testing.T) {
191203
conn := connectWithValidation(t)
192204
defer conn.Close()
193205

194-
tupleInsertSelectDelete(t, conn, maxTime)
206+
for _, tm := range maxTimes {
207+
t.Run(tm.String(), func(t *testing.T) {
208+
tupleInsertSelectDelete(t, conn, tm)
209+
})
210+
}
195211
}
196212

197-
func TestDatetimeMin(t *testing.T) {
213+
func TestDatetimeTooBig(t *testing.T) {
198214
skipIfDatetimeUnsupported(t)
199215

200216
conn := connectWithValidation(t)
201217
defer conn.Close()
202218

203-
tupleInsertSelectDelete(t, conn, minTime)
219+
for _, tm := range tooBigTimes {
220+
t.Run(tm.String(), func(t *testing.T) {
221+
_, err := NewDatetime(tm)
222+
if err == nil {
223+
t.Errorf("Time %s should be unsupported!", tm)
224+
}
225+
})
226+
}
204227
}
205228

206229
func TestDatetimeReplace(t *testing.T) {
@@ -214,7 +237,10 @@ func TestDatetimeReplace(t *testing.T) {
214237
t.Fatalf("Time parse failed: %s", err)
215238
}
216239

217-
dt := NewDatetime(tm)
240+
dt, err := NewDatetime(tm)
241+
if err != nil {
242+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
243+
}
218244
resp, err := conn.Replace(spaceTuple1, []interface{}{dt, "payload"})
219245
if err != nil {
220246
t.Fatalf("Datetime replace failed: %s", err)
@@ -359,16 +385,24 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
359385
conn := connectWithValidation(t)
360386
defer conn.Close()
361387

362-
dt1, _ := time.Parse(time.RFC3339, "2010-05-24T17:51:56.000000009Z")
363-
dt2, _ := time.Parse(time.RFC3339, "2022-05-24T17:51:56.000000009Z")
388+
tm1, _ := time.Parse(time.RFC3339, "2010-05-24T17:51:56.000000009Z")
389+
tm2, _ := time.Parse(time.RFC3339, "2022-05-24T17:51:56.000000009Z")
390+
dt1, err := NewDatetime(tm1)
391+
if err != nil {
392+
t.Fatalf("Unable to create Datetime from %s: %s", tm1, err)
393+
}
394+
dt2, err := NewDatetime(tm2)
395+
if err != nil {
396+
t.Fatalf("Unable to create Datetime from %s: %s", tm2, err)
397+
}
364398
const cid = 13
365399
const orig = "orig"
366400

367401
tuple := Tuple2{Cid: cid,
368402
Orig: orig,
369403
Events: []Event{
370-
{*NewDatetime(dt1), "Minsk"},
371-
{*NewDatetime(dt2), "Moscow"},
404+
{*dt1, "Minsk"},
405+
{*dt2, "Moscow"},
372406
},
373407
}
374408
resp, err := conn.Replace(spaceTuple2, &tuple)
@@ -405,7 +439,7 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
405439
t.Fatalf("Unable to convert 2 field to []interface{}")
406440
}
407441

408-
for i, tv := range []time.Time{dt1, dt2} {
442+
for i, tv := range []time.Time{tm1, tm2} {
409443
dt := events[i].([]interface{})[1].(Datetime)
410444
if !dt.ToTime().Equal(tv) {
411445
t.Fatalf("%v != %v", dt.ToTime(), tv)
@@ -463,8 +497,11 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) {
463497
defer conn.Close()
464498

465499
tm := time.Unix(500, 1000)
466-
dt := NewDatetime(tm)
467-
_, err := conn.Insert(spaceTuple1, []interface{}{dt})
500+
dt, err := NewDatetime(tm)
501+
if err != nil {
502+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
503+
}
504+
_, err = conn.Insert(spaceTuple1, []interface{}{dt})
468505
if err != nil {
469506
t.Fatalf("Datetime insert failed: %s", err.Error())
470507
}
@@ -495,7 +532,10 @@ func TestMPEncode(t *testing.T) {
495532
if err != nil {
496533
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
497534
}
498-
dt := NewDatetime(tm)
535+
dt, err := NewDatetime(tm)
536+
if err != nil {
537+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
538+
}
499539
buf, err := msgpack.Marshal(dt)
500540
if err != nil {
501541
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)