From 798bbeb9bb84a81f8f07f944e2e28002b80305ca Mon Sep 17 00:00:00 2001 From: Dan Peterson Date: Sun, 14 Mar 2021 15:48:26 -0300 Subject: [PATCH] driver: support scanning more formats into time.Time Fixes https://gitlab.com/cznic/sqlite/-/issues/46 --- all_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ sqlite.go | 23 ++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/all_test.go b/all_test.go index e631e11..fd8d4a2 100644 --- a/all_test.go +++ b/all_test.go @@ -1127,6 +1127,57 @@ func TestTime(t *testing.T) { } } +// https://gitlab.com/cznic/sqlite/-/issues/46 +func TestTimeScan(t *testing.T) { + ref := time.Date(2021, 1, 2, 16, 39, 17, 123456789, time.UTC) + + cases := []struct { + s string + w time.Time + }{ + {s: "2021-01-02 12:39:17 -0400 ADT m=+00000", w: ref.Truncate(time.Second)}, + {s: "2021-01-02 16:39:17 +0000 UTC m=+0.000000001", w: ref.Truncate(time.Second)}, + {s: "2021-01-02 12:39:17.123456 -0400 ADT m=+00000", w: ref.Truncate(time.Microsecond)}, + {s: "2021-01-02 16:39:17.123456 +0000 UTC m=+0.000000001", w: ref.Truncate(time.Microsecond)}, + {s: "2021-01-02 16:39:17Z", w: ref.Truncate(time.Second)}, + {s: "2021-01-02 16:39:17+00:00", w: ref.Truncate(time.Second)}, + {s: "2021-01-02T16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)}, + {s: "2021-01-02 16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)}, + {s: "2021-01-02 12:39:17-04:00", w: ref.Truncate(time.Second)}, + {s: "2021-01-02 16:39:17", w: ref.Truncate(time.Second)}, + {s: "2021-01-02T16:39:17", w: ref.Truncate(time.Second)}, + {s: "2021-01-02 16:39", w: ref.Truncate(time.Minute)}, + {s: "2021-01-02T16:39", w: ref.Truncate(time.Minute)}, + {s: "2021-01-02", w: ref.Truncate(24 * time.Hour)}, + } + + db, err := sql.Open(driverName, "file::memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + for _, colType := range []string{"DATE", "DATETIME", "TIMESTAMP"} { + for _, tc := range cases { + if _, err := db.Exec("drop table if exists x; create table x (y " + colType + ")"); err != nil { + t.Fatal(err) + } + if _, err := db.Exec("insert into x (y) values (?)", tc.s); err != nil { + t.Fatal(err) + } + + var got time.Time + + if err := db.QueryRow("select y from x").Scan(&got); err != nil { + t.Fatal(err) + } + if !got.Equal(tc.w) { + t.Errorf("scan(%q as %q) = %s, want %s", tc.s, colType, got, tc.w) + } + } + } +} + // https://sqlite.org/lang_expr.html#varparam // https://gitlab.com/cznic/sqlite/-/issues/42 func TestBinding(t *testing.T) { diff --git a/sqlite.go b/sqlite.go index 0b6e054..11bfde3 100644 --- a/sqlite.go +++ b/sqlite.go @@ -304,6 +304,19 @@ func (r *rows) Next(dest []driver.Value) (err error) { } } +// Inspired by mattn/go-sqlite3: https://github.com/mattn/go-sqlite3/blob/ab91e934/sqlite3.go#L210-L226 +// +// These time.Parse formats handle formats 1 through 7 listed at https://www.sqlite.org/lang_datefunc.html. +var parseTimeFormats = []string{ + "2006-01-02 15:04:05.999999999-07:00", + "2006-01-02T15:04:05.999999999-07:00", + "2006-01-02 15:04:05.999999999", + "2006-01-02T15:04:05.999999999", + "2006-01-02 15:04", + "2006-01-02T15:04", + "2006-01-02", +} + // Attempt to parse s as a time. Return (s, false) if s is not // recognized as a valid time encoding. func (c *conn) parseTime(s string) (interface{}, bool) { @@ -311,7 +324,15 @@ func (c *conn) parseTime(s string) (interface{}, bool) { return v, true } - // TODO Add URI select time storage format and handle more formats + ts := strings.TrimSuffix(s, "Z") + + for _, f := range parseTimeFormats { + t, err := time.Parse(f, ts) + if err == nil { + return t, true + } + } + return s, false }