Java8のDate and Time API(JSR 310)で文字列をparseする
みなさん、Java8使ってますか?Date and Time API(JSR 310)使ってますか?
僕はJava8でもJoda-Timeのお世話になっていたのですが、ようやくDate and Time APIを使ってみる気になりました。
僕が今までDate and Time APIを使ってこなかった理由の1つに以下があります。
存在しない日付の文字列をparseしても例外が発生せず、別の日付としてparseされてしまい、解決方法がわからなかった
(LocalDateTime#ofでは例外が発生します)
文字列をDateにparseする事ってよくあると思います。Joda-TimeのDateTime.parseなどで行っていた処理です。
Date and Time APIのLocalDateTime.parseでは、存在しない日付の文字列(例えば、‘2014/04/31’)をparseしても例外は発生せず、
別の日付としてparseされてしまいます。
ですが、最近になって解決方法がわかったので、まとめておきます。
以下の例の場合、04/31は存在しないので前日の04/30としてparseされ、テストがグリーンになります。
@Test
public void デフォルトでは存在しない日付をパースしても例外にならない() {
LocalDateTime expected = LocalDateTime.of(2014, 4, 30, 3, 4, 5, 123_000_000);
// 存在しない日付
LocalDateTime actual = LocalDateTime.parse("2014/04/31 03:04:05.123",
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"));
// 存在しない日の前日にパースされる
assertThat(actual, is(expected));
}
僕は、存在しない日付の文字列の場合、実行時例外をスローして欲しいのですが、
その場合、withResolverStyleでResolverStyleのSTRICTを指定すれば良さそうです。
ソースをこのように修正してみましょう。
@Test
public void 厳密モード() {
LocalDateTime expected = LocalDateTime.of(2014, 4, 30, 3, 4, 5, 123_000_000);
// 存在しない日付
LocalDateTime actual = LocalDateTime.parse("2014/04/31 03:04:05.123",
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS").withResolverStyle(ResolverStyle.STRICT));
assertThat(actual, is(expected));
}
java.time.format.DateTimeParseExceptionという例外がスローされました。
java.time.format.DateTimeParseException: Text '2014/04/31 03:04:05.123' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=4, DayOfMonth=31, YearOfEra=2014},ISO resolved to 03:04:05.123 of type java.time.format.Parsed
at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1919)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1854)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
やったー!できたー!と思って存在する日付(04/30など)をparseしてみると、
またしてもjava.time.format.DateTimeParseExceptionがスローされます。
@Test
public void 存在する文字列の日付をパースしてみる() {
LocalDateTime expected = LocalDateTime.of(2014, 4, 30, 3, 4, 5, 123_000_000);
// 存在する日付
LocalDateTime actual = LocalDateTime.parse("2014/04/30 03:04:05.123",
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS").withResolverStyle(ResolverStyle.STRICT));
assertThat(actual, is(expected));
}
java.time.format.DateTimeParseException: Text '2014/04/30 03:04:05.123' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=4, DayOfMonth=30, YearOfEra=2014},ISO resolved to 03:04:05.123 of type java.time.format.Parsed
at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1919)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1854)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
ISOがどうこう言ってますね。
結論から言ってしまいますが、yyyyをuuuuに変えればOKです。
ドキュメントのDateTimeFormatterを見ると、
y -> year-of-era u -> year
となっています。
うーん。。。ISO 8601的な何かでしょうか。。よくわかりません。。
詳しい方からのツッコミをお待ちしています。。
なお、SimpleDateFormatと、DateTimeFormatterの日時パターンは別物なので、注意が必要です。
DateTimeFormatterを使う時はドキュメントを確認した方が良さそうです。
今回検証に使ったソース
ここに置いてます
まとめ
- Date and Time API(JSR 310)では、文字列をparseする場合、注意が必要です
- DateTimeFormatterの日付パターンはSimpleDateFormatとは別物なので注意しましょう。DateTimeFormatterを使う時はドキュメントを読みましょう
- 存在しない日付の文字列をparseするとき、例外をスローして欲しければSTRICTを指定して、年の日付パターンは’u’を使いましょう
参考URL
DateTimeFormatter (Java Platform SE 8)
ResolverStyle (Java Platform SE 8)
SimpleDateFormat (Java Platform SE 8)
Java8 の Date & Time API ではまった。
Date and Time API 徹底攻略(4) - Date/Time その1
Java8日付時刻APIの使いづらさと凄さ