みなさん、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の使いづらさと凄さ