diff --git a/cursor.go b/cursor.go index 77b3771..0000fd5 100644 --- a/cursor.go +++ b/cursor.go @@ -4,7 +4,9 @@ import ( "database/sql" "fmt" "reflect" + "regexp" "strconv" + "strings" "time" ) @@ -31,23 +33,32 @@ func (c cursor) Next() bool { var timeType = reflect.TypeOf(time.Time{}) -var timeLayouts = []string{ - "2006-01-02", - "2006-01-02 15:04:05", - "2006-01-02 15:04:05.000", - "2006-01-02 15:04:05.000000", - "2006-01-02 15:04:05.000000000", - time.RFC3339Nano, +var simpleTimeLayoutRegexp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.(\d+))?$`) + +func guessTimeLayout(s string) string { + matches := simpleTimeLayoutRegexp.FindStringSubmatch(s) + if len(matches) > 0 { + var sb strings.Builder + sb.Grow(32) + sb.WriteString("2006-01-02 15:04:05") + if matches[1] != "" { + sb.WriteString(".") + for i := 0; i < len(matches[2]); i++ { + sb.WriteByte('0') + } + } + return sb.String() + } + return time.RFC3339Nano } func parseTime(s string) (time.Time, error) { - for _, layout := range timeLayouts { - t, err := time.Parse(layout, s) - if err == nil { - return t, nil - } + layout := guessTimeLayout(s) + t, err := time.Parse(layout, s) + if err != nil { + return time.Time{}, fmt.Errorf("unknown time format %s: %w", s, err) } - return time.Time{}, fmt.Errorf("unknown time format %s", s) + return t, nil } func isScanner(val reflect.Value) bool { diff --git a/cursor_test.go b/cursor_test.go index b9c36d3..039d9c8 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -209,3 +209,25 @@ func TestCursorMap(t *testing.T) { t.Error() } } + +func TestParseTime(t *testing.T) { + tests := []struct { + input string + output time.Time + }{ + {"2024-09-06 11:22:33", time.Date(2024, 9, 6, 11, 22, 33, 0, time.UTC)}, + {"2024-09-06 11:22:33.444", time.Date(2024, 9, 6, 11, 22, 33, 444000000, time.UTC)}, + {"2024-09-06 11:22:33.444555666", time.Date(2024, 9, 6, 11, 22, 33, 444555666, time.UTC)}, + {"2024-09-06T11:22:33.444555666Z", time.Date(2024, 9, 6, 11, 22, 33, 444555666, time.UTC)}, + } + for _, test := range tests { + tm, err := parseTime(test.input) + if err != nil { + t.Error(err) + continue + } + if tm != test.output { + t.Error(tm, test.output) + } + } +} diff --git a/expression.go b/expression.go index 74d5d57..954e4e3 100644 --- a/expression.go +++ b/expression.go @@ -328,6 +328,7 @@ func quoteString(s string) string { } func getSQL(scope scope, value interface{}) (sql string, priority priority, err error) { + const mysqlTimeFormat = "2006-01-02 15:04:05.000000" if value == nil { sql = "NULL" return @@ -355,14 +356,14 @@ func getSQL(scope scope, value interface{}) (sql string, priority priority, err case CaseExpression: sql, err = value.(CaseExpression).End().GetSQL(scope) case time.Time: - tmStr := value.(time.Time).Format("2006-01-02 15:04:05") + tmStr := value.(time.Time).Format(mysqlTimeFormat) sql = quoteString(tmStr) case *time.Time: tm := value.(*time.Time) if tm == nil { sql = "NULL" } else { - tmStr := tm.Format("2006-01-02 15:04:05") + tmStr := tm.Format(mysqlTimeFormat) sql = quoteString(tmStr) } default: