diff --git a/CHANGELOG.md b/CHANGELOG.md index a058a81ae..f2636e12a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. correctly (#213) - Decimal package use a test function GetNumberLength instead of a package-level function getNumberLength (#219) +- Datetime location after encode + decode is unequal (#217) ## [1.8.0] - 2022-08-17 diff --git a/datetime/datetime.go b/datetime/datetime.go index cf3ac8758..0fe20f572 100644 --- a/datetime/datetime.go +++ b/datetime/datetime.go @@ -96,6 +96,9 @@ const ( // supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] or // an invalid timezone or offset value is out of supported range: // [-12 * 60 * 60, 14 * 60 * 60]. +// +// NOTE: Tarantool's datetime.tz value is picked from t.Location().String(). +// "Local" location is unsupported, see ExampleNewDatetime_localUnsupported. func NewDatetime(t time.Time) (*Datetime, error) { seconds := t.Unix() @@ -103,13 +106,15 @@ func NewDatetime(t time.Time) (*Datetime, error) { return nil, fmt.Errorf("time %s is out of supported range", t) } - zone, offset := t.Zone() + zone := t.Location().String() + _, offset := t.Zone() if zone != NoTimezone { if _, ok := timezoneToIndex[zone]; !ok { return nil, fmt.Errorf("unknown timezone %s with offset %d", zone, offset) } } + if offset < offsetMin || offset > offsetMax { return nil, fmt.Errorf("offset must be between %d and %d hours", offsetMin, offsetMax) @@ -219,6 +224,13 @@ func (dtime *Datetime) Interval(next *Datetime) Interval { } // ToTime returns a time.Time that Datetime contains. +// +// If a Datetime created from time.Time value then an original location is used +// for the time value. +// +// If a Datetime created via unmarshaling Tarantool's datetime then we try to +// create a location with time.LoadLocation() first. In case of failure, we use +// a location created with time.FixedZone(). func (dtime *Datetime) ToTime() time.Time { return dtime.time } @@ -230,7 +242,8 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) { dt.seconds = tm.Unix() dt.nsec = int32(tm.Nanosecond()) - zone, offset := tm.Zone() + zone := tm.Location().String() + _, offset := tm.Zone() if zone != NoTimezone { // The zone value already checked in NewDatetime() or // UnmarshalMsgpack() calls. @@ -283,7 +296,17 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error { } zone = indexToTimezone[int(dt.tzIndex)] } - loc = time.FixedZone(zone, offset) + if zone != NoTimezone { + if loadLoc, err := time.LoadLocation(zone); err == nil { + loc = loadLoc + } else { + // Unable to load location. + loc = time.FixedZone(zone, offset) + } + } else { + // Only offset. + loc = time.FixedZone(zone, offset) + } } tt = tt.In(loc) diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index c128a82d0..60afa4077 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -507,6 +507,9 @@ func TestCustomTimezone(t *testing.T) { customZone := "Europe/Moscow" customOffset := 180 * 60 + // Tarantool does not use a custom offset value if a time zone is provided. + // So it will change to an actual one. + zoneOffset := 240 * 60 customLoc := time.FixedZone(customZone, customOffset) tm, err := time.Parse(time.RFC3339, "2010-08-12T11:44:14Z") @@ -527,11 +530,12 @@ func TestCustomTimezone(t *testing.T) { tpl := resp.Data[0].([]interface{}) if respDt, ok := toDatetime(tpl[0]); ok { - zone, offset := respDt.ToTime().Zone() + zone := respDt.ToTime().Location().String() + _, offset := respDt.ToTime().Zone() if zone != customZone { t.Fatalf("Expected zone %s instead of %s", customZone, zone) } - if offset != customOffset { + if offset != zoneOffset { t.Fatalf("Expected offset %d instead of %d", customOffset, offset) } @@ -586,62 +590,63 @@ var datetimeSample = []struct { fmt string dt string mpBuf string // MessagePack buffer. + zone string }{ /* Cases for base encoding without a timezone. */ - {time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"}, - {time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000"}, - {time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000"}, - {time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"}, - {time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"}, - {time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"}, - {time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"}, - {time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000"}, - {time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000"}, - {time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000"}, - {time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000"}, - {time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"}, + {time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000", ""}, + {time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000", ""}, + {time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000", ""}, + {time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000", ""}, + {time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000", ""}, + {time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000", ""}, + {time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000", ""}, /* Cases for encoding with a timezone. */ - {time.RFC3339 + " MST", "2006-01-02T15:04:00+03:00 MSK", "d804b016b9430000000000000000b400ee00"}, + {time.RFC3339, "2006-01-02T15:04:00Z", "d804e040b9430000000000000000b400b303", "Europe/Moscow"}, } func TestDatetimeInsertSelectDelete(t *testing.T) { @@ -653,8 +658,14 @@ func TestDatetimeInsertSelectDelete(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { tm, err := time.Parse(testcase.fmt, testcase.dt) - if testcase.fmt == time.RFC3339 { + if testcase.zone == "" { tm = tm.In(noTimezoneLoc) + } else { + loc, err := time.LoadLocation(testcase.zone) + if err != nil { + t.Fatalf("Unable to load location: %s", err) + } + tm = tm.In(loc) } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) @@ -966,7 +977,7 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, server, opts) defer conn.Close() - tm := time.Unix(500, 1000) + tm := time.Unix(500, 1000).In(time.FixedZone(NoTimezone, 0)) dt, err := NewDatetime(tm) if err != nil { t.Fatalf("Unable to create Datetime from %s: %s", tm, err) @@ -999,8 +1010,14 @@ func TestMPEncode(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { tm, err := time.Parse(testcase.fmt, testcase.dt) - if testcase.fmt == time.RFC3339 { + if testcase.zone == "" { tm = tm.In(noTimezoneLoc) + } else { + loc, err := time.LoadLocation(testcase.zone) + if err != nil { + t.Fatalf("Unable to load location: %s", err) + } + tm = tm.In(loc) } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) @@ -1016,7 +1033,7 @@ func TestMPEncode(t *testing.T) { refBuf, _ := hex.DecodeString(testcase.mpBuf) if reflect.DeepEqual(buf, refBuf) != true { t.Fatalf("Failed to encode datetime '%s', actual %x, expected %x", - testcase.dt, + tm, buf, refBuf) } @@ -1028,8 +1045,14 @@ func TestMPDecode(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { tm, err := time.Parse(testcase.fmt, testcase.dt) - if testcase.fmt == time.RFC3339 { + if testcase.zone == "" { tm = tm.In(noTimezoneLoc) + } else { + loc, err := time.LoadLocation(testcase.zone) + if err != nil { + t.Fatalf("Unable to load location: %s", err) + } + tm = tm.In(loc) } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) diff --git a/datetime/example_test.go b/datetime/example_test.go index ca1603ea8..50725e6a3 100644 --- a/datetime/example_test.go +++ b/datetime/example_test.go @@ -80,6 +80,22 @@ func Example() { fmt.Printf("Data: %v\n", respDt.ToTime()) } +// ExampleNewDatetime_localUnsupported demonstrates that "Local" location is +// unsupported. +func ExampleNewDatetime_localUnsupported() { + tm := time.Now().Local() + loc := tm.Location() + fmt.Println("Location:", loc) + if _, err := NewDatetime(tm); err != nil { + fmt.Printf("Could not create a Datetime with %s location.\n", loc) + } else { + fmt.Printf("A Datetime with %s location created.\n", loc) + } + // Output: + // Location: Local + // Could not create a Datetime with Local location. +} + // Example demonstrates how to create a datetime for Tarantool without UTC // timezone in datetime. func ExampleNewDatetime_noTimezone() { @@ -165,6 +181,42 @@ func ExampleDatetime_Add() { // New time: 2014-02-28 17:57:29.000000009 +0000 UTC } +// ExampleDatetime_Add_dst demonstrates how to add an Interval to a +// Datetime value with a DST location. +func ExampleDatetime_Add_dst() { + loc, err := time.LoadLocation("Europe/Moscow") + if err != nil { + fmt.Printf("Unable to load location: %s", err) + return + } + tm := time.Date(2008, 1, 1, 1, 1, 1, 1, loc) + dt, err := NewDatetime(tm) + if err != nil { + fmt.Printf("Unable to create Datetime: %s", err) + return + } + + fmt.Printf("Datetime time:\n") + fmt.Printf("%s\n", dt.ToTime()) + fmt.Printf("Datetime time + 6 month:\n") + fmt.Printf("%s\n", dt.ToTime().AddDate(0, 6, 0)) + dt, err = dt.Add(Interval{Month: 6}) + if err != nil { + fmt.Printf("Unable to add 6 month: %s", err) + return + } + fmt.Printf("Datetime + 6 month time:\n") + fmt.Printf("%s\n", dt.ToTime()) + + // Output: + // Datetime time: + // 2008-01-01 01:01:01.000000001 +0300 MSK + // Datetime time + 6 month: + // 2008-07-01 01:01:01.000000001 +0400 MSD + // Datetime + 6 month time: + // 2008-07-01 01:01:01.000000001 +0400 MSD +} + // ExampleDatetime_Sub demonstrates how to subtract an Interval from a // Datetime value. func ExampleDatetime_Sub() {