
GORMとPostgreSQLのtimezone?
こんにちは、Anti-Pattern Inc.の塚本です。
弊社ではGolang使用してアプリケーション開発をしています。DBはPostgreSQLでORマッパーにGORMを利用しています。
Golangコンテナ、DBコンテナはタイムゾーンを指定していないのでデフォルト値が適用されています。これをJSTにしたかった!というブログです。
■Golangコンテナ
$ docker exec -it 58d644eaec04 bash
root@58d644eaec04:/app# date
**Sat May 8 10:43:18 UTC 2021**
■DBコンテナ
root@2e3c2a3ad755:/# psql -U ***** -d *****
psql (11.9 (Debian 11.9-1.pgdg90+1))
Type "help" for help.
=> show timezone;
TimeZone
------------
**UTC**
time.Now()を出力した結果です。UTCが設定されています
now := time.Now()
pretty.Println(now)
// 一部省略
time.Time{
wall: 0xc01dba5be36c39a0,
ext: 9264217901,
loc: &time.Location{
name: "Local",
zone: {
{name:"UTC", offset:0, isDST:false},
},
extend: "UTC0",
},
}
time packageのzoneinfo.goを見ると、Locationが設定される優先順位が記述されています。
// Local represents the system's local time zone.
// On Unix systems, Local consults the **TZ environment**
// variable to find the time zone to use. No TZ means
// use the system default **/etc/localtime**.
// TZ="" means use UTC.
// TZ="foo" means use file foo in the system timezone directory.var Local *Location = &localLoc
コンテナにJSTのtimezoneを設定します
RUN apt-get update && apt-get install -y tzdata
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ENV TZ=Asia/Tokyo
イメージをリビルドして再起動した結果です
OSのtimezoneがJSTに変更されてます
$ docker exec -it 05be8001430e bash
root@05be8001430e:/app# date
**Sat May 8 20:34:47 JST 2021**
root@05be8001430e:/app# strings /etc/localtime
TZif2
TZif2
JST-9
time.Now()を出力した結果です。 name: “Asia/Tokyo”に変更され、JSTで出力されます
now := time.Now()
pretty.Println(now)
// 一部省略
time.Time{
wall: 0xc01dbbf41503be10,
ext: 320877338601,
loc: &time.Location{
name: "Asia/Tokyo",
zone: {
{name:"LMT", offset:33539, isDST:false},
{name:"JDT", offset:36000, isDST:true},
{name:"JST", offset:32400, isDST:false},
{name:"JST", offset:32400, isDST:false},
},
extend: "JST-9",
},
}
pretty.Println(now.String())
2021-05-08 20:48:48.8286243 +0900 **JST** m=+2.744851101
今回は直接変更して検証しています
ALTER DATABASE "****" SET timezone TO 'Asia/Tokyo';
$ docker exec -it f27946c4ba27 bash
root@f27946c4ba27:/# psql -U *** -d *****
psql (11.9 (Debian 11.9-1.pgdg90+1))
Type "help" for help.
=> show timezone;
TimeZone
------------
Asia/Tokyo
データ登録時のログで確認してますが、現在日時(JST)で設定されています。ここまでは想定通りです。
INSERT INTO "test_names" ("created_at","updated_at","deleted_at","name") VALUES ('2021-05-08 21:08:45.757','2021-05-08 21:08:45.757',NULL,'test') RETURNING "id"
次にデータ取得
DBから取得したデータのCreatedAtをログに出力します。
https://pkg.go.dev/gorm.io/gorm#Model
pretty.Println(result.Model)
pretty.Println(result.Model.CreatedAt.String())
pretty.Println(result.Model.CreatedAt.Location())
gorm.Model{
ID: 0x5,
CreatedAt: time.Time{
wall: 0x2d28f578,
ext: 63756104925,
loc: (*time.Location)(nil),
},
UpdatedAt: time.Time{
wall: 0x2d28f578,
ext: 63756104925,
loc: (*time.Location)(nil),
},
DeletedAt: gorm.DeletedAt{},
}
2021-05-08 21:08:45.757659 +0000 UTC
&time.Location{
name: "UTC",
zone: nil,
tx: nil,
extend: "",
cacheStart: 0,
cacheEnd: 0,
cacheZone: (*time.zone)(nil),
}
あれ?UTCになってる。locがnil ムムム・・
ちなみにDBのカラム定義はtimezoneを持ってません
created\_at | timestamp without time zone
では、timezoneを持たせて確認
ALTER TABLE tableName ALTER COLUMN created\_at TYPE timestamp with time zone
locに設定されて、timezoneはJSTと判定されてます(これが期待していた結果です)
// 抜粋
gorm.Model{
ID: 0x5,
CreatedAt: time.Time{
wall: 0x2d28f578,
ext: 63756072525,
loc: &time.Location{
name: "Asia/Tokyo",
zone: {
{name:"LMT", offset:33539, isDST:false},
{name:"JDT", offset:36000, isDST:true},
{name:"JST", offset:32400, isDST:false},
{name:"JST", offset:32400, isDST:false},
},
extend: "JST-9",
cacheStart: 9223372036854775807,
cacheEnd: 9223372036854775807,
cacheZone: &time.zone{(CYCLIC REFERENCE)},
},
},
UpdatedAt: time.Time{
wall: 0x2d28f578,
ext: 63756104925,
loc: (*time.Location)(nil),
},
DeletedAt: gorm.DeletedAt{},
}
2021-05-08 21:08:45.757659 +0900 JST
&time.Location{
name: "Asia/Tokyo",
zone: {
{name:"LMT", offset:33539, isDST:false},
{name:"JDT", offset:36000, isDST:true},
{name:"JST", offset:32400, isDST:false},
{name:"JST", offset:32400, isDST:false},
},
extend: "JST-9",
cacheStart: 9223372036854775807,
cacheEnd: 9223372036854775807,
cacheZone: &time.zone{(CYCLIC REFERENCE)},
}
DBとGolangの実行環境のtimezoneを変えて取得したデータのtimezoneを確認した結果はこちら

DBのカラムがtimezoneを保持していないと、Golang(GORM)で変数にバインドしたtime型のlocationはUTCとなる(①)。timezoneを保持している場合は、Golang(GORM)実行環境のtimezoneが設定される。
個人的には、DBとGolang実行環境でシステムは分離されているので、DBの設定を優先させても良いかと思いました(③)。
どうするかは、これから検討しようと思います。