Goで様々なフォーマットの時刻JSONをtime.Timeとして扱う

# はじめに
始めまして。最近業務でGoを書いているハセガワカンタです。
先日、みなさんも日常的に使っているであろうtime.Timeの仕様で困ったことがありました。使用しているAPIの関係で以下のようなJSONをtime.Timeとして扱わなければいけなくなったのです。

```json
{
    "date": "2024-01-01"
}
```
```go
type Example struct {
	Date time.Time `json:"date"`
}
```
実はこれ普通にUnmarshalしても上手くいきません

```go
type Example struct {
	Date time.Time `json:"date"`
}

func main() {
	j := `{"date": "2024-01-01"}`
	var e Example
	json.Unmarshal([]byte(j), &e)
	fmt.Println("date is", e.Date)
}

```
```
date is 0001-01-01 00:00:00 +0000 UTC
```

# time.TimeのMarshalerとUnmarshaler
なぜこれがUnmarshal出来ないのかpkg.go.devを読んでみました。
> MarshalJSON implements the json.Marshaler interface. The time is a quoted string in the RFC 3339 format with sub-second precision. If the timestamp cannot be represented as valid RFC 3339 (e.g., the year is out of range), then an error is reported.

> UnmarshalJSON implements the json.Unmarshaler interface. The time must be a quoted string in the RFC 3339 format.

https://github.com/golang/go/blob/821f94103b2980758cdb21ac6ae834ef0a07235a/src/time/time.go#L1360-L1389
```go
// MarshalJSON implements the json.Marshaler interface.
// The time is a quoted string in the RFC 3339 format with sub-second precision.
// If the timestamp cannot be represented as valid RFC 3339
// (e.g., the year is out of range), then an error is reported.
func (t Time) MarshalJSON() ([]byte, error) {
	b := make([]byte, 0, len(RFC3339Nano)+len(`""`))
	b = append(b, '"')
	b, err := t.appendStrictRFC3339(b)
	b = append(b, '"')
	if err != nil {
		return nil, errors.New("Time.MarshalJSON: " + err.Error())
	}
	return b, nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// The time must be a quoted string in the RFC 3339 format.
func (t *Time) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		return nil
	}
	// TODO(https://go.dev/issue/47353): Properly unescape a JSON string.
	if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
		return errors.New("Time.UnmarshalJSON: input is not a JSON string")
	}
	data = data[len(`"`) : len(data)-len(`"`)]
	var err error
	*t, err = parseStrictRFC3339(data)
	return err
}
```

どうやらRFC3339というフォーマットにしか対応していないようなので、MarshalerとUnmarshalerを弄って他のフォーマットにも対応させる必要があります。

# 実装
このように特定のフォーマットで決め打ちしても出来ますが、今回は汎用性を持たせるために別の実装にしました
```go
type YYYYMMDD time.Time

func (d *YYYYMMDD) UnmarshalJSON(b []byte) error {
	s := string(b)
	t, err := time.Parse(`"2006-01-02"`, s)
	if err != nil {
		return err
	}
	*d = YYYYMMDD(t)
	return nil
}

func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
	return []byte(time.Time(d).Format(`"2006-01-02"`)), nil
}
```

MultiFormatTimeという様々なフォーマットに対応できる型を作成し、Unmarshal時に採用したフォーマット形式を保持してMarshal時にも対応できるようにしています。
```go
// NOTE: Formatフィールドを持つことでUnMarshal時にどのフォーマットでパースされたかを保持し、それを元にMarshal時に元のフォーマットで出力する
type MultiFormatTime struct {
	time.Time
	Format string
}

// NOTE: 一致するフォーマットが存在しない場合はこのフォーマットで出力する
const DEFAULT_FORMAT = time.RFC3339

// NOTE: 必要になったらフォーマットを追加する
var formats = []string{
	time.RFC3339,
	time.DateOnly,
}

func (mt *MultiFormatTime) UnmarshalJSON(b []byte) error {
	s := string(b)
	var err error
	var t time.Time
	for _, format := range formats {
		t, err = time.Parse(`"`+format+`"`, s)
		if err == nil {
			mt.Time = t
			mt.Format = format
			return nil
		}
	}

	return err
}

func (mt MultiFormatTime) MarshalJSON() ([]byte, error) {
	if mt.Format != "" {
		return json.Marshal(mt.Time.Format(mt.Format))
	}
	return json.Marshal(mt.Time.Format(DEFAULT_FORMAT))
}

```

# ライブラリ
timeパッケージに定義されているフォーマット群にあらかじめ対応したものをパッケージとして公開しているのよければ使ってください。
https://github.com/KantaHasegawa/multi_format_time