GORMとPostgreSQLのtimezone?

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を確認した結果はこちら
1_K4rZ9j7TfC_EZmtgYnniKg

DBのカラムがtimezoneを保持していないと、Golang(GORM)で変数にバインドしたtime型のlocationはUTCとなる(①)。timezoneを保持している場合は、Golang(GORM)実行環境のtimezoneが設定される。

個人的には、DBとGolang実行環境でシステムは分離されているので、DBの設定を優先させても良いかと思いました(③)。

どうするかは、これから検討しようと思います。