Skip to content

Commit 9b0ec8a

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 72d7457 commit 9b0ec8a

File tree

3 files changed

+93
-29
lines changed

3 files changed

+93
-29
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: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,22 @@ 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 lesserBoundaryTimes = []time.Time{
19+
time.Date(-5879610, 06, 22, 0, 0, 1, 0, time.UTC),
20+
time.Date(-5879610, 06, 22, 0, 0, 0, 1, time.UTC),
21+
time.Date(5879611, 07, 10, 23, 59, 59, 0, time.UTC),
22+
time.Date(5879611, 07, 10, 23, 59, 59, 999999999, time.UTC),
23+
}
24+
25+
var boundaryTimes = []time.Time{
26+
time.Date(-5879610, 06, 22, 0, 0, 0, 0, time.UTC),
27+
time.Date(5879611, 07, 11, 0, 0, 0, 999999999, time.UTC),
28+
}
29+
30+
var greaterBoundaryTimes = []time.Time{
31+
time.Date(-5879610, 06, 21, 23, 59, 59, 999999999, time.UTC),
32+
time.Date(5879611, 07, 11, 0, 0, 1, 0, time.UTC),
33+
}
2234

2335
var isDatetimeSupported = false
2436

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

6577
func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) {
66-
dt := NewDatetime(tm)
78+
t.Helper()
79+
80+
dt, err := NewDatetime(tm)
81+
if err != nil {
82+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
83+
}
6784

6885
// Insert tuple with datetime.
69-
_, err := conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
86+
_, err = conn.Insert(spaceTuple1, []interface{}{dt, "payload"})
7087
if err != nil {
7188
t.Fatalf("Datetime insert failed: %s", err.Error())
7289
}
@@ -172,22 +189,30 @@ func TestDatetimeInsertSelectDelete(t *testing.T) {
172189
// time.Parse() could not parse formatted string with datetime where year is
173190
// bigger than 9999. That's why testcase with maximum datetime value represented
174191
// as a separate testcase. Testcase with minimal value added for consistency.
175-
func TestDatetimeMax(t *testing.T) {
192+
func TestDatetimeBoundaryRange(t *testing.T) {
176193
skipIfDatetimeUnsupported(t)
177194

178195
conn := test_helpers.ConnectWithValidation(t, server, opts)
179196
defer conn.Close()
180197

181-
tupleInsertSelectDelete(t, conn, maxTime)
198+
for _, tm := range append(lesserBoundaryTimes, boundaryTimes...) {
199+
t.Run(tm.String(), func(t *testing.T) {
200+
tupleInsertSelectDelete(t, conn, tm)
201+
})
202+
}
182203
}
183204

184-
func TestDatetimeMin(t *testing.T) {
205+
func TestDatetimeOutOfRange(t *testing.T) {
185206
skipIfDatetimeUnsupported(t)
186207

187-
conn := test_helpers.ConnectWithValidation(t, server, opts)
188-
defer conn.Close()
189-
190-
tupleInsertSelectDelete(t, conn, minTime)
208+
for _, tm := range greaterBoundaryTimes {
209+
t.Run(tm.String(), func(t *testing.T) {
210+
_, err := NewDatetime(tm)
211+
if err == nil {
212+
t.Errorf("Time %s should be unsupported!", tm)
213+
}
214+
})
215+
}
191216
}
192217

193218
func TestDatetimeReplace(t *testing.T) {
@@ -201,7 +226,10 @@ func TestDatetimeReplace(t *testing.T) {
201226
t.Fatalf("Time parse failed: %s", err)
202227
}
203228

204-
dt := NewDatetime(tm)
229+
dt, err := NewDatetime(tm)
230+
if err != nil {
231+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
232+
}
205233
resp, err := conn.Replace(spaceTuple1, []interface{}{dt, "payload"})
206234
if err != nil {
207235
t.Fatalf("Datetime replace failed: %s", err)
@@ -346,16 +374,24 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
346374
conn := test_helpers.ConnectWithValidation(t, server, opts)
347375
defer conn.Close()
348376

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

354390
tuple := Tuple2{Cid: cid,
355391
Orig: orig,
356392
Events: []Event{
357-
{*NewDatetime(dt1), "Minsk"},
358-
{*NewDatetime(dt2), "Moscow"},
393+
{*dt1, "Minsk"},
394+
{*dt2, "Moscow"},
359395
},
360396
}
361397
resp, err := conn.Replace(spaceTuple2, &tuple)
@@ -392,7 +428,7 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) {
392428
t.Fatalf("Unable to convert 2 field to []interface{}")
393429
}
394430

395-
for i, tv := range []time.Time{dt1, dt2} {
431+
for i, tv := range []time.Time{tm1, tm2} {
396432
dt := events[i].([]interface{})[1].(Datetime)
397433
if !dt.ToTime().Equal(tv) {
398434
t.Fatalf("%v != %v", dt.ToTime(), tv)
@@ -450,8 +486,11 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) {
450486
defer conn.Close()
451487

452488
tm := time.Unix(500, 1000)
453-
dt := NewDatetime(tm)
454-
_, err := conn.Insert(spaceTuple1, []interface{}{dt})
489+
dt, err := NewDatetime(tm)
490+
if err != nil {
491+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
492+
}
493+
_, err = conn.Insert(spaceTuple1, []interface{}{dt})
455494
if err != nil {
456495
t.Fatalf("Datetime insert failed: %s", err.Error())
457496
}
@@ -482,7 +521,10 @@ func TestMPEncode(t *testing.T) {
482521
if err != nil {
483522
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
484523
}
485-
dt := NewDatetime(tm)
524+
dt, err := NewDatetime(tm)
525+
if err != nil {
526+
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
527+
}
486528
buf, err := msgpack.Marshal(dt)
487529
if err != nil {
488530
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)