この記事は、昔からJavaのプログラムを書いていて、java.util.Dateやjava.util.Calendarでの日付処理に慣れ親しんでいる人が、「DateTimeFormatterはスレッドセーフなのでSimpleDateFormatより使いやすそう。でも今のコードでどう使えばいいの?」というときのヘルプになればと思います。
Javaでの日時処理
Javaでの旧世代の日時処理と言えばDateクラスとCalendarクラスです。DateクラスはJavaの一番最初期JDK1.0のころからある日時のクラス。Calendarクラスはその直後Javaの国際化の中で導入された日時のクラスです。プログラムではDateクラスは日時の入れ物に、Calendarクラスをタイムゾーン処理や日時演算に使うという使い方が多かったのではないでしょうか。
一方、Java8で新しい日時処理のクラスとして、java.timeパッケージが導入され日時の概念や処理が整理され詳細化されました。日時を文字列変換して出力するためにはDateTimeFormatterクラスが導入され、これまでSimpleDateFormatterの悩みだった「マルチスレッドで使えない」「インスタンス毎回作ると遅い」などの不満が解決されたものになっています。
DateTimeFormatterで日時出力
前置きはこれくらいにしておいて、DateTimeFormatterクラスで日付を整形して出力するにはDateTimeFormatter.formatter(TemporalAccessor)を使います。Dateクラスをこの引数にどう合わせるか、ですが、Date.toInstant()というメソッドを使うと変換ができるようになっています。DateTimeFormatterにはあらかじめ定数としてよく使いそうな書式が定義されています。例えばISO-8601の書式で出力するには次のコードで出力できます。
import java.util.Date;
import java.time.format.DateTimeFormatter;
// java.util.Dateの現在時刻をISO-8601形式(UTC)で表示
Date now = new Date();
System.out.println(DateTimeFormatter.ISO_INSTANT.format(now.toInstant()));
出力例
2021-02-01T02:33:29.291Z
ただ、他の書式、例えば RFC1123形式の Mon, 01 Feb 2021 15:00:00 +0900 で表示をするには同じコードではNGで例外が発生します。
Date now = new Date();
System.out.println(DateTimeFormatter.RFC_1123_DATE_TIME.format(now.toInstant()));
発生した例外
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
at java.time.Instant.getLong(Instant.java:603)
at java.time.format.DateTimePrintContext$1.getLong(DateTimePrintContext.java:205)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2551)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2190)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720)
この書式では時刻のオフセット、すなわちタイムゾーン情報を設定することが必要です。Dateクラスにはタイムゾーンの概念が無いのでこうなるのですかね。エラーメッセージはちょっとわかりにくいところです。
Date.toInstant()で得られたオブジェクトにatZoneメソッドでタイムゾーン情報を設定すればこの書式で表示ができるようになります。ZoneId.systemDefault()はシステムの既定のタイムゾーンを取得します。
Date now = new Date();
System.out.println(DateTimeFormatter.RFC_1123_DATE_TIME.format(now.toInstant().atZone(ZoneId.systemDefault())));
出力例
Mon, 1 Feb 2021 11:34:30 +0900
タイムゾーンを指定したい場合は以下のように指定ができます。
System.out.println(DateTimeFormatter.RFC_1123_DATE_TIME.format(now.toInstant().atZone(ZoneId.of("UTC"))));
出力例
Mon, 1 Feb 2021 02:34:30 GMT
おわりに
過去に作ったコードや各種ライブラリなどDateクラスは未だ現役で使われる場面も多いので、「内部データとしてDateは使わざるを得ない。でも表示処理など周辺は楽に書きたい」という場合にお役に立てば幸いです。
ただ、これからの日付処理ではjava.timeパッケージの日時クラスを使うのが良いと思うので、乱用はバッドノウハウになるかもしれません。十分ご注意ください。(かくいう私も結構つかってしまっています…)