Spring에서 날짜/시간을 처리하며 배운 점을 정리해 보았다.
어떤 표준 시간대를 기준으로 요청을 처리해야 할까? 개인적인 결론은 API 요청/응답, 프로그램 로직, DB 저장까지 UTC 기준으로 처리하는 것이다. 항상 UTC로 전달하고 사용자에게 표시하는 순간에 클라이언트가 적절하게 표현해주는 것이 가장 편리했다. Open API인 경우 시간대 정보를 함께 받고 싶을 수도 있는데 이 부분은 다음 글에서 GitHub REST API의 표준 시간대를 구현해 보겠다.
JDK 8 버전의 JDBC에서는 날짜/시간 타입이 다음과 같이 연결된다고 한다.
DATE - LocalDate
TIME - LocalTime
TIMESTAMP WITHOUT TIME ZONE - LocalDateTime
TIMESTAMP WITH TIME ZONE - OffsetDateTime
그러므로 필요에 따라 Local* 친구들을 쓰는 것도 좋겠다.
RequestBody를 제외한 @RequestParam, @PathVariable, @ModelAttribute에는 Spring의 Converter 인터페이스 구현체가 적용된다.
String -> LocalDateTime은 Jsr310Converters.StringToLocalDateTimeConverter이 사용되고, LocalDateTime -> String은 별도의 구현체가 없다.
Converter<String, LocalDateTime>을 구현하는 클래스를 등록해서 변환 방식을 설정할 수 있다.
RequestBody의 직렬화/역직렬화에는 Jackson이 사용된다. 예전에는 따로 설정해줘야 했는데 3.2.5 버전에서 테스트해보니 DTO 내의 LocalDateTime 속성도 잘 역직렬화/직렬화 된다. (만약 배열 형태로 응답한다면 설정의 spring.jackson.serialization.write-dates-as-timestamps: false로 설정해주자)
"2024-06-01T13:30:50"
시간대 정보가 포함되지 않으니 JS에서 파싱할 때 UTC임을 명시해줘야 한다. 필드에 @JsonFormat 어노테이션을 붙여서 설정할 수 있다.
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss 'HELLO'")
private LocalDateTime jsonFormat; // -> "2024-06-01 13:30:50 HELLO"
그런데 필드마다 이걸 붙이려니 정말 고생길이 훤하다. 다행히 Configuration에서 한 번에 설정할 수도 있다. (스프링 부트에서는 AutoConfigure를 망칠 수 있으므로 ObjectMapper 빈을 등록하지 말고 커스터마이저를 사용하자)
@Configuration
public class JsonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder
.serializers(localDateTimeSerializer());
}
private LocalDateTimeSerializer localDateTimeSerializer() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss 'DEFAULT'");
return new LocalDateTimeSerializer(formatter);
}
}
그러면 등록한 패턴에 맞게 잘 응답한다.
"2024-06-01 13:30:50 DEFAULT"
필요한 경우 StdDeserializer<?>, StdSerializer<?>를 상속받아 구현할 수도 있다.
다음 글에서는 GitHub REST API의 표준 시간대 결정 방식을 따라서 구현해 보자!!!(https://docs.github.com/ko/rest/using-the-rest-api/timezones-and-the-rest-api)