Skip to content

Commit 7099bee

Browse files
committed
bugfix: unequal timezone after decoding
The patch fixes unequal timezones after Datetime encoding/decoding. It does two things for the fix: 1. After the patch, a location name is picked from Time.Location().String() instead of Time.Zone(). It allows us to encode a timezone from the original name. 2. A decoder function tries to load a location from system location. It allows to handle unfixed timezones properly. Closes #217
1 parent d4905f5 commit 7099bee

File tree

4 files changed

+162
-63
lines changed

4 files changed

+162
-63
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2727
- A connection is still opened after ConnectionPool.Close() (#208)
2828
- Future.GetTyped() after Future.Get() does not decode response
2929
correctly (#213)
30+
- Datetime location after encode + decode is unequal (#217)
3031

3132
## [1.8.0] - 2022-08-17
3233

datetime/datetime.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,25 @@ const (
9696
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] or
9797
// an invalid timezone or offset value is out of supported range:
9898
// [-12 * 60 * 60, 14 * 60 * 60].
99+
//
100+
// NOTE: Tarantool's datetime.tz value is picked from t.Location().String().
101+
// "Local" location is unsupported, see ExampleNewDatetime_localUnsupported.
99102
func NewDatetime(t time.Time) (*Datetime, error) {
100103
seconds := t.Unix()
101104

102105
if seconds < minSeconds || seconds > maxSeconds {
103106
return nil, fmt.Errorf("time %s is out of supported range", t)
104107
}
105108

106-
zone, offset := t.Zone()
109+
zone := t.Location().String()
110+
_, offset := t.Zone()
107111
if zone != NoTimezone {
108112
if _, ok := timezoneToIndex[zone]; !ok {
109113
return nil, fmt.Errorf("unknown timezone %s with offset %d",
110114
zone, offset)
111115
}
112116
}
117+
113118
if offset < offsetMin || offset > offsetMax {
114119
return nil, fmt.Errorf("offset must be between %d and %d hours",
115120
offsetMin, offsetMax)
@@ -219,6 +224,13 @@ func (dtime *Datetime) Interval(next *Datetime) Interval {
219224
}
220225

221226
// ToTime returns a time.Time that Datetime contains.
227+
//
228+
// If a Datetime created from time.Time value then an original location is used
229+
// for the time value.
230+
//
231+
// If a Datetime created via unmarshaling Tarantool's datetime then we try to
232+
// create a location with time.LoadLocation() first. In case of failure, we use
233+
// a location created with time.FixedZone().
222234
func (dtime *Datetime) ToTime() time.Time {
223235
return dtime.time
224236
}
@@ -230,7 +242,8 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
230242
dt.seconds = tm.Unix()
231243
dt.nsec = int32(tm.Nanosecond())
232244

233-
zone, offset := tm.Zone()
245+
zone := tm.Location().String()
246+
_, offset := tm.Zone()
234247
if zone != NoTimezone {
235248
// The zone value already checked in NewDatetime() or
236249
// UnmarshalMsgpack() calls.
@@ -283,7 +296,17 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
283296
}
284297
zone = indexToTimezone[int(dt.tzIndex)]
285298
}
286-
loc = time.FixedZone(zone, offset)
299+
if zone != NoTimezone {
300+
if loadLoc, err := time.LoadLocation(zone); err == nil {
301+
loc = loadLoc
302+
} else {
303+
// Unable to load location.
304+
loc = time.FixedZone(zone, offset)
305+
}
306+
} else {
307+
// Only offset.
308+
loc = time.FixedZone(zone, offset)
309+
}
287310
}
288311
tt = tt.In(loc)
289312

datetime/datetime_test.go

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ func TestCustomTimezone(t *testing.T) {
507507

508508
customZone := "Europe/Moscow"
509509
customOffset := 180 * 60
510+
// Tarantool does not use a custom offset value if a time zone is provided.
511+
// So it will change to an actual one.
512+
zoneOffset := 240 * 60
510513

511514
customLoc := time.FixedZone(customZone, customOffset)
512515
tm, err := time.Parse(time.RFC3339, "2010-08-12T11:44:14Z")
@@ -527,11 +530,12 @@ func TestCustomTimezone(t *testing.T) {
527530

528531
tpl := resp.Data[0].([]interface{})
529532
if respDt, ok := toDatetime(tpl[0]); ok {
530-
zone, offset := respDt.ToTime().Zone()
533+
zone := respDt.ToTime().Location().String()
534+
_, offset := respDt.ToTime().Zone()
531535
if zone != customZone {
532536
t.Fatalf("Expected zone %s instead of %s", customZone, zone)
533537
}
534-
if offset != customOffset {
538+
if offset != zoneOffset {
535539
t.Fatalf("Expected offset %d instead of %d", customOffset, offset)
536540
}
537541

@@ -586,62 +590,63 @@ var datetimeSample = []struct {
586590
fmt string
587591
dt string
588592
mpBuf string // MessagePack buffer.
593+
zone string
589594
}{
590595
/* Cases for base encoding without a timezone. */
591-
{time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"},
592-
{time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"},
593-
{time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000"},
594-
{time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000"},
595-
{time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000"},
596-
{time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000"},
597-
{time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"},
598-
{time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"},
599-
{time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"},
600-
{time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"},
601-
{time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"},
602-
{time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"},
603-
{time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"},
604-
{time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"},
605-
{time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"},
606-
{time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"},
607-
{time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"},
608-
{time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"},
609-
{time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"},
610-
{time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"},
611-
{time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"},
612-
{time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"},
613-
{time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"},
614-
{time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"},
615-
{time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"},
616-
{time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"},
617-
{time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"},
618-
{time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"},
619-
{time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"},
620-
{time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"},
621-
{time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"},
622-
{time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"},
623-
{time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"},
624-
{time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"},
625-
{time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"},
626-
{time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"},
627-
{time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"},
628-
{time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"},
629-
{time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"},
630-
{time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"},
631-
{time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000"},
632-
{time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000"},
633-
{time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000"},
634-
{time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000"},
635-
{time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000"},
636-
{time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000"},
637-
{time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000"},
638-
{time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000"},
639-
{time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000"},
640-
{time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000"},
641-
{time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000"},
642-
{time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"},
596+
{time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000", ""},
597+
{time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000", ""},
598+
{time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000", ""},
599+
{time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000", ""},
600+
{time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000", ""},
601+
{time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000", ""},
602+
{time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000", ""},
603+
{time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000", ""},
604+
{time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000", ""},
605+
{time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000", ""},
606+
{time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000", ""},
607+
{time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000", ""},
608+
{time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000", ""},
609+
{time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000", ""},
610+
{time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000", ""},
611+
{time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000", ""},
612+
{time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000", ""},
613+
{time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000", ""},
614+
{time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000", ""},
615+
{time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000", ""},
616+
{time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000", ""},
617+
{time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000", ""},
618+
{time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000", ""},
619+
{time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000", ""},
620+
{time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000", ""},
621+
{time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000", ""},
622+
{time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000", ""},
623+
{time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000", ""},
624+
{time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000", ""},
625+
{time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000", ""},
626+
{time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000", ""},
627+
{time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000", ""},
628+
{time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000", ""},
629+
{time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000", ""},
630+
{time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000", ""},
631+
{time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000", ""},
632+
{time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000", ""},
633+
{time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000", ""},
634+
{time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000", ""},
635+
{time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000", ""},
636+
{time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000", ""},
637+
{time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000", ""},
638+
{time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000", ""},
639+
{time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000", ""},
640+
{time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000", ""},
641+
{time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000", ""},
642+
{time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000", ""},
643+
{time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000", ""},
644+
{time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000", ""},
645+
{time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000", ""},
646+
{time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000", ""},
647+
{time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000", ""},
643648
/* Cases for encoding with a timezone. */
644-
{time.RFC3339 + " MST", "2006-01-02T15:04:00+03:00 MSK", "d804b016b9430000000000000000b400ee00"},
649+
{time.RFC3339, "2006-01-02T15:04:00Z", "d804e040b9430000000000000000b400b303", "Europe/Moscow"},
645650
}
646651

647652
func TestDatetimeInsertSelectDelete(t *testing.T) {
@@ -653,8 +658,14 @@ func TestDatetimeInsertSelectDelete(t *testing.T) {
653658
for _, testcase := range datetimeSample {
654659
t.Run(testcase.dt, func(t *testing.T) {
655660
tm, err := time.Parse(testcase.fmt, testcase.dt)
656-
if testcase.fmt == time.RFC3339 {
661+
if testcase.zone == "" {
657662
tm = tm.In(noTimezoneLoc)
663+
} else {
664+
loc, err := time.LoadLocation(testcase.zone)
665+
if err != nil {
666+
t.Fatalf("Unable to load location: %s", err)
667+
}
668+
tm = tm.In(loc)
658669
}
659670
if err != nil {
660671
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
@@ -966,7 +977,7 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) {
966977
conn := test_helpers.ConnectWithValidation(t, server, opts)
967978
defer conn.Close()
968979

969-
tm := time.Unix(500, 1000)
980+
tm := time.Unix(500, 1000).In(time.FixedZone(NoTimezone, 0))
970981
dt, err := NewDatetime(tm)
971982
if err != nil {
972983
t.Fatalf("Unable to create Datetime from %s: %s", tm, err)
@@ -999,8 +1010,14 @@ func TestMPEncode(t *testing.T) {
9991010
for _, testcase := range datetimeSample {
10001011
t.Run(testcase.dt, func(t *testing.T) {
10011012
tm, err := time.Parse(testcase.fmt, testcase.dt)
1002-
if testcase.fmt == time.RFC3339 {
1013+
if testcase.zone == "" {
10031014
tm = tm.In(noTimezoneLoc)
1015+
} else {
1016+
loc, err := time.LoadLocation(testcase.zone)
1017+
if err != nil {
1018+
t.Fatalf("Unable to load location: %s", err)
1019+
}
1020+
tm = tm.In(loc)
10041021
}
10051022
if err != nil {
10061023
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)
@@ -1016,7 +1033,7 @@ func TestMPEncode(t *testing.T) {
10161033
refBuf, _ := hex.DecodeString(testcase.mpBuf)
10171034
if reflect.DeepEqual(buf, refBuf) != true {
10181035
t.Fatalf("Failed to encode datetime '%s', actual %x, expected %x",
1019-
testcase.dt,
1036+
tm,
10201037
buf,
10211038
refBuf)
10221039
}
@@ -1028,8 +1045,14 @@ func TestMPDecode(t *testing.T) {
10281045
for _, testcase := range datetimeSample {
10291046
t.Run(testcase.dt, func(t *testing.T) {
10301047
tm, err := time.Parse(testcase.fmt, testcase.dt)
1031-
if testcase.fmt == time.RFC3339 {
1048+
if testcase.zone == "" {
10321049
tm = tm.In(noTimezoneLoc)
1050+
} else {
1051+
loc, err := time.LoadLocation(testcase.zone)
1052+
if err != nil {
1053+
t.Fatalf("Unable to load location: %s", err)
1054+
}
1055+
tm = tm.In(loc)
10331056
}
10341057
if err != nil {
10351058
t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err)

datetime/example_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ func Example() {
8080
fmt.Printf("Data: %v\n", respDt.ToTime())
8181
}
8282

83+
// ExampleNewDatetime_localUnsupported demonstrates that "Local" location is
84+
// unsupported.
85+
func ExampleNewDatetime_localUnsupported() {
86+
tm := time.Now().Local()
87+
loc := tm.Location()
88+
fmt.Println("Location:", loc)
89+
if _, err := NewDatetime(tm); err != nil {
90+
fmt.Printf("Could not create a Datetime with %s location.\n", loc)
91+
} else {
92+
fmt.Printf("A Datetime with %s location created.\n", loc)
93+
}
94+
// Output:
95+
// Location: Local
96+
// Could not create a Datetime with Local location.
97+
}
98+
8399
// Example demonstrates how to create a datetime for Tarantool without UTC
84100
// timezone in datetime.
85101
func ExampleNewDatetime_noTimezone() {
@@ -165,6 +181,42 @@ func ExampleDatetime_Add() {
165181
// New time: 2014-02-28 17:57:29.000000009 +0000 UTC
166182
}
167183

184+
// ExampleDatetime_Add_dst demonstrates how to add an Interval to a
185+
// Datetime value with a DST location.
186+
func ExampleDatetime_Add_dst() {
187+
loc, err := time.LoadLocation("Europe/Moscow")
188+
if err != nil {
189+
fmt.Printf("Unable to load location: %s", err)
190+
return
191+
}
192+
tm := time.Date(2008, 1, 1, 1, 1, 1, 1, loc)
193+
dt, err := NewDatetime(tm)
194+
if err != nil {
195+
fmt.Printf("Unable to create Datetime: %s", err)
196+
return
197+
}
198+
199+
fmt.Printf("Datetime time:\n")
200+
fmt.Printf("%s\n", dt.ToTime())
201+
fmt.Printf("Datetime time + 6 month:\n")
202+
fmt.Printf("%s\n", dt.ToTime().AddDate(0, 6, 0))
203+
dt, err = dt.Add(Interval{Month: 6})
204+
if err != nil {
205+
fmt.Printf("Unable to add 6 month: %s", err)
206+
return
207+
}
208+
fmt.Printf("Datetime + 6 month time:\n")
209+
fmt.Printf("%s\n", dt.ToTime())
210+
211+
// Output:
212+
// Datetime time:
213+
// 2008-01-01 01:01:01.000000001 +0300 MSK
214+
// Datetime time + 6 month:
215+
// 2008-07-01 01:01:01.000000001 +0400 MSD
216+
// Datetime + 6 month time:
217+
// 2008-07-01 01:01:01.000000001 +0400 MSD
218+
}
219+
168220
// ExampleDatetime_Sub demonstrates how to subtract an Interval from a
169221
// Datetime value.
170222
func ExampleDatetime_Sub() {

0 commit comments

Comments
 (0)