Springで日付の入力および出力パターンを統一する方法

# はじめに
Springで日付フィールドをLocalDateで定義した場合、入力パターンをDateTimeFormatなどで指定することがほとんどかと思います。、出力時も例えばJSONの場合はJsonFormatで日付の出力パターンをいちいち設定しているかと思います。
確かに時刻付の場合は、秒を含むケース、含まないケース、など、処理によって仕様が違うことは珍しくありません。
しかし、日付に関しては、大抵の場合システムで統一しているのではないかと思います。
LocalDateのような時刻を含まないパターンの場合は、個々にパターンを指定せずにシステムとして統一する方法を紹介します。

# JSONへの日付出力パターンを統一する方法
レスポンスボディへのJSON形式での出力は、jacksonが担ってます。
WebMvcConfigにて、システムとして使用している``MappingJackson2HttpMessageConverter``を取得し、そこからObjectMapperを取得、取得したObjcetMapperに日付の出力パターンを指定したシリアライザーを登録する、という流れでLocalDateで定義された変数を一律に統一するとこができます。

```java:WebMvcConfig
@Override
public void extendMessageConverters(
        List<HttpMessageConverter<?>> converters) {

    MappingJackson2HttpMessageConverter converter = null;
    // 定義済みのMappingJackson2HttpMessageConverterを取得
    for (HttpMessageConverter<?> httpMessageConverter : converters) {
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
            converter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
            break;
        }
    }

    SimpleModule module = new SimpleModule();
    // 日付の出力パターンを設定
    module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern("uuuu/MM/dd")));

    converter.getObjectMapper().registerModule(module);
}
```

# 入力パラメータの日付の入力パターンを統一する方法
ControllerAdviceアノテーションを付与したクラスで、InitBinderを付与したメソッドを作成し、``WebDataBinder#registerCustomEditor``メソッドで``LocalDate``の日付形式を定義したカスタムPropertyEditorを登録します。
下記の例は、``PropertyEditorSupport``を継承したクラスを匿名クラスで作成しています。

```java
@ControllerAdvice
public class SampleControllerAdvice
    @InitBinder
    public void initBinder(WebDataBinder dataBinder) {
    
        dataBinder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                if (!StringUtils.hasText(text)) {
                    setValue(null);
                } else {
                    try {
                        // 日付の入力パターンを設定
                        setValue(LocalDate.parse(text.trim(),
                            DateTimeFormatter.ofPattern("uuuu/MM/dd").withResolverStyle(ResolverStyle.STRICT)));
                    }
                    catch (DateTimeParseException ex) {
                        throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
                    }
                }
            }
        });
    }
}
```


# 入力がJSONの場合の日付入力パターンの統一方法
JSON形式での入力の場合は、jacksonが担ってます。
JSONでの出力と同様、``MappingJackson2HttpMessageConverter``を取得し、そこからObjectMapperを取得、取得したObjcetMapperに日付の出力パターンを指定したシリアライザーを登録する、という流れでLocalDateで定義された変数を一律に統一するとこができます。

```java:WebMvcConfig
public void extendMessageConverters(
        List<HttpMessageConverter<?>> converters) {

    MappingJackson2HttpMessageConverter converter = null;
    // 定義済みのMappingJackson2HttpMessageConverterを取得
    for (HttpMessageConverter<?> httpMessageConverter : converters) {
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
            converter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
            break;
        }
    }

    SimpleModule module = new SimpleModule();
    // ...
    
    // 日付の入力パターンを設定
    module.addDeserializer(LocalDate.class,
            new LocalDateDeserializer(
                    DateTimeFormatter.ofPattern("uuuu/MM/dd").withResolverStyle(ResolverStyle.STRICT)));

    converter.getObjectMapper().registerModule(module);
}
```

# デフォルトの入出力形式を変更したい場合
## JSONで出力する場合
出力対象のBeanにJsonFormatアノテーションを付与すれば、デフォルトの出力形式から変えることが出来ます。

```java
@JsonFormat(pattern = "uuuu-MM-dd")
LocalDate updateDate;
```
**出力例**
```json
{
    "updateDate": "2024-08-30"
}
```

## 入力パラメータの日付形式をデフォルトから変更する場合（JSON以外）
入力対象のFormにDateTimeFormatアノテーションを付与すれば、デフォルトの入力形式から変えることが出来ます。
```java
@DateTimeFormat(pattern = "uuuu-MM-dd")
private LocalDate updateDate;
```

## 入力パラメータの日付形式をデフォルトから変更する場合（JSON）
入力対象のBean（Form）にJsonFormatアノテーションを付与すれば、デフォルトの入力形式から変えることが出来ます。
```java
@JsonFormat(pattern = "uuuu-MM-dd", lenient = OptBoolean.FALSE)
private LocalDate updateDate;
```

# 最後に
日時の場合も同じような感じでできます。

ただし、Date型を使用しているケースだと、日付だけのパターンと日時のパターンが混在していると思うのでシステムとして統一というのは難しいかもしれません。

これから新規開発する場合は、日付はLocalDate、日時はLocalDateTime(もしくはZonedDateTime)で統一するのがよいかと思います。