Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 26 additions & 3 deletions datetime/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,25 @@ 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()

if seconds < minSeconds || seconds > maxSeconds {
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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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.
Expand Down Expand Up @@ -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)

Expand Down
143 changes: 83 additions & 60 deletions datetime/datetime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand Down
52 changes: 52 additions & 0 deletions datetime/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down