Goのシステムで日付の扱い方
背景
グローバルなシステムで日付を扱うときは、システム内部ではUTCを扱った方が、柔軟に日付変換の対応ができる
msecのunixtimestampでDBに入れたほうが良い
secでも問題ないならそれでも良いが、そんなシステムは稀だと思われる
メリット
変換ミスが起きない(unix timestampなので、変換できないことはないはず)
検索パフォーマンスが良い?
TODO:実際に検証
デメリット
数値の羅列なので、読みづらい
都度変換が必要(要件による)
前提知識
/etc/localtime
現在のタイムゾーン情報の設定ファイル(バイナリ)
/etc/timezone
debian系で使われているタイムゾーン設定ファイル(テキスト)
/usr/share/zoneinfo
タイムゾーン情報の設定ファイル郡
time.Localは/etc/localtimeと環境変数のTZに依存する
TZ指定されていない場合はtime.Localを使用
time.Localの初期値は/etc/localtimeの設定値
TZ指定する場合はTZを/usr/share/zoneinfo/などから取得
time.ParseはデフォルトUTCになるので要注意
2018-01-01 00:00:00 (JST)のようにTZが指定されている場合はそのTZになる
特定のTZに変換するときはtime.ParseInLocation()を使う
code:go
input := "2021/06/18 13:00:10.123"
tz := "America/New_York"
format := "2006/01/02 15:04:05.999"
loc, _ := time.LoadLocation(tz)
t, _ := time.ParseInLocation(format, input, loc)
fmt.Println(t) // 2021-06-18 13:00:10.123 -0400 EDT
日付をUTCに変換するときはt.UTC()を使う
code:go
t := time.Now()
ut := t.UTC()
fmt.Println(t) // 2021-06-19 16:03:48.941514 +0900 JST m=+0.000145084
fmt.Println(ut) // 2021-06-19 07:03:48.941514 +0000 UTC
UTCの日付を指定したTZの日付に変換する場合はt.In()を使う
code:go
t := time.Now()
fmt.Println(t) // 2021-06-19 16:19:57.196421 +0900 JST m=+0.000156126
fmt.Println(t.UTC()) // 2021-06-19 07:19:57.196421 +0000 UTC
loc, _ := time.LoadLocation("Asia/Tokyo")
fmt.Println(t.UTC().In(loc)) // 2021-06-19 16:19:57.196421 +0900 JST
検証内容
前提条件
InとOutはTZに沿って日付を変換する仕様とする
TZのはタイムゾーンIDとする
InとOutのフォーマットは 2006/01/02 15:04:05.999
検証環境
M1 Mac
ORM:xorm
検証コード
code:go
package main
import (
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
"xorm.io/xorm"
)
func newDB() *xorm.Engine {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&loc=UTC",
"test", "test", "localhost", "3307", "test")
db, err := xorm.NewEngine("mysql", dsn)
if err != nil {
log.Fatal(err)
}
return db
}
type User struct {
Created int64 xorm:"created"
}
func main() {
db := newDB()
_, err := db.Exec("truncate table user")
if err != nil {
log.Fatal(err)
}
input := "2021/06/18 13:00:10.123"
tz := "America/New_York"
format := "2006/01/02 15:04:05.999"
loc, err := time.LoadLocation(tz)
if err != nil {
log.Fatal(err)
}
t, err := time.ParseInLocation(format, input, loc)
if err != nil {
log.Fatal(err)
}
fmt.Println(t)
msec := t.UTC().UnixNano() / int64(time.Millisecond)
fmt.Println("original time", input)
fmt.Println("original tz", tz)
fmt.Println("save time", t.UTC())
fmt.Println("save msec", msec)
data := User{Created: msec}
// 構造体で、カラム名がcreatedの場合、日付の桁数が削れられてしまう(msec未対応のため?)
// ので、NoAutoTime()を使用する必要がある
if _, err := db.NoAutoTime().Table("user").Insert(data); err != nil {
log.Fatal(err)
}
users := []User{}
if err := db.Find(&users); err != nil {
log.Fatal(err)
}
fmt.Println("got msec", c)
got := time.Unix(0, c*int64(time.Millisecond)).UTC().In(loc)
fmt.Println("got time", got.Format(format))
}
結果
code:sh
MacbookPro13% go run main.go
2021-06-18 13:00:10.123 -0400 EDT
original time 2021/06/18 13:00:10.123
original tz America/New_York
save time 2021-06-18 17:00:10.123 +0000 UTC
save msec 1624035610123
got msec 1624035610123
got time 2021/06/18 13:00:10.123
MacbookPro13%
参考資料