package models import ( "database/sql/driver" "encoding/json" "strings" "time" ) // NullTime wraps *time.Time with custom JSON and GORM handling: // - JSON marshal: nil → "1970-01-01T00:00:00Z", non-nil → RFC3339 // - JSON unmarshal: null / "" / "1970-01-01..." → nil (stored as NULL in DB) // - GORM Scan/Value: nil ↔ NULL column type NullTime struct { Time *time.Time } func (nt NullTime) MarshalJSON() ([]byte, error) { if nt.Time == nil { return []byte(`"1970-01-01T00:00:00Z"`), nil } return json.Marshal(nt.Time.Format(time.RFC3339)) } func (nt *NullTime) UnmarshalJSON(data []byte) error { if string(data) == "null" { nt.Time = nil return nil } var s string if err := json.Unmarshal(data, &s); err != nil { return err } if s == "" || strings.HasPrefix(s, "1970-01-01") { nt.Time = nil return nil } t, err := time.Parse(time.RFC3339, s) if err != nil { return err } nt.Time = &t return nil } // Value implements driver.Valuer so GORM writes NULL for nil. func (nt NullTime) Value() (driver.Value, error) { if nt.Time == nil { return nil, nil } return *nt.Time, nil } // Scan implements sql.Scanner so GORM reads NULL as nil. func (nt *NullTime) Scan(value interface{}) error { if value == nil { nt.Time = nil return nil } t, ok := value.(time.Time) if !ok { nt.Time = nil return nil } nt.Time = &t return nil }