Skip to content

Commit 80ee1ba

Browse files
committed
Merge pull request #243 from augustoroman/master
Store/retrieve timezones for time.Time values.
2 parents 008acb7 + 7b0d180 commit 80ee1ba

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

sqlite3.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,17 @@ import (
9999
// into the database. When parsing a string from a timestamp or
100100
// datetime column, the formats are tried in order.
101101
var SQLiteTimestampFormats = []string{
102+
// By default, store timestamps with whatever timezone they come with.
103+
// When parsed, they will be returned with the same timezone.
104+
"2006-01-02 15:04:05.999999999-07:00",
105+
"2006-01-02T15:04:05.999999999-07:00",
102106
"2006-01-02 15:04:05.999999999",
103107
"2006-01-02T15:04:05.999999999",
104108
"2006-01-02 15:04:05",
105109
"2006-01-02T15:04:05",
106110
"2006-01-02 15:04",
107111
"2006-01-02T15:04",
108112
"2006-01-02",
109-
"2006-01-02 15:04:05-07:00",
110113
}
111114

112115
func init() {
@@ -803,7 +806,7 @@ func (s *SQLiteStmt) bind(args []driver.Value) error {
803806
}
804807
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(p), C.int(len(v)))
805808
case time.Time:
806-
b := []byte(v.UTC().Format(SQLiteTimestampFormats[0]))
809+
b := []byte(v.Format(SQLiteTimestampFormats[0]))
807810
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
808811
}
809812
if rv != C.SQLITE_OK {
@@ -902,18 +905,15 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
902905
val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i)))
903906
switch rc.decltype[i] {
904907
case "timestamp", "datetime", "date":
905-
unixTimestamp := strconv.FormatInt(val, 10)
906908
var t time.Time
907-
if len(unixTimestamp) == 13 {
908-
duration, err := time.ParseDuration(unixTimestamp + "ms")
909-
if err != nil {
910-
return fmt.Errorf("error parsing %s value %d, %s", rc.decltype[i], val, err)
911-
}
912-
epoch := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
913-
t = epoch.Add(duration)
909+
// Assume a millisecond unix timestamp if it's 13 digits -- too
910+
// large to be a reasonable timestamp in seconds.
911+
if val > 1e12 || val < -1e12 {
912+
val *= int64(time.Millisecond) // convert ms to nsec
914913
} else {
915-
t = time.Unix(val, 0)
914+
val *= int64(time.Second) // convert sec to nsec
916915
}
916+
t = time.Unix(0, val).UTC()
917917
if rc.s.c.loc != nil {
918918
t = t.In(rc.s.c.loc)
919919
}

sqlite3_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ func TestBooleanRoundtrip(t *testing.T) {
324324
}
325325
}
326326

327+
func timezone(t time.Time) string { return t.Format("-07:00") }
328+
327329
func TestTimestamp(t *testing.T) {
328330
tempFilename := TempFilename()
329331
db, err := sql.Open("sqlite3", tempFilename)
@@ -342,30 +344,40 @@ func TestTimestamp(t *testing.T) {
342344
timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC)
343345
timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC)
344346
timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)
347+
tzTest := time.FixedZone("TEST", -9*3600-13*60)
345348
tests := []struct {
346349
value interface{}
347350
expected time.Time
348351
}{
349352
{"nonsense", time.Time{}},
350353
{"0000-00-00 00:00:00", time.Time{}},
351354
{timestamp1, timestamp1},
352-
{timestamp1.Unix(), timestamp1},
353-
{timestamp1.UnixNano() / int64(time.Millisecond), timestamp1},
354-
{timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1},
355+
{timestamp2.Unix(), timestamp2.Truncate(time.Second)},
356+
{timestamp2.UnixNano() / int64(time.Millisecond), timestamp2.Truncate(time.Millisecond)},
357+
{timestamp1.In(tzTest), timestamp1.In(tzTest)},
355358
{timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1},
356359
{timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1},
357360
{timestamp1.Format("2006-01-02 15:04:05"), timestamp1},
358361
{timestamp1.Format("2006-01-02T15:04:05"), timestamp1},
359362
{timestamp2, timestamp2},
360363
{"2006-01-02 15:04:05.123456789", timestamp2},
361364
{"2006-01-02T15:04:05.123456789", timestamp2},
365+
{"2006-01-02T05:51:05.123456789-09:13", timestamp2.In(tzTest)},
362366
{"2012-11-04", timestamp3},
363367
{"2012-11-04 00:00", timestamp3},
364368
{"2012-11-04 00:00:00", timestamp3},
365369
{"2012-11-04 00:00:00.000", timestamp3},
366370
{"2012-11-04T00:00", timestamp3},
367371
{"2012-11-04T00:00:00", timestamp3},
368372
{"2012-11-04T00:00:00.000", timestamp3},
373+
{"2006-01-02T15:04:05.123456789Z", timestamp2},
374+
{"2012-11-04Z", timestamp3},
375+
{"2012-11-04 00:00Z", timestamp3},
376+
{"2012-11-04 00:00:00Z", timestamp3},
377+
{"2012-11-04 00:00:00.000Z", timestamp3},
378+
{"2012-11-04T00:00Z", timestamp3},
379+
{"2012-11-04T00:00:00Z", timestamp3},
380+
{"2012-11-04T00:00:00.000Z", timestamp3},
369381
}
370382
for i := range tests {
371383
_, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value)
@@ -400,6 +412,14 @@ func TestTimestamp(t *testing.T) {
400412
if !tests[id].expected.Equal(dt) {
401413
t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
402414
}
415+
if timezone(tests[id].expected) != timezone(ts) {
416+
t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value,
417+
timezone(tests[id].expected), timezone(ts))
418+
}
419+
if timezone(tests[id].expected) != timezone(dt) {
420+
t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value,
421+
timezone(tests[id].expected), timezone(dt))
422+
}
403423
}
404424

405425
if seen != len(tests) {

0 commit comments

Comments
 (0)