mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Time improvements.
This commit is contained in:
@@ -324,7 +324,7 @@ func (r rows) Next(dest []driver.Value) error {
|
||||
case sqlite3.FLOAT:
|
||||
dest[i] = r.stmt.ColumnFloat(i)
|
||||
case sqlite3.TEXT:
|
||||
dest[i] = maybeDate(r.stmt.ColumnText(i))
|
||||
dest[i] = maybeTime(r.stmt.ColumnText(i))
|
||||
case sqlite3.BLOB:
|
||||
buf, _ := dest[i].([]byte)
|
||||
dest[i] = r.stmt.ColumnBlob(i, buf)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// if it roundtrips back to the same string.
|
||||
// This way times can be persisted to, and recovered from, the database,
|
||||
// but if a string is needed, [database/sql] will recover the same string.
|
||||
func maybeDate(text string) driver.Value {
|
||||
func maybeTime(text string) driver.Value {
|
||||
// Weed out (some) values that can't possibly be
|
||||
// [time.RFC3339Nano] timestamps.
|
||||
if len(text) < len("2006-01-02T15:04:05Z") {
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Fuzz_maybeDate(f *testing.F) {
|
||||
// This checks that any string can be recovered as the same string.
|
||||
func Fuzz_maybeTime_1(f *testing.F) {
|
||||
f.Add("")
|
||||
f.Add(" ")
|
||||
f.Add("SQLite")
|
||||
@@ -21,7 +22,7 @@ func Fuzz_maybeDate(f *testing.F) {
|
||||
f.Add("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
value := maybeDate(str)
|
||||
value := maybeTime(str)
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
@@ -44,3 +45,56 @@ func Fuzz_maybeDate(f *testing.F) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// This checks that any [time.Time] can be recovered as a [time.Time],
|
||||
// with nanosecond accuracy, and preserving any timezone offset.
|
||||
func Fuzz_maybeTime_2(f *testing.F) {
|
||||
f.Add(0, 0)
|
||||
f.Add(0, 1)
|
||||
f.Add(0, -1)
|
||||
f.Add(0, 999_999_999)
|
||||
f.Add(0, 1_000_000_000)
|
||||
f.Add(7956915742, 222_222_222) // twosday
|
||||
f.Add(639095955742, 222_222_222) // twosday, year 22222AD
|
||||
f.Add(-763421161058, 222_222_222) // twosday, year 22222BC
|
||||
|
||||
checkTime := func(t *testing.T, date time.Time) {
|
||||
value := maybeTime(date.Format(time.RFC3339Nano))
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
// Make sure times round-trip to the same time:
|
||||
if !v.Equal(date) {
|
||||
t.Fatalf("did not round-trip: %v", date)
|
||||
}
|
||||
// Make with the same zone offset:
|
||||
_, off1 := v.Zone()
|
||||
_, off2 := date.Zone()
|
||||
if off1 != off2 {
|
||||
t.Fatalf("did not round-trip: %v", date)
|
||||
}
|
||||
case string:
|
||||
t.Fatalf("was not recovered: %v", date)
|
||||
default:
|
||||
t.Fatalf("invalid type %T: %v", v, date)
|
||||
}
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, sec, nsec int) {
|
||||
// Reduce the search space.
|
||||
if 1e12 < sec || sec < -1e12 {
|
||||
// Dates before 29000BC and after 33000AD; I think we're safe.
|
||||
return
|
||||
}
|
||||
if 0 < nsec || nsec > 1e10 {
|
||||
// Out of range nsec: [time.Time.Unix] handles these.
|
||||
return
|
||||
}
|
||||
|
||||
unix := time.Unix(int64(sec), int64(nsec))
|
||||
checkTime(t, unix)
|
||||
checkTime(t, unix.UTC())
|
||||
checkTime(t, unix.In(time.FixedZone("", -8*3600)))
|
||||
checkTime(t, unix.In(time.FixedZone("", +8*3600)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestStmt_BindName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmt_Time(t *testing.T) {
|
||||
func TestStmt_ColumnTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
@@ -430,23 +430,23 @@ func TestStmt_Time(t *testing.T) {
|
||||
}
|
||||
|
||||
if now := time.Now(); stmt.Step() {
|
||||
if got := stmt.ColumnTime(0, sqlite3.TimeFormatAuto); !reference.Equal(got) {
|
||||
if got := stmt.ColumnTime(0, sqlite3.TimeFormatAuto); !got.Equal(reference) {
|
||||
t.Errorf("got %v, want %v", got, reference)
|
||||
}
|
||||
if got := stmt.ColumnTime(1, sqlite3.TimeFormatAuto); !reference.Equal(got) {
|
||||
if got := stmt.ColumnTime(1, sqlite3.TimeFormatAuto); !got.Equal(reference) {
|
||||
t.Errorf("got %v, want %v", got, reference)
|
||||
}
|
||||
if got := stmt.ColumnTime(2, sqlite3.TimeFormatAuto); reference.Sub(got) > time.Millisecond {
|
||||
if got := stmt.ColumnTime(2, sqlite3.TimeFormatAuto); got.Sub(reference).Abs() > time.Millisecond {
|
||||
t.Errorf("got %v, want %v", got, reference)
|
||||
}
|
||||
|
||||
if got := stmt.ColumnTime(3, sqlite3.TimeFormatAuto); now.Sub(got) > time.Second {
|
||||
if got := stmt.ColumnTime(3, sqlite3.TimeFormatAuto); got.Sub(now).Abs() > time.Second {
|
||||
t.Errorf("got %v, want %v", got, now)
|
||||
}
|
||||
if got := stmt.ColumnTime(4, sqlite3.TimeFormatAuto); now.Sub(got) > time.Second {
|
||||
if got := stmt.ColumnTime(4, sqlite3.TimeFormatAuto); got.Sub(now).Abs() > time.Second {
|
||||
t.Errorf("got %v, want %v", got, now)
|
||||
}
|
||||
if got := stmt.ColumnTime(5, sqlite3.TimeFormatAuto); now.Sub(got) > time.Millisecond {
|
||||
if got := stmt.ColumnTime(5, sqlite3.TimeFormatAuto); got.Sub(now).Abs() > time.Second/10 {
|
||||
t.Errorf("got %v, want %v", got, now)
|
||||
}
|
||||
|
||||
|
||||
10
time.go
10
time.go
@@ -57,7 +57,7 @@ const (
|
||||
// Encode encodes a time value using this format.
|
||||
//
|
||||
// [TimeFormatDefault] and [TimeFormatAuto] encode using [time.RFC3339Nano],
|
||||
// with nanosecond accuracy, and preserving timezone.
|
||||
// with nanosecond accuracy, and preserving any timezone offset.
|
||||
//
|
||||
// Formats [TimeFormat1] through [TimeFormat10]
|
||||
// convert time values to UTC before encoding.
|
||||
@@ -102,16 +102,20 @@ func (f TimeFormat) Encode(t time.Time) any {
|
||||
// The time value can be a string, an int64, or a float64.
|
||||
//
|
||||
// Formats [TimeFormat8] through [TimeFormat10]
|
||||
// (and [TimeFormat8TZ] through [TimeFormat10TZ])
|
||||
// assume a date of 2000-01-01.
|
||||
//
|
||||
// The timezone indicator and fractional seconds are always optional
|
||||
// for formats [TimeFormat2] through [TimeFormat10].
|
||||
// for formats [TimeFormat2] through [TimeFormat10]
|
||||
// (and [TimeFormat2TZ] through [TimeFormat10TZ]).
|
||||
//
|
||||
// [TimeFormatAuto] implements (and extends) the SQLite auto modifier.
|
||||
// The julian day number is safe to use for historical dates,
|
||||
// Julian day numbers are safe to use for historical dates,
|
||||
// from 4712BC through 9999AD.
|
||||
// Unix timestamps (expressed in seconds, milliseconds, microseconds, or nanoseconds),
|
||||
// are safe to use for current events, from 1980 through at least 2260.
|
||||
// Unix timestamps before 1980 may be misinterpreted as julian day numbers,
|
||||
// or have the wrong time unit.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
|
||||
Reference in New Issue
Block a user