forked from mirror/go-sqlite3
Merge pull request #36 from mstetson/timestamp-fixes
A few more timestamp fixes
This commit is contained in:
commit
b8adf7d8c1
29
sqlite3.go
29
sqlite3.go
|
@ -40,9 +40,19 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SQLiteTimestampFormat = "2006-01-02 15:04:05"
|
// Timestamp formats understood by both this module and SQLite.
|
||||||
const SQLiteDateFormat = "2006-01-02"
|
// The first format in the slice will be used when saving time values
|
||||||
const SQLiteDatetimeFormat = "2006-01-02 15:04:05.000"
|
// into the database. When parsing a string from a timestamp or
|
||||||
|
// datetime column, the formats are tried in order.
|
||||||
|
var SQLiteTimestampFormats = []string{
|
||||||
|
"2006-01-02 15:04:05.999999999",
|
||||||
|
"2006-01-02T15:04:05.999999999",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02 15:04",
|
||||||
|
"2006-01-02T15:04",
|
||||||
|
"2006-01-02",
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sql.Register("sqlite3", &SQLiteDriver{})
|
sql.Register("sqlite3", &SQLiteDriver{})
|
||||||
|
@ -213,7 +223,7 @@ func (s *SQLiteStmt) bind(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(p), C.int(len(v)))
|
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(p), C.int(len(v)))
|
||||||
case time.Time:
|
case time.Time:
|
||||||
b := []byte(v.UTC().Format(SQLiteTimestampFormat))
|
b := []byte(v.UTC().Format(SQLiteTimestampFormats[0]))
|
||||||
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
|
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
|
||||||
}
|
}
|
||||||
if rv != C.SQLITE_OK {
|
if rv != C.SQLITE_OK {
|
||||||
|
@ -327,19 +337,14 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
|
||||||
|
|
||||||
switch rc.decltype[i] {
|
switch rc.decltype[i] {
|
||||||
case "timestamp", "datetime":
|
case "timestamp", "datetime":
|
||||||
for {
|
for _, format := range SQLiteTimestampFormats {
|
||||||
if dest[i], err = time.Parse(SQLiteTimestampFormat, s); err == nil {
|
if dest[i], err = time.Parse(format, s); err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if dest[i], err = time.Parse(SQLiteDateFormat, s); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if dest[i], err = time.Parse(SQLiteDatetimeFormat, s); err == nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
// The column is a time value, so return the zero time on parse failure.
|
// The column is a time value, so return the zero time on parse failure.
|
||||||
dest[i] = time.Time{}
|
dest[i] = time.Time{}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
dest[i] = s
|
dest[i] = s
|
||||||
|
|
221
sqlite3_test.go
221
sqlite3_test.go
|
@ -245,35 +245,46 @@ func TestTimestamp(t *testing.T) {
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
_, err = db.Exec("DROP TABLE foo")
|
_, err = db.Exec("DROP TABLE foo")
|
||||||
_, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP)")
|
_, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP, dt DATETIME)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to create table:", err)
|
t.Fatal("Failed to create table:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC)
|
timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC)
|
||||||
_, err = db.Exec("INSERT INTO foo(id, ts) VALUES(1, ?)", timestamp1)
|
timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC)
|
||||||
|
timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)
|
||||||
|
tests := []struct {
|
||||||
|
value interface{}
|
||||||
|
expected time.Time
|
||||||
|
}{
|
||||||
|
{"nonsense", time.Time{}},
|
||||||
|
{"0000-00-00 00:00:00", time.Time{}},
|
||||||
|
{timestamp1, timestamp1},
|
||||||
|
{timestamp1.Unix(), timestamp1},
|
||||||
|
{timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1},
|
||||||
|
{timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1},
|
||||||
|
{timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1},
|
||||||
|
{timestamp1.Format("2006-01-02 15:04:05"), timestamp1},
|
||||||
|
{timestamp1.Format("2006-01-02T15:04:05"), timestamp1},
|
||||||
|
{timestamp2, timestamp2},
|
||||||
|
{"2006-01-02 15:04:05.123456789", timestamp2},
|
||||||
|
{"2006-01-02T15:04:05.123456789", timestamp2},
|
||||||
|
{"2012-11-04", timestamp3},
|
||||||
|
{"2012-11-04 00:00", timestamp3},
|
||||||
|
{"2012-11-04 00:00:00", timestamp3},
|
||||||
|
{"2012-11-04 00:00:00.000", timestamp3},
|
||||||
|
{"2012-11-04T00:00", timestamp3},
|
||||||
|
{"2012-11-04T00:00:00", timestamp3},
|
||||||
|
{"2012-11-04T00:00:00.000", timestamp3},
|
||||||
|
}
|
||||||
|
for i := range tests {
|
||||||
|
_, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to insert timestamp:", err)
|
t.Fatal("Failed to insert timestamp:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp2 := time.Date(2012, time.April, 6, 23, 22, 0, 0, time.UTC)
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, ts) VALUES(2, ?)", timestamp2.Unix())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert timestamp:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, ts) VALUES(3, ?)", "nonsense")
|
rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC")
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert nonsense:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp4 := time.Date(2012, time.April, 6, 23, 22, 0, 0, time.FixedZone("TEST", -7*3600))
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, ts) VALUES(4, ?)", timestamp4)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert timestamp:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT id, ts FROM foo ORDER BY id ASC")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to query foo table:", err)
|
t.Fatal("Unable to query foo table:", err)
|
||||||
}
|
}
|
||||||
|
@ -282,44 +293,27 @@ func TestTimestamp(t *testing.T) {
|
||||||
seen := 0
|
seen := 0
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int
|
var id int
|
||||||
var ts time.Time
|
var ts, dt time.Time
|
||||||
|
|
||||||
if err := rows.Scan(&id, &ts); err != nil {
|
if err := rows.Scan(&id, &ts, &dt); err != nil {
|
||||||
t.Error("Unable to scan results:", err)
|
t.Error("Unable to scan results:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if id < 0 || id >= len(tests) {
|
||||||
if id == 1 {
|
t.Error("Bad row id: ", id)
|
||||||
seen += 1
|
continue
|
||||||
if !timestamp1.Equal(ts) {
|
}
|
||||||
t.Errorf("Value for id 1 should be %v, not %v", timestamp1, ts)
|
seen++
|
||||||
|
if !tests[id].expected.Equal(ts) {
|
||||||
|
t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
|
||||||
|
}
|
||||||
|
if !tests[id].expected.Equal(dt) {
|
||||||
|
t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == 2 {
|
if seen != len(tests) {
|
||||||
seen += 1
|
t.Errorf("Expected to see %d rows", len(tests))
|
||||||
if !timestamp2.Equal(ts) {
|
|
||||||
t.Errorf("Value for id 2 should be %v, not %v", timestamp2, ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if id == 3 {
|
|
||||||
seen += 1
|
|
||||||
if !ts.IsZero() {
|
|
||||||
t.Errorf("Value for id 3 should be the zero time, not %v", ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if id == 4 {
|
|
||||||
seen += 1
|
|
||||||
if !timestamp4.Equal(ts) {
|
|
||||||
t.Errorf("Value for id 4 should be %v, not %v", timestamp4, ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen != 4 {
|
|
||||||
t.Error("Expected to see four timestamps")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,130 +408,3 @@ func TestBoolean(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateOnlyTimestamp(t *testing.T) {
|
|
||||||
// make sure that timestamps without times are extractable. these are generated when
|
|
||||||
// e.g., you use the sqlite `DATE()` built-in.
|
|
||||||
|
|
||||||
db, er := sql.Open("sqlite3", "db.sqlite")
|
|
||||||
if er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
db.Close()
|
|
||||||
os.Remove("db.sqlite")
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, er = db.Exec(`
|
|
||||||
CREATE TABLE test
|
|
||||||
( entry_id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
||||||
, entry_published TIMESTAMP NOT NULL
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
if er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, er = db.Exec(`
|
|
||||||
INSERT INTO test VALUES ( 1, '2012-11-04')
|
|
||||||
`)
|
|
||||||
if er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, er := db.Query("SELECT entry_id, entry_published FROM test")
|
|
||||||
if er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
if !rows.Next() {
|
|
||||||
if er := rows.Err(); er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
} else {
|
|
||||||
t.Fatalf("Unable to extract row containing date-only timestamp")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryId int64
|
|
||||||
var entryPublished time.Time
|
|
||||||
|
|
||||||
if er := rows.Scan(&entryId, &entryPublished); er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entryId != 1 {
|
|
||||||
t.Errorf("EntryId does not match inserted value")
|
|
||||||
}
|
|
||||||
|
|
||||||
if entryPublished.Year() != 2012 || entryPublished.Month() != 11 || entryPublished.Day() != 4 {
|
|
||||||
t.Errorf("Extracted time does not match inserted value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDatetime(t *testing.T) {
|
|
||||||
db, err := sql.Open("sqlite3", "./datetime.db")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to open database:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
db.Close()
|
|
||||||
os.Remove("./datetime.db")
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = db.Exec("DROP TABLE datetimetest")
|
|
||||||
_, err = db.Exec(`
|
|
||||||
CREATE TABLE datetimetest
|
|
||||||
( entry_id INTEGER
|
|
||||||
, my_datetime DATETIME
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to create table:", err)
|
|
||||||
}
|
|
||||||
datetime := "2006-01-02 15:04:05.003"
|
|
||||||
_, err = db.Exec(`
|
|
||||||
INSERT INTO datetimetest(entry_id, my_datetime)
|
|
||||||
VALUES(1, ?)`, datetime)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert datetime:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query(
|
|
||||||
"SELECT entry_id, my_datetime FROM datetimetest ORDER BY entry_id ASC")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to query datetimetest table:", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
if !rows.Next() {
|
|
||||||
if er := rows.Err(); er != nil {
|
|
||||||
t.Fatal(er)
|
|
||||||
} else {
|
|
||||||
t.Fatalf("Unable to extract row containing datetime")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryId int
|
|
||||||
var myDatetime time.Time
|
|
||||||
|
|
||||||
if err := rows.Scan(&entryId, &myDatetime); err != nil {
|
|
||||||
t.Error("Unable to scan results:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entryId != 1 {
|
|
||||||
t.Errorf("EntryId does not match inserted value")
|
|
||||||
}
|
|
||||||
|
|
||||||
if myDatetime.Year() != 2006 ||
|
|
||||||
myDatetime.Month() != 1 ||
|
|
||||||
myDatetime.Day() != 2 ||
|
|
||||||
myDatetime.Hour() != 15 ||
|
|
||||||
myDatetime.Minute() != 4 ||
|
|
||||||
myDatetime.Second() != 5 ||
|
|
||||||
myDatetime.Nanosecond() != 3000000 {
|
|
||||||
t.Errorf("Extracted time does not match inserted value")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue