Commit 3cb36f53 authored by gwenn's avatar gwenn

Improve date/time handling.

parent c0d93694
......@@ -39,41 +39,45 @@ func JulianDay(t time.Time) float64 {
}
// UnixTime is an alias used to persist time as int64 (max precision is 1s and timezone is lost)
type UnixTime time.Time
type UnixTime struct {
time.Time
}
// Scan implements the database/sql/Scanner interface.
func (t *UnixTime) Scan(src interface{}) error {
if src == nil {
*t = UnixTime{}
t.Time = time.Time{}
return nil
} else if unixepoch, ok := src.(int64); ok {
*t = UnixTime(time.Unix(unixepoch, 0)) // local time
t.Time = time.Unix(unixepoch, 0) // local time
return nil
}
return fmt.Errorf("unsupported UnixTime src: %T", src)
return fmt.Errorf("unsupported UnixTime src: %T, %v", src, src)
}
// Value implements the database/sql/driver/Valuer interface
func (t UnixTime) Value() (driver.Value, error) {
if (time.Time)(t).IsZero() {
if t.IsZero() {
return nil, nil
}
return (time.Time)(t).Unix(), nil
return t.Unix(), nil
}
// JulianTime is an alias used to persist time as float64 (max precision is 1s and timezone is lost)
type JulianTime time.Time
type JulianTime struct {
time.Time
}
// Scan implements the database/sql/Scanner interface.
func (t *JulianTime) Scan(src interface{}) error {
if src == nil {
*t = JulianTime{}
t.Time = time.Time{}
return nil
} else if jd, ok := src.(int64); ok {
*t = JulianTime(JulianDayToLocalTime(float64(jd))) // local time
t.Time = JulianDayToLocalTime(float64(jd)) // local time
return nil
} else if jd, ok := src.(float64); ok {
*t = JulianTime(JulianDayToLocalTime(jd)) // local time
t.Time = JulianDayToLocalTime(jd) // local time
return nil
}
return fmt.Errorf("unsupported JulianTime src: %T", src)
......@@ -81,26 +85,28 @@ func (t *JulianTime) Scan(src interface{}) error {
// Value implements the database/sql/driver/Valuer interface
func (t JulianTime) Value() (driver.Value, error) {
if (time.Time)(t).IsZero() {
if t.IsZero() {
return nil, nil
}
return JulianDay((time.Time)(t)), nil
return JulianDay(t.Time), nil
}
// TimeStamp is an alias used to persist time as '2006-01-02T15:04:05.000Z07:00' string
type TimeStamp time.Time
type TimeStamp struct {
time.Time
}
// Scan implements the database/sql/Scanner interface.
func (t *TimeStamp) Scan(src interface{}) error {
if src == nil {
*t = TimeStamp{}
t.Time = time.Time{}
return nil
} else if txt, ok := src.(string); ok {
v, err := time.Parse("2006-01-02T15:04:05.000Z07:00", txt)
if err != nil {
return err
}
*t = TimeStamp(v)
t.Time = v
return nil
}
return fmt.Errorf("unsupported TimeStamp src: %T", src)
......@@ -108,8 +114,28 @@ func (t *TimeStamp) Scan(src interface{}) error {
// Value implements the database/sql/driver/Valuer interface
func (t TimeStamp) Value() (driver.Value, error) {
if (time.Time)(t).IsZero() {
if t.IsZero() {
return nil, nil
}
return (time.Time)(t).Format("2006-01-02T15:04:05.000Z07:00"), nil
return t.Format("2006-01-02T15:04:05.000Z07:00"), nil
}
// MarshalText encoding.TextMarshaler interface.
// TimeStamp is formatted as null when zero or RFC3339.
func (t TimeStamp) MarshalText() ([]byte, error) {
if t.IsZero() {
return []byte("null"), nil
}
return t.Time.MarshalText()
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Date is expected in RFC3339 format or null.
func (t *TimeStamp) UnmarshalText(data []byte) error {
if len(data) > 0 && data[0] == 'n' {
t.Time = time.Time{}
return nil
}
ti := &t.Time
return ti.UnmarshalText(data)
}
......@@ -82,7 +82,7 @@ func TestScanNullTime(t *testing.T) {
var unix UnixTime
err := db.OneValue("SELECT NULL", &unix)
checkNoError(t, err, "Error scanning null time: %#v")
if !(time.Time)(unix).IsZero() {
if !unix.IsZero() {
t.Error("Expected zero time")
}
}
......@@ -100,7 +100,7 @@ func TestBindTimeAsString(t *testing.T) {
now := time.Now()
//id1, err := is.Insert(YearMonthDay(now))
//checkNoError(t, err, "error inserting YearMonthDay: %s")
id2, err := is.Insert(TimeStamp(now))
id2, err := is.Insert(TimeStamp{now})
checkNoError(t, err, "error inserting TimeStamp: %s")
// The format used to persist has a max precision of 1ms.
......@@ -129,9 +129,9 @@ func TestBindTimeAsNumeric(t *testing.T) {
checkNoError(t, err, "prepare error: %s")
now := time.Now()
id1, err := is.Insert(UnixTime(now))
id1, err := is.Insert(UnixTime{now})
checkNoError(t, err, "error inserting UnixTime: %s")
id2, err := is.Insert(JulianTime(now))
id2, err := is.Insert(JulianTime{now})
checkNoError(t, err, "error inserting JulianTime: %s")
checkFinalize(is, t)
......@@ -158,7 +158,7 @@ func TestJulianTime(t *testing.T) {
checkNoError(t, err, "prepare error: %s")
now := time.Now()
id, err := is.Insert(JulianTime(now))
id, err := is.Insert(JulianTime{now})
checkNoError(t, err, "error inserting JulianTime: %s")
_, err = is.Insert(JulianTime{})
checkNoError(t, err, "error inserting JulianTime: %s")
......@@ -170,11 +170,11 @@ func TestJulianTime(t *testing.T) {
var jt JulianTime
err = db.OneValue("SELECT time FROM test where ROWID = ?", &jt, id)
checkNoError(t, err, "error selecting JulianTime: %s")
assert.Equal(t, now, time.Time(jt))
assert.Equal(t, now, jt.Time)
err = db.OneValue("SELECT null", &jt)
checkNoError(t, err, "%s")
assert.T(t, ((time.Time)(jt)).IsZero())
assert.T(t, jt.IsZero())
err = db.OneValue("SELECT 0", &jt)
checkNoError(t, err, "%s")
......@@ -194,7 +194,7 @@ func TestTimeStamp(t *testing.T) {
checkNoError(t, err, "prepare error: %s")
now := time.Now()
id, err := is.Insert(TimeStamp(now))
id, err := is.Insert(TimeStamp{now})
checkNoError(t, err, "error inserting TimeStamp: %s")
_, err = is.Insert(TimeStamp{})
checkNoError(t, err, "error inserting TimeStamp: %s")
......@@ -206,13 +206,13 @@ func TestTimeStamp(t *testing.T) {
var ts TimeStamp
err = db.OneValue("SELECT time FROM test where ROWID = ?", &ts, id)
checkNoError(t, err, "error selecting TimeStamp: %s")
if !now.Equal(time.Time(ts)) {
t.Errorf("got timeStamp: %s; want %s", time.Time(ts), now)
if !now.Equal(ts.Time) {
t.Errorf("got timeStamp: %s; want %s", ts, now)
}
err = db.OneValue("SELECT null", &ts)
checkNoError(t, err, "%s")
assert.T(t, ((time.Time)(ts)).IsZero())
assert.T(t, ts.IsZero())
err = db.OneValue("SELECT 'bim'", &ts)
assert.T(t, err != nil)
......@@ -233,7 +233,7 @@ func TestUnixTime(t *testing.T) {
checkNoError(t, err, "prepare error: %s")
now := time.Now()
id, err := is.Insert(UnixTime(now))
id, err := is.Insert(UnixTime{now})
checkNoError(t, err, "error inserting UnixTime: %s")
_, err = is.Insert(UnixTime{})
checkNoError(t, err, "error inserting UnixTime: %s")
......@@ -245,11 +245,11 @@ func TestUnixTime(t *testing.T) {
var ut UnixTime
err = db.OneValue("SELECT time FROM test where ROWID = ?", &ut, id)
checkNoError(t, err, "error selecting UnixTime: %s")
assert.Equal(t, now, time.Time(ut))
assert.Equal(t, now, ut.Time)
err = db.OneValue("SELECT null", &ut)
checkNoError(t, err, "%s")
assert.T(t, ((time.Time)(ut)).IsZero())
assert.T(t, ut.IsZero())
err = db.OneValue("SELECT 'bim'", &ut)
assert.T(t, err != nil)
......
......@@ -157,6 +157,7 @@ type Conn struct {
timeUsed time.Time
nTransaction uint8
// DefaultTimeLayout specifies the layout used to persist time ("2006-01-02 15:04:05.000Z07:00" by default).
// When set to "", time is persisted as integer (unix time).
// Using type alias implementing the Scanner/Valuer interfaces is suggested...
DefaultTimeLayout string
// ScanNumericalAsTime tells the driver to try to parse column with NUMERIC affinity as time.Time (using the DefaultTimeLayout)
......
......@@ -377,8 +377,9 @@ func (s *Stmt) BindByIndex(index int, value interface{}) error {
case time.Time:
if NullIfZeroTime && value.IsZero() {
rv = C.sqlite3_bind_null(s.stmt, i)
} else if s.c.DefaultTimeLayout == "" {
rv = C.sqlite3_bind_int64(s.stmt, i, C.sqlite3_int64(value.Unix()))
} else {
//rv = C.sqlite3_bind_int64(s.stmt, i, C.sqlite3_int64(value.Unix()))
cs, l := cstring(value.Format(s.c.DefaultTimeLayout))
rv = C.my_bind_text(s.stmt, i, cs, l)
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment