GORM Playground Link
https://github.com/go-gorm/playground/pull/821
Description
A model has time.Time
field with a default tag. When saving a list of these models, that time.Time
field doesn't get updated (you can see it in the SQL). After removing the default tag, it gets updated correctly. Also if saving just one model (not a list), it also gets updated.
This problem doesn't happen with int
field, that also has a default tag.
type Price struct {
ProductId int `gorm:"primaryKey"`
Price int `gorm:"default:1"`
SomeTime time.Time `gorm:"default:'2015-10-22T14:00:00Z'"`
}
func main() {
prices := []Price{
{ProductId: 1, Price: 100, SomeTime: time.Now()},
{ProductId: 2, Price: 150, SomeTime: time.Now()},
}
// Will correctly create two rows
db.Debug().Save(prices)
// Will NOT update some_time field (check SQL below)
//
// [1.647ms] [rows:2] INSERT INTO "prices" ("price","some_time","product_id") VALUES
// (100,'2015-10-23 00:00:00',1),(150,'2024-06-06 21:42:19.527',2) ON CONFLICT ("product_id")
// DO UPDATE SET "price"="excluded"."price" RETURNING "some_time","product_id"
prices[1].SomeTime = time.Now().Add(-10000 * time.Hour)
db.Debug().Save(prices)
}
Comment From: PCloud63514
I've reviewed the issue and was able to reproduce the same behavior locally as demonstrated in the playground.
After digging into the root cause, I identified the following problem:
The first problematic behavior occurs in gorm.io/gorm/schema/field.go at line 278:
case reflect.Struct:
if _, ok := fieldValue.Interface().(*time.Time); ok {
field.DataType = Time
} else if fieldValue.Type().ConvertibleTo(TimeReflectType) {
field.DataType = Time
} else if fieldValue.Type().ConvertibleTo(TimePtrReflectType) {
field.DataType = Time
}
if field.HasDefaultValue && !skipParseDefaultValue && field.DataType == Time {
if t, err := now.Parse(field.DefaultValue); err == nil { // << Issue
field.DefaultValueInterface = t
}
}
At the line now.Parse(field.DefaultValue), an error is triggered:
Can't parse string as time: '2015-10-22T14:00:00Z'
Can't parse string as time: '2015-10-22T14:00:00Z'
cannot convert "2015-10-22 14:00:00" constant to []string
The root cause is that the parsing fails in github.com/jinzhu/now at line 196:
if t, err = now.parseWithFormat(str, currentLocation); err == nil {
As a result, field.DefaultValueInterface = t is never reached, leaving DefaultValueInterface as nil.
This becomes an issue later in gorm.io/gorm/callbacks/create.go at line 373, where the following condition fails:
// field.DefaultValueInterface != nil << Issue
for _, column := range values.Columns {
if field := stmt.Schema.LookUpField(column.Name); field != nil {
if v, ok := selectColumns[field.DBName]; (ok && v) || (!ok && !restricted) {
if !field.PrimaryKey && (!field.HasDefaultValue || field.DefaultValueInterface != nil ||
strings.EqualFold(field.DefaultValue, "NULL")) && field.AutoCreateTime == 0 {
...
}
}
}
}
Because the DefaultValueInterface is nil, the some_time column is not included in the update clause during an ON CONFLICT scenario.
This leads to the final query being executed as:
INSERT INTO `prices` (`price`,`some_time`,`product_id`) VALUES
(100,"2025-08-01 00:57:05.888",1),
(1500,"2024-06-10 08:57:05.889",5)
ON CONFLICT (`product_id`) DO UPDATE SET `price`=`excluded`.`price` RETURNING `some_time`,`product_id`
Note that some_time is missing in the DO UPDATE clause due to the issue above.
Comment From: PCloud63514
As a temporary workaround for this issue, you can explicitly include the target column in the update clause as shown below:
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "product_id"}},
DoUpdates: clause.AssignmentColumns([]string{"price", "some_time"}),
}).Create(&prices)
By explicitly specifying the update columns, you can bypass the issue and ensure that the some_time field is updated correctly.
Comment From: PCloud63514
Ah, there was a mistake in my previous comment. There is no issue with github.com/jinzhu/now!
The actual root cause is due to the use of single quotes in the struct tag definition:
SomeTime time.Time `gorm:"default:'2015-10-22T14:00:00Z'"`
Because of the surrounding '', the default value is parsed as a quoted string, and the single quotes are passed to the now.Parse function, which causes a parsing error.
Once the quotes are removed like below, everything works as expected:
SomeTime time.Time `gorm:"default:2015-10-22T14:00:00Z"`
I'm not sure if GORM is intentionally allowing or requiring quotes around default values in this context, but it might be worth clarifying whether this behavior is by design or not.
result query:
INSERT INTO `prices` (`price`,`memo`,`some_time`,`product_id`) VALUES (100,"pass","2025-08-01 23:17:14.558",1),(1500,"CONV","2024-06-11 07:17:14.559",5) ON CONFLICT (`product_id`) DO UPDATE SET `price`=`excluded`.`price`,`memo`=`excluded`.`memo`,`some_time`=`excluded`.`some_time` RETURNING `product_id`
Comment From: mosceo
I know why I used quotes in the first place. When I put this tag gorm:"default:2015-10-22 14:00:00Z
, GORM returned a syntax error when creating the table. Notice, there is not T
in the date. But with quotes around the date it worked. When there is T
, but no timezone at the end, it also returns an error (gorm:"default:1970-01-01T00:00:00"
)
I also noticed that with the tag gorm:"default:0001-01-01T00:00:00Z"
, GORM refused to create a table:
ERROR: date/time field value out of range: "0000-00-00 00:00:00" (SQLSTATE 22008)
[4.005ms] [rows:0] CREATE TABLE "prices" ("product_id" bigserial,"price" bigint,"some_time" timestamptz DEFAULT '0000-00-00 00:00:00',PRIMARY KEY ("product_id"))
But when there are quotes gorm:"default:'0001-01-01T00:00:00Z'"
, it creates it correctly:
[6.915ms] [rows:0] CREATE TABLE "prices" ("product_id" bigserial,"price" bigint,"some_time" timestamptz DEFAULT '0001-01-01T00:00:00Z',PRIMARY KEY ("product_id"))