Java : Date, CalendarではなくLocalDateTime, ZonedDateTimeを使おう

Java 8から日付・時刻の新しいAPIが追加されました。
Instant, LocalTime, LocalDate, LocalDateTime, ZonedDateTimeなどです。
これらは古いAPIであるDate, Calendarなどを改善したものです。


用語の定義

本記事では、時間に関する用語を以下のように定義します。

用語 定義
日付 年と月と日 2021/5/4
2000年12月31日
時刻 時と分と秒 12:30:59
7時30分45秒
日時 日付と時刻 2021/5/4 12:30:59
2000年12月31日 7時30分45秒
時点 時間軸上の一点。
(具体的には、Dateは1970年1月1日00:00:00 GMTからのミリ秒数、Instantは1970-01-01T00:00:00Zからの秒とナノ秒)
1609504200.123 (2021-01-01T12:30:00.123Z)

新APIと旧APIの機能比較

新/旧 API 時点 日付 時刻 タイムゾーン 不変 スレッドセーフ
Instant × × ×
LocalDate × × ×
LocalTime × × ×
LocalDateTime × ×
ZonedDateTime ×
ZoneId × × × × ×
Date × × × × ×
Calendar × × ×
TimeZone × × × × ×

新APIでは、全体的に機能が細分化されました。
そして、タイムゾーンのあり・なしが明確に分離されました。

日付だけしか使わない場合はLocalDate、時刻だけしか使わない場合はLocalTimeで済みます。
日時にタイムゾーンが不要であればLocalDateTimeを使いましょう。

旧APIだと、なんでもかんでもCalendarでした。

1つのクラスはなるべく最小限の機能だけにして小さくする…設計の基本ですね。

あと、新APIはすべてのクラスが不変です。
不変であるということはスレッドセーフでもあります。

関連記事:不変オブジェクト(イミュータブル) とは

新APIの簡単な紹介

Java : 日付・時刻の基本」で、新APIの基本的な使い方を紹介しています。
そちらを参照していただけたら幸いです。

旧APIの簡単な紹介

Date

クラスDateは、特定の時点を表します(精度はミリ秒)。

新APIのInstantに相当するクラスです。
Java 1.0ではカレンダー的な機能もありましたが、Java 1.1で非推奨になりました。

Calendar

Calendarクラスは、特定の時点とYEAR、MONTH、DAY_OF_MONTH、HOURなどのカレンダ・フィールド・セット間の変換、および次週の日付の取得などのカレンダ・フィールド操作を行うための抽象クラスです。 特定のインスタントは、1970年1月1日00:00:00.000 GMT (グレゴリオ暦)を元期とするミリ秒単位のオフセットで表現できます。

新APIのZonedDateTimeに相当するクラスです。
日付・時刻の操作、計算、取得などひととおりのことができます。

TimeZone

TimeZoneは、タイムゾーン・オフセットを表します。また、サマー・タイムを認識します。

新APIのZoneId(とZoneOffset)に相当するクラスです。
サマータイムも考慮さています。

相互運用も可能

古いライブラリなどでは、DateやCalendarクラスをパラメータに要求するものがあるかもしれません。
その場合は、新旧APIの変換を行いましょう。

旧API → 新API

// Date(旧API) → Instant(新API)
final Date date = new Date(946684800123L);
System.out.println(date); // Sat Jan 01 09:00:00 JST 2000

final Instant instant = date.toInstant();
System.out.println(instant); // 2000-01-01T00:00:00.123Z
System.out.println(instant.getEpochSecond()); // 946684800
System.out.println(instant.getNano()); // 123000000
// TimeZone(旧API) → ZoneId(新API)
final TimeZone timeZone = TimeZone.getDefault();
System.out.println(timeZone.getID()); // Asia/Tokyo

final ZoneId zoneId = timeZone.toZoneId();
System.out.println(zoneId); // Asia/Tokyo

(Calendarから直接 ZonedDateTimeに変換するAPIはなさそう?)

// Calendar(旧API) → ZonedDateTime(新API)
final Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime()); // Thu May 20 13:12:43 JST 2021

final Instant instant = calendar.toInstant();
System.out.println(instant); // 2021-05-20T04:12:43.803Z

final ZoneId zoneId = calendar.getTimeZone().toZoneId();
System.out.println(zoneId); // Asia/Tokyo

final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(zonedDateTime); // 2021-05-20T13:12:43.803+09:00[Asia/Tokyo]

新API → 旧API

// Instant(新API) → Date(旧API)
final Instant instant = Instant.ofEpochSecond(946684800L, 123456789L);
System.out.println(instant); // 2000-01-01T00:00:00.123456789Z

final Date date = Date.from(instant);
System.out.println(date); // Mon Jan 12 08:00:08 JST 1970

// ミリ秒より小さい桁は切り捨てられます。
System.out.println(date.getTime()); // 946684800123
// ZoneId(新API) → TimeZone(旧API)
final ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Tokyo

final TimeZone timeZone = TimeZone.getTimeZone(zoneId);
System.out.println(timeZone.getID()); // Asia/Tokyo

(ZonedDateTimeから直接 Calendarに変換するAPIはなさそう?)

// ZonedDateTime(新API) -> Calendar(旧API)
final ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime); // 2021-05-20T13:28:23.460856+09:00[Asia/Tokyo]

final Instant instant = zonedDateTime.toInstant();
System.out.println(instant); // 2021-05-20T04:28:23.460856Z

final TimeZone timeZone = TimeZone.getTimeZone(zonedDateTime.getZone());
System.out.println(timeZone.getID()); // Asia/Tokyo

final Calendar calendar = Calendar.getInstance(timeZone);
calendar.setTime(Date.from(instant));

System.out.println(calendar.getTime()); // Thu May 20 13:28:23 JST 2021

まとめ

本記事では、日付・時刻の新APIと旧APIの比較を簡単にご紹介しました。

使い慣れた旧APIを、つい使ってしまう…気持ちはわかります。

とはいえ、旧APIに問題があるから新APIが生まれたわけです。
いろいろと改善はされているはずです。

新APIを使うことで、結果としてプログラムの品質があがり、テスト期間を短縮できる…かもしれません。

ぜひ新APIを使っていきましょう。


関連記事

ページの先頭へ