LoginSignup
26
21

More than 5 years have passed since last update.

Golangのgormで、カスタマイズしたtime.Timeに差し変える

Last updated at Posted at 2015-08-18

gormというORMで、デフォルトで定義されている論理削除可能な構造体は、下記のようなものですが、

// gorm.Model
type Model struct {
    ID        uint64 `gorm:"primary_key"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt *time.Time
}

これをクライアント側に返却するとき、時刻をJSONエンコードなりTextエンコードなりすると思いますが、
そのまま使うと時刻文字列のフォーマットが、RFC3339Nanoというナノ秒まで持つフォーマットを使って変換されてしまいます。

// Time Format
const (
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
)

// JSON Encode
func (t Time) MarshalJSON() ([]byte, error) {
    if y := t.Year(); y < 0 || y >= 10000 {
        // RFC 3339 is clear that years are 4 digits exactly.
        // See golang.org/issue/4556#c15 for more discussion.
        return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
    }
    return []byte(t.Format(`"` + RFC3339Nano + `"`)), nil
}

クライアントによっては、ナノ秒など解釈できないものがあるためかなり不便です。
普通そんな細かい精度の時刻など必要としない場合の方が多いため、 ミリ秒以下を切り捨てたいところです。

そんなときは、時刻のタイプ自体をカスタマイズすることで解決することが出来ます。

restime.go
package typedef

import (
    "database/sql/driver"
    "errors"
    "time"
)

// XXX: アプリでナノ秒は必要ないので秒まででフォーマット
//{
//    "createdAt": "2015-08-09T15:03:05.135166971Z",
//    "objectID": "55c112a40000a1"
//}
// See: http://qiita.com/taizo/items/2c3a338f1aeea86ce9e2
type ResourceTime struct {
    time.Time
}

func (rt ResourceTime) MarshalJSON() ([]byte, error) {
    t := rt.Time
    if y := t.Year(); y < 0 || y >= 10000 {
        return nil, errors.New("ResourceTime.MarshalJSON: year outside of range [0,9999]")
    }
    return []byte(t.Format(`"` + time.RFC3339 + `"`)), nil
}

func (rt ResourceTime) MarshalText() ([]byte, error) {
    t := rt.Time
    if y := t.Year(); y < 0 || y >= 10000 {
        return nil, errors.New("Time.MarshalText: year outside of range [0,9999]")
    }
    return []byte(t.Format(time.RFC3339)), nil
}

// Sql driver interface

func (rt *ResourceTime) Scan(value interface{}) error {
    rt.Time = value.(time.Time)
    return nil
}

func (rt ResourceTime) Value() (driver.Value, error) {
    return rt.Time, nil
}

やってることは、

  • エンコードの時刻文字列フォーマットを秒数まで出力するものに変更
  • SQL Driverが実装すべきメソッドを定義(ScanValue)

これだけでクライアントに返却する時刻文字列のフォーマットを変更することができました。

type Model struct {
    ID        uint64 `gorm:"primary_key"`
    CreatedAt ResourceTime `json:"createdAt"`
    UpdatedAt ResourceTime `json:"updatedAt"`
    DeletedAt *time.Time   `json:"deletedAt,omitempty"`
}

未解決事項

論理削除で使われる DeleteAtは、time.Timeへのポインターで定義されていますが、これを *ResourceTimeに変更するとMySQLにカラムは追加されますが、実行時、レコード追加するときにエラーになります。
今のところよくわからなかったので、別途 view objectみたいな構造体を定義して詰め直し、時刻フォーマットを変更する方法で回避しました。
詳細は、この辺参考 JSON and struct composition in Go

参考

環境

  • Golang 1.4.2
  • MySQL 5.6.26
26
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21