この記事は、昔から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
パッケージの日時クラスを使うのが良いと思うので、乱用はバッドノウハウになるかもしれません。十分ご注意ください。(かくいう私も結構つかってしまっています…)