본문 바로가기
이슈와해결

Gson 베이스 프로젝트에서 LocalDateTime 컨버팅 지옥 탈출하기

by Renechoi 2024. 2. 18.

이글에 대해서

사내 프로젝트에서 사용하는 Gson을 개선하면서 작성한 글입니다. 특히 날짜/시간 포맷 관련된 컨버팅 로직에 대한 해결책을 제시합니다. 레거시 호환을 위해 Jackson 대신 Gson을 사용하면서, 스프링과의 호환성 문제로 인해 종종 컨버팅 오류가 발생하는데, LocalDateTime 처리 문제도 그 중 하나입니다. 단순 컨버팅 이슈부터 여러 서비스에서 일관성 없는 설정들 때문에 코드베이스가 복잡해지는 상황을 개선하고 싶었고, 더 이상 시간 관련 컨버팅 이슈에 신경을 쓰지 않는 것을 목적으로 가능한 모든 경우를 호환할 수 있는 글로벌한 LocalPeriodTime 컨버터를 구현하게 됩니다. 실제 코드는 컨셉 코드로 대체하였습니다.

 


 

요약

Gson을 사용하는 현 프로젝트에서 LocalPeriodTime 이 제대로 컨버팅 되지 못하는 문제를 커스텀 Parser와 Converter를 구현하여 해결합니다. 본 글에서는 해당 로직을 구현하는 과정과 작동 방식에 대해 설명합니다. 마지막으로 향후 발생 가능할 법한 디버깅 시나리오에 대해서 다루고, 디버깅 방법론을 제안합니다.

 

문제 상황

Gson을 사용한 직렬화/역직렬화 과정에서 LocalDateTime 파싱을 자꾸 제대로 못하는 상황이 발생합니다. 프로젝트마다 LocalDateTime을 다루는 패턴이 상이합니다.

 

원하는 것

시간이라고 인식되는 문자열은 그냥 무조건 다 LocalDateTime으로 파싱하고 싶습니다. 더 이상 LocalDateTime을 String으로 파싱할 건지, LocalDate로 파싱할 건지, ZoneTime으로 파싱할 건지, 무엇을 쓰더라도 시간 컨버팅 지옥에서 벗어나고 싶습니다. 😈

 

접근 방법

문제와 원하는 것이 명확한 상황입니다. 이를 달성하고자 다음과 같이 접근했습니다.

  • 가능한 모든 경우에 대해서 일단 테스트 코드에서 전부 케이스를 모아놓고
  • 테스트 코드가 통과할 때까지 로직을 구현해 본 다음에
  • 로직이 모두 통과한다면
  • 컨버팅 이슈가 더 이상 발생하지 않지 않을까 ?!
  • 즉, 가능한 모든 시간 패턴에 대한 컨버팅이 테스트 코드를 통과한다면 이로써 컨버팅 로직은 신뢰성을 보장받는 것이 아닐까?

이와 같은 사고 흐름에 따라 간단한 컨버터를 구상했습니다. 다음 컨버터는 String source 를 받아서 LocalDateTime을 리턴합니다.

 @Override
 public LocalDateTime convert(String source) {}

 

구현 과정

1. 가능한 모든 케이스에 대한 테스트스위트 작성

먼저 가능한 모든 케이스를 모아봤습니다.

class CustomLocalDateTimeParserTest {

    private final CustomLocalDateTimeParser parser = new CustomLocalDateTimeParser(
            ListCollection.of(new IsoLocalDateTimePattern())
    );

    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 1")
    void convert_PossibleString_ReturnsLocalDateTime_1() {
        String possibleString = "2024-01-30";

    }

    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 2")
    void convert_PossibleString_ReturnsLocalDateTime_2() {
        String possibleString = "2024-01-30T15:45";

    }

    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 3")
    void convert_PossibleString_ReturnsLocalDateTime_3() {
        String possibleString = "2024-01-30T15:45+01:00";

    }

// 이하 생략

 

이와 같은 케이스를 모아 보니 40개가 모였습니다.

테스트 구성을 갖춘 테스트 코드 작성

그런 다음 테스트를 실행할 수 있도록 given when then 구조에 맞게 테스트를 작성해주었습니다.

class CustomLocalDateTimeParserTest {

    private final CustomLocalDateTimeParser parser = new CustomLocalDateTimeParser(
            ListCollection.of(new IsoLocalDateTimePattern())
    );

    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 1")
    void convert_PossibleString_ReturnsLocalDateTime_1() {
        String possibleString = "2024-01-30";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 0, 0);
        LocalDateTime actualDateTime = parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }

    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 2")
    void convert_PossibleString_ReturnsLocalDateTime_2() {
        String possibleString = "2024-01-30T15:45";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 15, 45);
        LocalDateTime actualDateTime =  parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }

 

즉 시간 형식으로 추정되는 문자열이 들어왔을 때 해당 문자열은 LocalDateTime으로 파싱됨을 보장하는 테스트 세트입니다.

 

이제 테스트를 실패시키기 위해 프로덕션 코드의 함수를 구현했습니다.

 

케이스가 다양하기 때문에 처음부터 파싱 패턴에 따른 전략패턴 시나리오를 구성하였습니다.

 

@Operator
@RequiredArgsConstructor
public class CustomLocalDateTimeParser {

    private final List<LocalDateTimePattern> localDateTimePatterns;
    public LocalDateTime parseAsLocalDateTime(String source) {
        return localDateTimePatterns.stream()
                .map(pattern -> pattern.parse(source))
                .filter(Objects::nonNull)
                .findFirst()
                .orElse(null);
    }
}

 

 

public interface LocalDateTimePattern {
    LocalDateTime parse(String source);
}

 

 

@Component
public class IsoLocalDateTimePattern implements LocalDateTimePattern{
    @Override
    public LocalDateTime parse(String source) {

        try {
            return LocalDateTime.parse(source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        } catch (DateTimeParseException e) {
            return null;
        }
    }
}

 

또한 패턴 케이스가 얼마나 많이 나올지 모르기 때문에 컴포넌트로 구성했는데요. 이부분에서 util성 객체로서 static으로 만들지 bean으로 만들지를 고민했는데, 패턴이 많아지면 의존성을 주입해줄 때 코드가 길어질 것을 고려해서 bean 방식을 선호했습니다.

 

이 부분은 사용성에 따라서 static하게 변경할 수도 있을 것입니다!

 

이제 간단한 프로덕션 로직을 구성했으니,

40개의 케이스에 대해서 이와 같은 테스트를 작성하고 돌려볼 차례입니다.

 

Iso 패턴에 대해서만 등록하고 돌려보았습니다.

 

  private final CustomLocalDateTimeParser parser = new CustomLocalDateTimeParser(
            ListCollection.of(new IsoLocalDateTimePattern())
    );

 

 

 

깔끔하게 실패할 것으로 기대했는데 생각보다 많이 통과했네요.

 

즉, 40개의 케이스 중에 5개는 Iso 포맷으로 컨버팅이 되었음을 알 수 있습니다.

 

이제 나머지 35개의 케이스를 통과시켜줄 다른 패턴을 등록해주면 될 것입니다.

 

패턴 등록하기

Iso 외에 다음과 같이 Dash(Hyphen), slash, 한국어, 그리고 기타 등등의 패턴에 대해서 등록했습니다.

@Component
public class HyphenBasedTimePattern implements LocalDateTimePattern {

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(

            // t가 없는 경우
            DateTimeFormatter.ofPattern("dd-MM-yyyy"),
            DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm"),
            DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"),

            DateTimeFormatter.ofPattern("yyyy-MM-dd"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"),

            // t가 있는 경우
            DateTimeFormatter.ofPattern("dd-MMM-yyyy'T'HH:mm:ss"),

            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmX"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    );

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}
@Component
public class SlashDateTimePattern implements LocalDateTimePattern {

    private static final List<DateTimeFormatter> SLASH_FORMATTERS = Arrays.asList(
            DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"),
            DateTimeFormatter.ofPattern("MM/dd/yyyy"),
            DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss a"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
    );

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : SLASH_FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}
@Component
public class KoreanDateTimePattern implements LocalDateTimePattern {

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
            DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분"),
            DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"),
            DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분 ss초")
    );

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}
@Component
public class CustomDateTimePattern implements LocalDateTimePattern {

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(

            DateTimeFormatter.ofPattern("yyyyMMddHHmm"),
            DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss"),
            DateTimeFormatter.ofPattern("MMMM dd, yyyy HH:mm"),
            DateTimeFormatter.ofPattern("yyyy.MM.dd"),
            DateTimeFormatter.ofPattern("dd'th' MMMM yyyy HH:mm a"),
            DateTimeFormatter.ofPattern("MMMM dd, yyyy HH:mm:ss")
    );

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}

 

등록하고 나니 꽤 많은 테스트가 이제 통과하기 시작했는데...

 

 

성공률을 보면 16 : 24 => 성공률 60%였습니다 !! ㅠㅠ

실패한 케이스를 보니깐 다음과 같았습니다.

 

@Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 1")
    void convert_PossibleString_ReturnsLocalDateTime_1() {
        String possibleString = "2024-01-30";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 0, 0);
        LocalDateTime actualDateTime = parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }
    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 3")
    void convert_PossibleString_ReturnsLocalDateTime_3() {
        String possibleString = "2024-01-30T15:45+01:00";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 15, 45);
        LocalDateTime actualDateTime = parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }
    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 4")
    void convert_PossibleString_ReturnsLocalDateTime_4() {
        String possibleString = "30-01-2024";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 0, 0);
        LocalDateTime actualDateTime = parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }
    @Test
    @DisplayName("요청 string이 날짜 형식이라면 LocalDateTime 으로 변환한다 - 5")
    void convert_PossibleString_ReturnsLocalDateTime_5() {
        String possibleString = "2024/01/30";
        LocalDateTime expectedDateTime = LocalDateTime.of(2024, 1, 30, 0, 0);
        LocalDateTime actualDateTime = parser.parseAsLocalDateTime(possibleString);
        assertEquals(expectedDateTime, actualDateTime, "컨버팅 에러 !!!");
    }

 

3번의 경우 ISO 8601 형식이라고 하여 등록되지 않은 패턴으로 실패하는 것이 당연했습니다. ISO 패턴에서 모든 ISO 패턴을 처리할 수 있도록 패턴을 리팩터링했습니다.

 

@Component
public class IsoLocalDateTimePattern implements LocalDateTimePattern{

    private static final List<DateTimeFormatter> ISO_FORMATTERS = Arrays.asList(
            DateTimeFormatter.ISO_LOCAL_DATE_TIME,
            DateTimeFormatter.ISO_DATE_TIME,
            DateTimeFormatter.ISO_INSTANT,
            DateTimeFormatter.ISO_OFFSET_DATE_TIME,
            DateTimeFormatter.ISO_ZONED_DATE_TIME,
            DateTimeFormatter.ISO_DATE,
            DateTimeFormatter.ISO_TIME
    );

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : ISO_FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}

 

이와 같은 ISO로 고도화로 3번 케이스는 통과시켰습니다 !

 

 

문제는 1, 4, 5 번이었습니다. 이 항목들은 분명히 이미 등록되어 있는 패턴인데도 파싱을 하지 못했습니다.

 

1번의 경우 "2024-01-31" 케이스로 본 작업의 시작이 된 LocalDate 타입이었습니다.

 

찾아 보니 해당 패턴의 경우 패턴을 명시해주어도 LocalDateTime으로 변환이 가능하지 않았는데요.

 

LocalDateTime.parse 메서드가 기본적으로 해당 타입을 변환하려면 시간 정보가 필요하다는 것이었습니다. "yyyy-MM-dd"는 형식은 시간 정보가 없기 때문에 LocalDateTime 객체 생성이 불가했습니다.

 

   new DateTimeFormatterBuilder()
                    .appendPattern("yyyy-MM-dd")
                    .optionalStart()
                    .appendLiteral(' ')
                    .appendPattern("HH:mm:ss")
                    .optionalEnd()
                    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                    .toFormatter()

 

따라서 LocalDateTime 류에 대해서는 위의 구현에서처럼 시간을 명시적으로 제공해주는 방식으로 구현을 해주어야 패턴을 인식했습니다.

이 방식으로 했을 때 마침내 LocalDate가 깔끔하게 파싱됐습니다.

 

 

4, 5번 케이스도 모두 LocalDate 류로서 같은 방식으로 가능했습니다.

 

문제는 defaultValue를 설정해줄 때 DateTimeFormatterBuilder를 연속으로 append 하는 방식으로는 동작하지 않는다는 점입니다. 그래서 각각의 DateTimeFormatterBuilder를 만들어주어야 했습니다.

 

중복되는 코드를 묶어서 간략하게 구성했습니다.

@Component
public class DefaultValueTimePattern implements LocalDateTimePattern {

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
            createFormatter("yyyy-MM-dd"),
            createFormatter("dd-MM-yyyy")
    );

    private static DateTimeFormatter createFormatter(String pattern) {
        return new DateTimeFormatterBuilder()
                .appendPattern(pattern)
                .optionalStart()
                .appendLiteral(' ')
                .appendPattern("HH:mm:ss")
                .optionalEnd()
                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                .toFormatter();
    }

    @Override
    public LocalDateTime parse(String source) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDateTime.parse(source, formatter);
            } catch (DateTimeParseException ignored) {
            }
        }
        return null;
    }
}

 

실패하는 테스트 중에 해당 패턴으로 해결할 수 있는 다른 패턴으로

"yyyy/MM/dd", "yyyy.MM.dd" 가 있었다. 이 둘을 해결하고 나니 이제 실패하는 테스트가 8개가 되었네요 !

🥵

 

나머지 실패하는 케이스에는

        String possibleString = "2024년 1월 30일 18시 30분";
        String possibleString = "2024년 01월 30일";
        String possibleString = "30-January-2024";
        String possibleString = "2024년 1월 30일 00시 00분 00초";
        String possibleString = "Feb 29, 2024 23:59:59";
        String possibleString = "2024년 2월 29일 23시 59분 59초";

 

과 같이 문자가 들어간 케이스가 있었고

 

        String possibleString = "2024/01/30T15:45:30.123Z";

 

과 같이 zone을 포함한 케이스가 있었습니다.

 

다음과 같은 패턴을 추가하였습니다.

 

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
            createFormatter("yyyy-MM-dd"),
            createFormatter("dd-MM-yyyy"),
            createFormatter("yyyy/MM/dd"),
            createFormatter("MM/dd/yyyy"),

            createFormatter("yyyy.MM.dd"),
            createFormatter("dd-MMMM-yyyy"),
            createFormatter(          "yyyy년 MM월 dd일")
    );

 

이로써 40개의 모든 테스트를 통과하게 되었습니다 ! 😂 👏🏻👏🏻👏🏻

 

 

누락된 케이스들을 통과시키기 위한 치트키로서

프로젝트의 yml과 gson config에 설정되어 있는 포맷들을 가져와서 비교하여 현재 테스트스위트에서 다루지 못한 케이스가 있는지를 GPT를 통해서 분석하여 추가 테스트를 더 작성했습니다.

 

그렇게 하여 10가지 케이스를 더 추출하여 총 50개의 테스트를 완성했습니다.

 

그 테스트 중에서도 2개가 실패하여 같은 과정을 거쳐 패턴을 등록해주었습니다.

 

그리고 마지막으로 최종 치트키로서 현재까지 등록된 패턴을 GPT에게 알려주고 동일한 류의 패턴에서 누락된 것이 없는지 판단하여 비슷한 패턴은 모조리 등록할 수 있도록 했습니다.

 

사용하기

MVC Resolver

먼저 스프링 Mvc 컨버팅 과정에서 Dto 내부의 필드로서 LocalDateTime을 파싱해야 하는 경우에 사용할 수 있습니다.

 

Springframework의 core 패키지의 converter 를 커스텀으로 구현하고 이를 등록해주어야 합니다.

 

다음과 같이 구현했습니다.

@Component
@RequiredArgsConstructor
public class CustomStringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {

    private final ConversionService conversionService;
    private final CustomLocalDateTimeParser customLocalDateTimeParser;

    @Override
    public LocalDateTime convert(String source) {
        if (StringUtils.isBlank(source)) {
            return passConversionService(source);
        }
        try {
            return customLocalDateTimeParser.parseAsLocalDateTime(source);
        } catch(Exception e){
            return passConversionService(source);
        }
    }

    private LocalDateTime passConversionService(String source) {
        if (conversionService != null) {
            return conversionService.convert(source, LocalDateTime.class);
        }
        return null;
    }
}
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(customStringToLocalDateTimeConverter);
    }
}

 

다음과 같은 엔드포인트에 진입되는 Dto가 제대로 파싱되는지를 이제 보겠습니다.

 

    @GetMapping("/search")
    @Operation(summary = "조회 - 검색(page)")
    public ResponseEntity<Page<Info>> search(@Validated SearchRequest searchRequest, @ParameterObject Pageable pageable) {
        return OK(facade.search(searchRequest, pageable));
    }

 

커스텀 CustomStringToLocalDateTimeConverter를 정상 통과했고

 

 

컨트롤러까지 정상 도착하였습니다.

 

 

HttpMessageConverter

@RequestBody 로 선언된 Dto를 파싱할 때의 프로세스에서 사용되는 컨버터에 대해서는 현재의 프로젝트에서는

GsonDateTimeAdaptor 가 시간에 대한 converting을 수행하고 있습니다.

 

프로젝트의 영향도를 고려하여 현재 진행중인 PMS 프로젝트에서는 MVC에 대해서만 사용하도록 등록한 상태입니다.

 

GsonDateTimeAdaptor의 로직을 대체할 수 있을까요?

 

한 스텝씩 테스트해보면서 변경해보면 어떨까 싶어요.

기타 사용시

Component로 등록해 놓았기 때문에 간단히 의존성 추가하고 사용하면 됩니다.

@Autowired
private CustomStringToLocalDateTimeConverter converter;

LocalDateTime localDateTime = converter.convert(source);

디버깅 필요시

50 패턴에 대해 테스트했고 왠만한 패턴은 다 등록해놓았는데 여전히 안되는 경우가 있을 수 있습니다.

 

그럴 때에는 마찬 가지로 테스트 세트에 안되는 패턴을 등록하고, 해당 패턴이 등록하도록 LocalDateTimePattern의 구현체 중 하나에 해당하는 패턴 문자열을 등록하는 것으로 쉽게 해결될 것으로 기대해볼 수 있습니다.

 

현재 등록한 대분류로서 패턴 종류는 다음과 같이 있습니다.

IsoLocalDateTimePattern, HyphenBasedTimePattern, SlashDateTimePattern, KoreanDateTimePattern, DefaultValueTimePattern, CustomDateTimePattern

이 중에 하나인 HyphenBasedTimePattern를 예로 들자면,

    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(

            // t가 없는 경우
            DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm"),
            DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"),

            DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"),
            DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss.SSS"),

            // 생략

 

위와 같은 FORMATTERS 에 필요한 패턴을 추가하면 될 것입니다.

 

꼭! 테스트를 먼저 등록하면 좋을 것 같습니다.

추가 테스트 코드

실제로 해당 Converter가 사용되는 사례에서 gson이 componet로서 프로젝트에서 관여하고 있기 때문에 보다 더 정확도를 높이려면 @SpringBootTest로서 다양한 환경에서 변환 테스트가 필요하다고 생각합니다. 가장 대표적인 예시가 실제로 Mvc Dto를 변환하는 작업이 진행되는 로직을 테스트하려면 통합테스트로서 컨트롤러 파싱 과정을 테스트해야 할 필요가 있습니다.

 

이에 대한 테스트는 향후 과제로 남겨 놓도록 하겠습니다.

반응형