日付の取り扱い
日付の取り扱いに関しては Java 8 から新しいAPIが導入されました。
今まで標準のAPIで Date, Calendar を使用していた日付操作が LocalDate, LocalDateTime 等に変わってきています。
日付の初期化
今回は LocalDate の簡単な使い方と使用時の注意点、実はこんな便利な使い方があったというものを紹介していきます。
後半には取り扱い方の注意点を紹介していますので最後まで見てみてください。
現在日で初期化
現在日を取得するために Java8 以前では、以下のようなコードを書いていました。
Date currentDate1 = new Date();
Calendar currentDate2 = Calendar.getInstance();
しかしこのコードでは日付だけでなく時間も含まれているため日付のみ使用する場合には注意が必要でした。
例えばある日付の間にある情報を取得したい場合にこの時間が影響してしまい、意図しないデータがとれてきてしまうこともあります。
意図しない動きをしないように時間部分をカットする必要があります。
final LocalDate currentDate = LocalDate.now();
System.out.println(currentDate); // 2020-01-15
LocalDate を使用し現在日を設定する場合は now メソッドを使用します。
LocalDate の場合、年月日がセットされ時間以降は取り扱われません。
日付を指定して初期化
日付を指定して初期化するために Java8 以前では、以下のようなコードを書いていました。
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2019);
calendar.set(Calendar.MONTH, 11);
calendar.set(Calendar.DAY_OF_MONTH, 25);
この書き方の場合月は0はじまりのため、11をセットすると12月になります。
またこれも時間がついてくるため、時間のカットが必要になります。
LocalDate targetDate = LocalDate.of(2019, 12, 25)
System.out.println(targetDate); // 2019-12-25
日付を指定して初期化を行う場合 of メソッドを使用します。
Date や Calendar に比べると日付の設定が直感的になり楽になりました。
文字から日付を初期化
文字から日付を取得するために Java8 以前では、以下のようなコードを書いていました。
DateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
Date date = formatter.parse("2019/12/25");
こちらは見たまま直感的に文字列から日付へ変更できています。
しかし実は SimpleDateFormat はスレッドセーフではありません。
スレッドプログラミングやWebアプリケーションを作る際に、誤った使い方をすると別のユーザーの日付が書き換わるといった現象が発生してしまいます。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate targetDate = LocalDate.parse("2019/12/25", formatter);
System.out.println(targetDate); // 2019-12-25
日付の文字列から LocalDate 型のインスタンスを生成します。
DateTimeFormatter スレッドセーフのため安心です。
LocalDate targetDate = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.parse("2019/12/25", LocalDate::from);
System.out.println(targetDate); // 2019-12-25
このようなコードも書けます。
上記のコードは一見すると良さそうに見えますが問題があります。
[問題1] 正確な日付の取り扱い
先ほどのコードを真似て以下のコードを書いてみました。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.parse("2020/02/30", LocalDate::from);
System.out.println(targetDate); // 2020-02-29
2020年2月は29日までしかなく、30日を指定してもエラーにならず勝手に29日に変換されました。
一見便利にみえますが、正確な日付チェックや変換を行いたい場合には向いていません。
そこで以下のように書き換えます。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.withResolverStyle(ResolverStyle.STRICT)
.parse("2020/02/30", LocalDate::from);
System.out.println(targetDate); // 例外発生
ResolverStyle.STRICT を設定することで日付を厳密に解決するため、不正な日付の場合は例外が発生するようになります。
[問題2] 日付フォーマット
先程のプログラムを少し変えて日付文字列のスラッシュがないバージョンを作りました。
LocalDate targetDate = DateTimeFormatter.ofPattern("yyyyMMdd")
.withResolverStyle(ResolverStyle.STRICT)
.parse("20191225", LocalDate::from);
System.out.println(targetDate); // ???
こちらのコードは正しいように見えますが出力時に例外が発生します。
DateTimeFormatter (Java Platform SE 8)
上記のドキュメントを見ると以下のように書かれています。
Symbol | Meaning | Presentation | Examples |
---|---|---|---|
g | era | text | AD; Anno Domini; A |
u | year | year | 2004; 04 |
y | year-of-era | year | 2004; 04 |
フォーマットのシンボル y には year-of-era と表記されています。
era は暦を表しており先程の STRICT で厳密に日付を取り扱ったことで暦を判定できずエラーとなっています。
LocalDate targetDate = DateTimeFormatter.ofPattern("uuuuMMdd")
.withResolverStyle(ResolverStyle.STRICT)
.parse("20191225", LocalDate::from);
System.out.println(targetDate); // 2019-12-25
シンボルを y から u へ変更したところ、日付の変換が成功しました。
LocalDate targetDate = DateTimeFormatter.ofPattern("GGGGyy年MM月dd日")
.withLocale(Locale.JAPAN)
.withChronology(JapaneseChronology.INSTANCE)
.withResolverStyle(ResolverStyle.STRICT)
.parse("平成30年12月25日", LocalDate::from);
System.out.println(targetDate); // 2018-12-25
ちなみに和暦を取り扱う場合は上記のように書きます。
※ 和暦の取り扱いは厳密には難しいためここでは簡易的な変換のみ紹介です
まとめ
Java 8 から日付の取り扱い方がかなり簡単になりました。
しかし使用時には色々な注意点があることもわかりました。
ネットで調べれば使い方はすぐに出てきますが、本当にそのやり方が正しいかはきちんとした知識がないと判断できません。
今回紹介した内容も含めてきちんと公式のドキュメントを見ることも一緒におすすめします。