LoginSignup
11
3

More than 3 years have passed since last update.

gormと仲良くなりたい(2) - datetimeでマイクロ秒(6桁)まで格納/取得する

Last updated at Posted at 2019-12-18

はじめに

GoでORMを使用する時の定番である gorm のお話2つ目。
以下動作は Databse MySQL5.7 の場合です。

gorm の Auto Migrations は便利なんですが time.Time が絡むと地味に厄介です。
今回のネタは上記の問題を解決して マイクロ秒(6桁)まで格納する までの検証です。

検証: デフォルト

gormのタグを何も定義せずに time.Time型 で Auto Migrations してみましょう。

今回の検証とは関係ないですがMySQL/Goでは time.Time を使用したい場合DatabaseUrlに?parseTime=true をしていおかないと上手く time.Time 型にパース出来ない事があります。

以下の実装では

  • table作成(Drop/AutoMigrate)
  • データ投入 (Create) => 2006-01-02 15:04:05.999999999
  • レコード取得1件 (First)

を行います。

package main

import (
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
    "time"
)

type Sample struct {
    SampleDate time.Time
}

func main() {
    db, err := gorm.Open("mysql", "test:test@/sample?parseTime=true")
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()

    // Drop Table
    if err := db.DropTableIfExists(&Sample{}).Error; err != nil {
        panic(err.Error())
    }
    // Migrate the schema
    if err := db.AutoMigrate(&Sample{}).Error; err != nil {
        panic(err.Error())
    }

    // Create
    dt := time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.UTC)
    fmt.Printf("insert data = %+v\n", dt)
    db.Create(&Sample{SampleDate: dt})

    var result Sample
    if err := db.First(&result).Error; err != nil {
        panic(err.Error())
    }
    fmt.Printf("result: %+v\n", result)
}
> SHOW CREATE TABLE samples;
CREATE TABLE `samples` (
  `sample_date` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
insert data = 2006-01-02 15:04:05.999999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05 +0000 UTC}

timestamp型でColumnが作られました。
timestamp型には以下の問題があります。

  • デフォルトでは秒までしか入らない
  • 2038-01-19 03:14:07 までしか格納できない2038年問題が発生する

Selectの結果を見ても秒までしか取得できていないことが分かります。

解決策: datetime(6) 型で定義する

公式Docに書いてありますが gorm では structタグ で型を定義できます。
SampleDateのタグに gorm:"type:datetime(6)" を設定します。

type Sample struct {
    SampleDate time.Time `gorm:"type:datetime(6)"`
}

実行結果は以下の通り。

insert data = 2006-01-02 15:04:05.999999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05.999999 +0000 UTC}

マイクロ秒(6桁)まで正しく取得できました。
マイクロ秒は9桁までありますがMySQLでは datetime(6) 以上の精度では設定できません。
7桁目以降の精度の値は切り捨てられている点は注意が必要です。

おまけ: ミリ秒を使いたい

マイクロ秒ではなくミリ秒を使いたい場合もあると思います。
その場合は、ミリ秒まで格納できるよう datetime(3) のColumnを定義します。

type Sample struct {
    SampleDate time.Time `gorm:"type:datetime(3)"`
}

マイクロ秒まで持ったtime.Timeを格納すると

    dt := time.Date(2006, 1, 2, 15, 4, 5, 123999999, time.UTC)
    fmt.Printf("insert data = %+v\n", dt)
    db.Create(&Sample{SampleDate: dt})
insert data = 2006-01-02 15:04:05.123999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05.124 +0000 UTC}

四捨五入されます。
MySQL5.5まではミリ秒以上の精度のデータは切り捨て
MySQL5.6以降の場合はミリ秒以上の精度のデータは四捨五入されます。
テストデータでうっかり四捨五入されてtestに失敗しないよう注意が必要です。

まとめ

  • time.Timeの定義はgormではデフォルトでtimestamp型になる
  • structタグで色々出来ます公式Docを読みましょう
  • MySQL/Goではマイクロ秒は6桁までしか精度が担保できない
  • datetime(3) (ミリ秒) を使う場合はマイクロ秒の精度の値が格納された際の考慮が必要
11
3
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
11
3