gorm.Model の時刻型の timezone が JST にならない

## はじめに
gorm.Model の timezone が UTC から JST にならず、小一時間頭を悩ませたため、その過程で得たものをアウトプットします。
Go（gorm） + PostgreSQL を使っている方が対象です。

## 環境
- Go 1.17.1
- PostgreSQL 13.4
- Gorm v2

## 結論
PostgreSQL の時刻カラムの型が、timestamp（without time zone） だったことが原因でした。
（結論だけ知りたい方向けです）

## 解決の流れ
### gorm.Model の仕組み
まずは gorm.Model の仕組みをサラッと解説します。

```go:go
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}
```
gorm.Model とは、gorm で定義されている構造体のことです。
この構造体を埋め込むことにより、簡単にコードを使い回すことができます。

`CreatedAt`、`UpdatedAt` は、フィールドの値がゼロ値であれば、レコードの更新時または作成時に現在時刻が自動で設定されます。

使い方は下記。

```go:go
type User struct {
  Name     string
  Email    string
  Password string
  gorm.Model
}
```
シンプルですね。
### timezone を変更する
gorm.Modelの `CreatedAt`、`UpdatedAt` の型を見れば、gorm の時刻には time パッケージが使われていることが分かります。
そうなると、timezone を JST にするためには、time パッケージの方式に従って timezone を変更してあげれば良さそうです。

timezone を設定している、 `time.Local`を見ていきましょう。

```go:go
// 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
```
上記のコメント通り、環境変数 `TZ`を設定します。

```zsh:.zshrc
export TZ="Asia/Tokyo"
```
```zsh:zsh
source ~/.zshrc
```
これで timezone が JST になるはずです。

先ほどのサンプルコードを再度実行してみます。

```go:go
package main

import (
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
}

func main() {
	db, _ := gorm.Open(postgres.Open("url"))
	u := new(User)
	db.Create(u)
    fmt.Println(u.CreatedAt)
}
```
```zsh:zsh
$ go run main.go
#=> 2021-11-16 19:58:24.658214 +0900 UTC
```
環境変数を設定したのに、何故か timezone が JST に変わっていません...。

### テーブルのカラムが原因
調べたところ、`created_at`、`updated_at`、`deleted_at` カラムの型が原因でした。
PostgreSQL の日付 + 時刻型には、timezone が付いている型とそうでない型の2種類があります。

- timestamp (without time zone)
- timestamp (with time zone)

公式ドキュメントによると、timezone の有無を指定しないでカラムを作成した場合は timestamp (without time zone) 型になってしまうそうです。
> 注意: 標準SQLでは、単なるtimestampという記述はtimestamp without time zoneと同じであることを要求します。 PostgreSQLはこれに準じます。 timestamp with time zoneはtimestamptzと省略することが許容されています。これはPostgreSQL独自の拡張です。

僕は timezone の有無を指定しないでカラムを作成してしまったので、今回の状況に当てはまります。

DB に接続して `ALTER TABLE` を実行し、カラムの型を timestamp (with time zone) に変えてしまいましょう。

```sql:sql
ALTER TABLE テーブル名 ALTER COLUMN created_at TYPE timestamp with time zone;
ALTER TABLE テーブル名 ALTER COLUMN updated_at TYPE timestamp with time zone;
ALTER TABLE テーブル名 ALTER COLUMN deleted_at TYPE timestamp with time zone;
```
これで再度 go を実行してみます。

```zsh:zsh
$ go run main.go
#=> 2021-11-16 19:58:24.658214 +0900 JST
```
timezone が UTC から JST になりました！:hugging:

## おわりに
go（gorm） ではなく、PostgreSQL の知識不足が原因だった気がします。
PostgreSQL は本格的にキャッチアップした経験がないので、良い機会だと思って触ってみます。
バランス良くキャッチアップすることが大事ですね...！

## 参考
- gorm
  - [https://pkg.go.dev/gorm.io/gorm](https://pkg.go.dev/gorm.io/gorm)
  - [https://gorm.io/ja_JP/docs/models.html#time_tracking](https://gorm.io/ja_JP/docs/models.html#time_tracking)
- time
  - [https://pkg.go.dev/time](https://pkg.go.dev/time)
- PostgreSQL
  - [https://www.postgresql.jp/document/9.4/html/datatype-datetime.html](https://www.postgresql.jp/document/9.4/html/datatype-datetime.html)