From 7807694d736f1e75f80eb3aa577eb3a50ecc5ad2 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 26 Jul 2021 23:49:42 +0200 Subject: [PATCH 1/2] write time.Time with 0000-01-01 date as only time of day HH:MM:SS - adds support for the "time of day" format of the TIME type - fixes falsely rejecting a valid date with year 0 --- connection.go | 14 +++++--------- packets.go | 10 +++------- utils.go | 19 ++++++++++++++++++- utils_test.go | 18 ++++++++++++------ 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/connection.go b/connection.go index 835f89729..386cfee73 100644 --- a/connection.go +++ b/connection.go @@ -242,16 +242,12 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin buf = append(buf, '0') } case time.Time: - if v.IsZero() { - buf = append(buf, "'0000-00-00'"...) - } else { - buf = append(buf, '\'') - buf, err = appendDateTime(buf, v.In(mc.cfg.Loc)) - if err != nil { - return "", err - } - buf = append(buf, '\'') + buf = append(buf, '\'') + buf, err = appendTime(buf, v.In(mc.cfg.Loc)) + if err != nil { + return "", err } + buf = append(buf, '\'') case json.RawMessage: buf = append(buf, '\'') if mc.status&statusNoBackslashEscapes == 0 { diff --git a/packets.go b/packets.go index 1867ecab2..4378c88a6 100644 --- a/packets.go +++ b/packets.go @@ -1113,13 +1113,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { var a [64]byte var b = a[:0] - if v.IsZero() { - b = append(b, "0000-00-00"...) - } else { - b, err = appendDateTime(b, v.In(mc.cfg.Loc)) - if err != nil { - return err - } + b, err = appendTime(b, v.In(mc.cfg.Loc)) + if err != nil { + return err } paramValues = appendLengthEncodedInteger(paramValues, diff --git a/utils.go b/utils.go index bcdee1b46..28573f5cf 100644 --- a/utils.go +++ b/utils.go @@ -276,12 +276,29 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va return nil, fmt.Errorf("invalid DATETIME packet length %d", num) } +// appendTime serializes a time.Time t and appends it to buf. +func appendTime(buf []byte, t time.Time) ([]byte, error) { + // this is the layout string for the "time of day" format of the MySQL TIME type + // see https://dev.mysql.com/doc/refman/8.0/en/time.html + const mysqlTimeLayout = "15:04:05" + + switch { + case t.IsZero(): + return append(buf, "0000-00-00"...), nil + case t.Year() == 0 && t.Month() == 1 && t.Day() == 1: + return t.AppendFormat(buf, mysqlTimeLayout), nil + default: + return appendDateTime(buf, t) + } +} + +// appendDateTime is a special case of appendTime. func appendDateTime(buf []byte, t time.Time) ([]byte, error) { year, month, day := t.Date() hour, min, sec := t.Clock() nsec := t.Nanosecond() - if year < 1 || year > 9999 { + if year < 0 || year > 9999 { return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap } year100 := year / 100 diff --git a/utils_test.go b/utils_test.go index b0069251e..dbc712c1d 100644 --- a/utils_test.go +++ b/utils_test.go @@ -295,7 +295,7 @@ func TestIsolationLevelMapping(t *testing.T) { } } -func TestAppendDateTime(t *testing.T) { +func TestAppendTime(t *testing.T) { tests := []struct { t time.Time str string @@ -333,13 +333,19 @@ func TestAppendDateTime(t *testing.T) { str: "9999-12-31 23:59:59.999999999", }, { + // zero date t: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), - str: "0001-01-01", + str: "0000-00-00", + }, + { + // only time + t: time.Date(0, 1, 1, 8, 30, 0, 0, time.UTC), + str: "08:30:00", }, } for _, v := range tests { buf := make([]byte, 0, 32) - buf, _ = appendDateTime(buf, v.t) + buf, _ = appendTime(buf, v.t) if str := string(buf); str != v.str { t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str) } @@ -347,9 +353,9 @@ func TestAppendDateTime(t *testing.T) { // year out of range { - v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC) + v := time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC) buf := make([]byte, 0, 32) - _, err := appendDateTime(buf, v) + _, err := appendTime(buf, v) if err == nil { t.Error("want an error") return @@ -358,7 +364,7 @@ func TestAppendDateTime(t *testing.T) { { v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC) buf := make([]byte, 0, 32) - _, err := appendDateTime(buf, v) + _, err := appendTime(buf, v) if err == nil { t.Error("want an error") return From 58442b33351ccb2bd71e0b53efd51890de4cda44 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 26 Jul 2021 23:56:08 +0200 Subject: [PATCH 2/2] add test for valid date with year 0 --- utils_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utils_test.go b/utils_test.go index dbc712c1d..d7a6dd1d0 100644 --- a/utils_test.go +++ b/utils_test.go @@ -337,6 +337,11 @@ func TestAppendTime(t *testing.T) { t: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), str: "0000-00-00", }, + { + // valid date with year 0 + t: time.Date(0, 2, 2, 12, 34, 56, 0, time.UTC), + str: "0000-02-02 12:34:56", + }, { // only time t: time.Date(0, 1, 1, 8, 30, 0, 0, time.UTC),