Gorm: UpdatedAt column is always set to time.Time

Created on 19 Mar 2018  Â·  9Comments  Â·  Source: go-gorm/gorm

What version of Go are you using (go version)?

go version go1.9.1 windows/amd64

Which database and its version are you using?

PostgreSQL 9.4/9.5

Please provide a complete runnable program to reproduce your issue. IMPORTANT

Need to runnable with GORM's docker compose config or please provides your config.

package main

import (
    "fmt"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

var db *gorm.DB

func init() {
    var err error
    db, err = gorm.Open("postgres", "user=gorm password=gorm DB.name=gorm port=9920 sslmode=disable")
    if err != nil {
        panic(err)
    }
    db.LogMode(true)
}

type Model struct {
    ID uint `gorm:"primary_key"`
    //Created_at an updated_at are of type int because we will save time in UNIX timestamp format
    CreatedAt int
    UpdatedAt int
}

//BeforeUpdate is a hook to set the created_at column to UNIX timestamp int.
func (m *Model) BeforeUpdate(scope *gorm.Scope) error {
    scope.SetColumn("UpdatedAt", int(time.Now().Unix()))
    return nil
}

//BeforeCreate is a hook to set the created_at column to UNIX timestamp int.
func (m *Model) BeforeCreate(scope *gorm.Scope) error {
    scope.SetColumn("CreatedAt", int(time.Now().Unix()))
    return nil
}

type User struct {
    Model
    Name string
}

func main() {
    oUser := User{
        Name: "Cool Name",
    } //this is the original user
    db.Create(&oUser)
    user := User{
        Name: "New Name",
    }
    db.Where("id = ?", 1).First(&oUser)
    err := db.Debug().Model(&oUser).Updates(user).Error
    if err != nil {
        fmt.Println("failed")
    } else {
        fmt.Println("success")
    }
}

The issue is that UpdatedAt column is ALWAYS set to time.Time no matter what.

Most helpful comment

@meilihao, that works! I just replace the callback functions, thanks! 🥇

func CreateConnection(c config.Configuration) (*gorm.DB, error) {
    connectionUri := fmt.Sprintf(
        "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local",
        c.Database.Username,
        c.Database.Password,
        c.Database.Host,
        c.Database.Database,
    )

    db, err := gorm.Open("mysql", connectionUri)
    if err != nil {
        log.Fatalf("error open connection: %s", err)
        return nil, err
    }

    db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
    db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)

    return db, nil
}

func updateTimeStampForCreateCallback(scope *gorm.Scope) {
    if !scope.HasError() {
        now := time.Now().UTC().Unix()

        if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
            if createdAtField.IsBlank {
                createdAtField.Set(now)
            }
        }
    }
}

func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
    if _, ok := scope.Get("gorm:update_column"); !ok {
        now := time.Now().UTC().Unix()
        scope.SetColumn("UpdatedAt", now)
    }
}

All 9 comments

why don't use gorm.Model, see the doc

and you should use the real column name updated_at or created_at.

check your database's column real name.

I have created my own Model because:

  • GORM allows you to, so you can create extra columns and scope their changes.
  • I want my time to be unix time in int64 rather than timestamp column in the database.

The real names are fine, it just doesn't accept changing the updated_at column from timestamp to int64

@SAFAD I'm running into the same problem with updated_at and deleted_at. Did you find a solution?

@SAFAD Do you find a solution ? I'm facing to the same problem.

@mooncool I ended up creating my own model struct and specifying my own hooks for the timestamps. Note - I left deletedAt as is because I don't care so much that that value is int64, and I didn't want to mess w/ gorm's default indexing/filtering by it.

type Model struct {
    ID        uint       `gorm:"primary_key" json:"id"`
    CreatedAt int64      `json:"createdAt"`
    UpdatedAt int64      `json:"updatedAt"`
    DeletedAt *time.Time `sql:"index" json:"deletedAt"`
}

func (m *Model) BeforeUpdate(scope *gorm.Scope) error {
    scope.SetColumn("UpdatedAt", time.Now().Unix())
    return nil
}

func (m *Model) BeforeCreate(scope *gorm.Scope) error {
    if m.UpdatedAt == 0 {
        scope.SetColumn("UpdatedAt", time.Now().Unix())
    }

    scope.SetColumn("CreatedAt", time.Now().Unix())
    return nil
}

@mooncool I ended up creating my own model struct and specifying my own hooks for the timestamps. Note - I left deletedAt as is because I don't care so much that that value is int64, and I didn't want to mess w/ gorm's default indexing/filtering by it.

type Model struct {
  ID        uint       `gorm:"primary_key" json:"id"`
  CreatedAt int64      `json:"createdAt"`
  UpdatedAt int64      `json:"updatedAt"`
  DeletedAt *time.Time `sql:"index" json:"deletedAt"`
}

func (m *Model) BeforeUpdate(scope *gorm.Scope) error {
  scope.SetColumn("UpdatedAt", time.Now().Unix())
  return nil
}

func (m *Model) BeforeCreate(scope *gorm.Scope) error {
  if m.UpdatedAt == 0 {
      scope.SetColumn("UpdatedAt", time.Now().Unix())
  }

  scope.SetColumn("CreatedAt", time.Now().Unix())
  return nil
}

@inoda Many thanks!

Can we close this? Seems like @inoda has a good solution.

use own model struct, why create is ok and update,delete failed?

type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt int64
  UpdatedAt int64
  DeletedAt *int64
}

func (m *Model) BeforeUpdate(scope *gorm.Scope) error {
    scope.SetColumn("UpdatedAt", time.Now().Unix())
    return nil
}

func (m *Model) BeforeCreate(scope *gorm.Scope) error {
    if m.UpdatedAt == 0 {
        scope.SetColumn("UpdatedAt", time.Now().Unix())
    }

    scope.SetColumn("CreatedAt", time.Now().Unix())
    return nil
}

func (m *Model) BeforeDelete(scope *gorm.Scope) error {
    scope.SetColumn("DeletedAt", time.Now().Unix())
    return nil
}
...
u:=User{Name:"ad"}
db.Create(&u)
db.Model(&u).Update("name", "hello")
db.Delete(&u)

log:

(/home/chen/tmpfs/db.go:100) 
[2019-05-15 11:09:13]  [0.51ms]  INSERT INTO "users" ("created_at","updated_at","deleted_at","name") VALUES ('1557889753','1557889753',NULL,'ad') RETURNING "users"."id"

(/home/chen/tmpfs/db.go:102) 
[2019-05-15 11:09:13]  pq: 无效的整数类型输入语法: "2019-05-15T11:09:13.17873335+08:00" 

(/home/chen/tmpfs/db.go:102) 
[2019-05-15 11:09:13]  [0.52ms]  UPDATE "users" SET "name" = 'hello', "updated_at" = '2019-05-15 11:09:13'  WHERE "users"."deleted_at" IS NULL AND "users"."id" = '6'

(/home/chen/tmpfs/db.go:106) 
[2019-05-15 11:09:13]  pq: 无效的整数类型输入语法: "2019-05-15T11:09:13.179478181+08:00" 

(/home/chen/tmpfs/db.go:106) 
[2019-05-15 11:09:13]  [0.40ms]  UPDATE "users" SET "deleted_at"='2019-05-15 11:09:13'  WHERE "users"."deleted_at" IS NULL AND "users"."id" = '6'

May i replace gorm's default callback func?

db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
db.Callback().Delete().Replace("gorm:delete", deleteCallback)

@meilihao, that works! I just replace the callback functions, thanks! 🥇

func CreateConnection(c config.Configuration) (*gorm.DB, error) {
    connectionUri := fmt.Sprintf(
        "%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local",
        c.Database.Username,
        c.Database.Password,
        c.Database.Host,
        c.Database.Database,
    )

    db, err := gorm.Open("mysql", connectionUri)
    if err != nil {
        log.Fatalf("error open connection: %s", err)
        return nil, err
    }

    db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
    db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)

    return db, nil
}

func updateTimeStampForCreateCallback(scope *gorm.Scope) {
    if !scope.HasError() {
        now := time.Now().UTC().Unix()

        if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
            if createdAtField.IsBlank {
                createdAtField.Set(now)
            }
        }
    }
}

func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
    if _, ok := scope.Get("gorm:update_column"); !ok {
        now := time.Now().UTC().Unix()
        scope.SetColumn("UpdatedAt", now)
    }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

hypertornado picture hypertornado  Â·  3Comments

koalacxr picture koalacxr  Â·  3Comments

Ganitzsh picture Ganitzsh  Â·  3Comments

izouxv picture izouxv  Â·  3Comments

sredxny picture sredxny  Â·  3Comments