[Gin] リクエストとレスポンスにlocationを指定する
Ginを使用したREST APIでDBではUTC時刻を扱っているけど、
リクエスト、レスポンスはlocationの値でやり取りしたい時にやったことをまとめました。
環境
- golang: v1.19.3
- gin-gonic/gin: v1.8.1
リクエスト
クエリパラメータ
クエリパラメータをバインドした際のtime.Time型のデフォルトlocationはLocalになっている。
特定のlocationを指定したい場合は、リクエスト構造体タグにtime_location:"{timezone}"
を追加します。
クエリ文字列からtime.Timeに変換する際に指定のlocationに設定した値が生成されます。
下記は"Asia/Tokyo"の日時を受け取り、レスポンスはUTCの値と一緒に返却する例です。
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
type MappingTime struct {
JSTTime time.Time `form:"jst_time" json:"jst_time" time_format:"2006-01-02 15:04" time_location:"Asia/Tokyo"`
}
func main() {
route := gin.Default()
route.GET("/mappingtimes", getMappingTime)
route.Run(":8080")
}
func getMappingTime(c *gin.Context) {
var m MappingTime
if err := c.ShouldBindWith(&m, binding.Query); err == nil {
// JST to UTC
utcFromJstTime := m.JSTTime.UTC()
c.JSON(http.StatusOK, gin.H{
"jst_time": m.JSTTime.Format(time.RFC3339),
"utc_time": utcFromJstTime.Format(time.RFC3339),
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"err": err.Error()})
}
}
リクエスト結果を見てみると、JST(+09:00)の時刻で受け取れています。
$curl "localhost:8080/mappingtimes?jst_time=2022-01-01%2010%3A00"
{"jst_time":"2022-01-01T10:00:00+09:00","utc_from_jst_time":"2022-01-01T01:00:00Z"}
リクエストボディ
上記のstructタグ指定はフォームのバインドのみ処理されます。
JSONでPOSTするリクエストボディの場合、Unmarshal(JSONからstructに変換)する際にlocationを指定する必要があリます。
Unmarshal
するときはUnmarshalJSON()
を実行するので、UnmarshalJSON()
でlocationを指定するカスタム関数を定義したtime.Time型を実装します。
下記はJSTで受け取り
Unmarshal時にJSTで受け取り、MarshalにもJSTで返却する場合の例です。
type JSTTime struct {
*time.Time
}
const (
layout = "2006-01-02 15:04"
)
var (
tz *time.Location
)
func init() {
tz = time.FixedZone("Asia/Tokyo", 9*60*60)
}
// Marshal
func (jt JSTTime) MarshalJSON() ([]byte, error) {
// 出力時のLocationを指定
t2 := jt.Time.In(tz)
return json.Marshal(t2.Format(layout))
}
// Unmarshal
func (jt *JSTTime) UnmarshalJSON(d []byte) error {
// JSTのlocation付きtime.Timeに変換
t, err := time.ParseInLocation(fmt.Sprintf(`"%s"`, layout), string(d), tz)
if err != nil {
return err
}
*jt = JSTTime{&t}
return err
}
レスポンスボディ
time.Timeのlocationを変更するにはtime.Time.In
に対象のlocationを指定します。
Marshal(structからJSON)に変換したレスポンスボディを使用する場合は、
前述のリクエストボディに記載していますがMarshalJSON()
内でlocationを指定する必要があります。
内部で使用する時刻
DBのクエリなどに指定する場合など、入出力以外の時刻はtime.UTC()
を使用して明示的にUTCの時刻を扱うようにしました。
Discussion