DateTimeFormatter 사용할 때 파싱하려는 문자열의 Locale 정보도 꼭 주는게 좋다.

다음과 같이 DateTimeFormatter를 사용해서 “6:55 AM”이라는 문자열을 LocalTime으로 파싱하려고 한다.

@Test
public void testParsingTime() {
    String time = "6:55 AM";
    LocalTime localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("h:mm a"));
    assertThat(localTime.getHour(), is(6));
    assertThat(localTime.getMinute(), is(55));
}

코드에는 별로 크게 이상한 점이 보이지 않는다. 파싱하는 패턴이 올바른지 궁금하다면 샘플 데이터를 몇가지 더 살펴볼 수 있는데, 지금 내가 파싱하려는 데이터는 이런식이다.

7:15 AM, 8:00 AM, 11:05 AM, 1:00 PM, 1:35 PM

시간은 두자리를 꼭 채우지는 않기 때문에 h 하나만 사용하면 되고 시간과 분은 콜론(:)으로 구분하고 다음에 분은 두자리를 꼭 지키고 있기 때문에 mm을 사용했다.
마지막에 스페이스를 하나 띄고나서 a로 AM, PM을 파싱하고자 한다. 딱히 틀린건 없다고 생각했는데 막상 실행하면 에러가 난다.

java.time.format.DateTimeParseException: Text '6:55 AM' could not be parsed at index 5
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1947)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1849)
    at java.time.LocalTime.parse(LocalTime.java:441)
    at me.whiteship.domain.ShuttleTest.testParsingTime(ShuttleTest.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

이 에러를 해결하려면 Locale 정보를 추가해야 한다.

@Test
public void testParsingTime() {
    String time = "6:55 AM";
    LocalTime localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("h:mm a")
            .withLocale(Locale.ENGLISH)); // without Locale an exception may occur.
    assertThat(localTime.getHour(), is(6));
    assertThat(localTime.getMinute(), is(55));
}

현재 내가 작업중인 개발 환경의 JVM 환결 설정에 Locale 정보다 아마도 KOREA로 되어있는것 같다. 그래서 그 기본값에 따라 AM, PM 정보를 파싱하려는데, 한국어로는 그게 “오전”, “오후”로 표기하니까 AM, PM 정보를 파싱할 수 없어서 에러가 발생한 것이고, 데이터가 영어니까 영문 Locale로 파싱하도록 수정한 코드는 잘 실행된다.