From bd716c52e5d1d03b4b736bf3602823d84e749d48 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 1 Jun 2017 23:19:33 +0800 Subject: [PATCH] fix time issues and add some tests for time --- engine.go | 51 ++------- session_convert.go | 28 +++-- statement.go | 4 +- time_test.go | 255 +++++++++++++++++++++++++++++++++++++++++++++ xorm.go | 6 ++ 5 files changed, 284 insertions(+), 60 deletions(-) create mode 100644 time_test.go diff --git a/engine.go b/engine.go index 5b087b37..3195e47e 100644 --- a/engine.go +++ b/engine.go @@ -40,7 +40,7 @@ type Engine struct { showExecTime bool logger core.ILogger - TZLocation *time.Location + TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database disableGlobalCache bool @@ -1498,7 +1498,6 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { results = append(results, result) if err != nil { return nil, err - //lastError = err } } } @@ -1506,49 +1505,23 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { return results, lastError } -// TZTime change one time to xorm time location -func (engine *Engine) TZTime(t time.Time) time.Time { - if !t.IsZero() { // if time is not initialized it's not suitable for Time.In() - return t.In(engine.TZLocation) - } - return t -} - -// NowTime return current time -func (engine *Engine) NowTime(sqlTypeName string) interface{} { - t := time.Now() - return engine.FormatTime(sqlTypeName, t) -} - // NowTime2 return current time func (engine *Engine) NowTime2(sqlTypeName string) (interface{}, time.Time) { t := time.Now() - return engine.FormatTime(sqlTypeName, t), t -} - -// FormatTime format time -func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) { - return engine.formatTime(engine.TZLocation, sqlTypeName, t) + return engine.formatTime(sqlTypeName, t.In(engine.DatabaseTZ)), t.In(engine.TZLocation) } func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) { if col.DisableTimeZone { - return engine.formatTime(nil, col.SQLType.Name, t) + return engine.formatTime(col.SQLType.Name, t) } else if col.TimeZone != nil { - return engine.formatTime(col.TimeZone, col.SQLType.Name, t) + return engine.formatTime(col.SQLType.Name, t.In(col.TimeZone)) } - return engine.formatTime(engine.TZLocation, col.SQLType.Name, t) + return engine.formatTime(col.SQLType.Name, t.In(engine.DatabaseTZ)) } -func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.Time) (v interface{}) { - if engine.dialect.DBType() == core.ORACLE { - return t - } - if tz != nil { - t = t.In(tz) - } else { - t = engine.TZTime(t) - } +// formatTime format time as column type +func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) { switch sqlTypeName { case core.Time: s := t.Format("2006-01-02 15:04:05") //time.RFC3339 @@ -1556,18 +1529,10 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T case core.Date: v = t.Format("2006-01-02") case core.DateTime, core.TimeStamp: - if engine.dialect.DBType() == "ql" { - v = t - } else if engine.dialect.DBType() == "sqlite3" { - v = t.UTC().Format("2006-01-02 15:04:05") - } else { - v = t.Format("2006-01-02 15:04:05") - } + v = t.Format("2006-01-02 15:04:05") case core.TimeStampz: if engine.dialect.DBType() == core.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") - } else if engine.DriverName() == "mssql" { - v = t } else { v = t.Format(time.RFC3339Nano) } diff --git a/session_convert.go b/session_convert.go index 7ef57b5f..1616547e 100644 --- a/session_convert.go +++ b/session_convert.go @@ -23,6 +23,11 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti var x time.Time var err error + var parseLoc = session.Engine.DatabaseTZ + if col.TimeZone != nil { + parseLoc = col.TimeZone + } + if sdata == "0000-00-00 00:00:00" || sdata == "0001-01-01 00:00:00" { } else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column @@ -30,33 +35,26 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti sd, err := strconv.ParseInt(sdata, 10, 64) if err == nil { x = time.Unix(sd, 0) - // !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion - if col.TimeZone == nil { - x = x.In(session.Engine.TZLocation) - } else { - x = x.In(col.TimeZone) - } session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else { session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } } else if len(sdata) > 19 && strings.Contains(sdata, "-") { - x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation) + x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } if err != nil { - x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } - } else if len(sdata) == 19 && strings.Contains(sdata, "-") { - x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { - x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else if col.SQLType.Name == core.Time { if strings.Contains(sdata, " ") { @@ -70,7 +68,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti } st := fmt.Sprintf("2006-01-02 %v", sdata) - x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) } else { outErr = fmt.Errorf("unsupported time format %v", sdata) @@ -80,7 +78,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err) return } - outTime = x + outTime = x.In(session.Engine.TZLocation) return } @@ -593,7 +591,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val return nil, nil } } - tf := session.Engine.FormatTime(col.SQLType.Name, t) + tf := session.Engine.formatColTime(col, t) return tf, nil } diff --git a/statement.go b/statement.go index 3c9ac4e9..58fa616b 100644 --- a/statement.go +++ b/statement.go @@ -376,7 +376,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } - val = engine.FormatTime(col.SQLType.Name, t) + val = engine.formatColTime(col, t) } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = nulType.Value() } else { @@ -612,7 +612,7 @@ func buildConds(engine *Engine, table *core.Table, bean interface{}, if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } - val = engine.FormatTime(col.SQLType.Name, t) + val = engine.formatColTime(col, t) } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok { continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { diff --git a/time_test.go b/time_test.go new file mode 100644 index 00000000..51dba462 --- /dev/null +++ b/time_test.go @@ -0,0 +1,255 @@ +// Copyright 2017 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func formatTime(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +func TestTimeUserTime(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TimeUser struct { + Id string + OperTime time.Time + } + + assertSync(t, new(TimeUser)) + + var user = TimeUser{ + Id: "lunny", + OperTime: time.Now(), + } + + fmt.Println("user", user.OperTime) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 TimeUser + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix()) + assert.EqualValues(t, formatTime(user.OperTime), formatTime(user2.OperTime)) + fmt.Println("user2", user2.OperTime) +} + +func TestTimeUserTimeDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type TimeUser struct { + Id string + OperTime time.Time + } + + assertSync(t, new(TimeUser)) + + var user = TimeUser{ + Id: "lunny", + OperTime: time.Now(), + } + + fmt.Println("user", user.OperTime) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 TimeUser + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix()) + assert.EqualValues(t, formatTime(user.OperTime), formatTime(user2.OperTime)) + fmt.Println("user2", user2.OperTime) +} + +func TestTimeUserCreated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserCreated struct { + Id string + CreatedAt time.Time `xorm:"created"` + } + + assertSync(t, new(UserCreated)) + + var user = UserCreated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserCreated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + fmt.Println("user2", user2.CreatedAt) +} + +func TestTimeUserCreatedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserCreated struct { + Id string + CreatedAt time.Time `xorm:"created"` + } + + assertSync(t, new(UserCreated)) + + var user = UserCreated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserCreated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + fmt.Println("user2", user2.CreatedAt) +} + +func TestTimeUserUpdated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserUpdated struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + } + + assertSync(t, new(UserUpdated)) + + var user = UserUpdated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt, user.UpdatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserUpdated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt) + + var user3 = UserUpdated{ + Id: "lunny2", + } + + cnt, err = testEngine.Update(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix()) + + var user4 UserUpdated + has, err = testEngine.Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt)) + assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt)) + fmt.Println("user3", user.CreatedAt, user4.UpdatedAt) +} + +func TestTimeUserUpdatedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserUpdated struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + } + + assertSync(t, new(UserUpdated)) + + var user = UserUpdated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt, user.UpdatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserUpdated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt) + + var user3 = UserUpdated{ + Id: "lunny2", + } + + cnt, err = testEngine.Update(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix()) + + var user4 UserUpdated + has, err = testEngine.Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt)) + assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt)) + fmt.Println("user3", user.CreatedAt, user4.UpdatedAt) +} diff --git a/xorm.go b/xorm.go index 63cb15a6..3e1e2e50 100644 --- a/xorm.go +++ b/xorm.go @@ -89,6 +89,12 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { tagHandlers: defaultTagHandlers, } + if uri.DbType == core.SQLITE { + engine.DatabaseTZ = time.UTC + } else { + engine.DatabaseTZ = time.Local + } + logger := NewSimpleLogger(os.Stdout) logger.SetLevel(core.LOG_INFO) engine.SetLogger(logger)