beego orm中時區的問題

tailnode發表於2019-02-16

轉載請註明出處,原文連結:http://tailnode.tk/2017/01/be…

先看簡化後程式碼,下面只列出main函式

func main() {
    t := "2017-01-19 00:00:00"
    o := orm.NewOrm()

    qb, _ := orm.NewQueryBuilder("mysql")
    sql := qb.Select("COUNT(*)").From("test").Where("create_time > ?").String()
    o.Raw(sql, t).Exec()

    o.QueryTable("test").Filter("create_time__gt", t).Count()
}

這麼看的話感覺兩個SQL應該是相同的:

[ORM] - 2017-01-19 19:28:02 - [Queries/default] - [  OK /     db.Exec /     1.2ms] - [SELECT COUNT(*) FROM test WHERE create_time > ?] - `2017-01-19 00:00:00`
[ORM] - 2017-01-19 19:28:02 - [Queries/default] - [  OK / db.QueryRow /     2.3ms] - [SELECT COUNT(*) FROM `test` T0 WHERE T0.`create_time` > ? ] - `2017-01-19 00:00:00`

我在本機測試OK,但在另一個環境SQL是這樣的:

[ORM] - 2017-01-19 11:30:43 - [Queries/default] - [  OK /     db.Exec /     1.2ms] - [SELECT COUNT(*) FROM test WHERE create_time > ?] - `2017-01-19 00:00:00`
[ORM] - 2017-01-19 11:30:43 - [Queries/default] - [  OK / db.QueryRow /     1.2ms] - [SELECT COUNT(*) FROM `test` T0 WHERE T0.`create_time` > ? ] - `2017-01-19 08:00:00`

相差8小時,第一時間想到時區問題,去有問題的環境一看果真如此。

然後看了下beego orm的程式碼,下面列出關鍵部分。
1.orm/db_utils.gogetFlatParams()
此函式是解析Filter()生成SQL的關鍵部分,如果Filter()第一個引數型別是Date或Datetime,第二個引數型別是string就把string解析成time.Time型別
在上面case中len(v) = 19,執行time.ParseInLocation(formatDateTime, s, DefaultTimeLoc),因為有問題的環境是UTC時區,所以此函式會把字串2017-01-19 00:00:00解析成time.Time2017-01-19 00:00:00 +0000 UTC(變數t,但實際是東八區的時間,正確的t應該是2017-01-19 00:00:00 +0800 CST)。

func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
……
    switch kind {
    case reflect.String:
        v := val.String()
        if fi != nil {
            if fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
                var t time.Time
                var err error
                if len(v) >= 19 {
                    s := v[:19]
                    t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
                } else {
                    s := v
                    if len(v) > 10 {
                        s = v[:10]
                    }
                    t, err = time.ParseInLocation(formatDate, s, tz)
                }
                if err == nil {
                    if fi.fieldType == TypeDateField {
                        v = t.In(tz).Format(formatDate)
                    } else {
                        v = t.In(tz).Format(formatDateTime)
                    }
                }
            }
        }
        arg = v
……
}

2.t.In(tz).Format(formatDateTime)再次將t格式化為字串,引數tz是關鍵,它是在下面程式碼中賦值的。因為MySQL設定的是東八區,所以會設定al.TZ為東八區,也就是t.In(tz).Format(formatDateTime)中tz是東八區,導致Format返回的字串是2017-01-19 08:00:00,於是就有了上面兩條SQL不同的問題。

func detectTZ(al *alias) {
    // orm timezone system match database
    // default use Local
    al.TZ = time.Local
    ...... 
    switch al.Driver {
    case DRMySQL:
        row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)")
        var tz string
        row.Scan(&tz)
        if len(tz) >= 8 {
            if tz[0] != `-` {
                tz = "+" + tz
            }
            t, err := time.Parse("-07:00:00", tz)
            if err == nil {
                al.TZ = t.Location()
            } else {
                DebugLog.Printf("Detect DB timezone: %s %s
", tz, err.Error())
            }
        }
......

相關文章