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
新API の Instant に相当するクラスです。
Java 1.0 ではカレンダー的な機能もありましたが、Java 1.1 で非推奨になりました。
Calendar
新API の ZonedDateTime に相当するクラスです。
日付・時刻の操作、計算、取得などひととおりのことができます。
TimeZone
新API の ZoneId(とZoneOffset) に相当するクラスです。
サマータイムも考慮さています。
相互運用も可能
古いライブラリなどでは、Date や Calendarクラスをパラメータに要求するものがあるかもしれません。
その場合は、新旧API の変換を行いましょう。
旧API → 新API
// Date(旧API) → Instant(新API)
final var date = new Date(4102444800123L);
System.out.println(date.getTime()); // 4102444800123
final var instant = date.toInstant();
System.out.println(instant); // 2100-01-01T00:00:00.123Z
System.out.println(instant.getEpochSecond()); // 4102444800
System.out.println(instant.getNano()); // 123000000
// TimeZone(旧API) → ZoneId(新API)
final var timeZone = TimeZone.getDefault();
System.out.println(timeZone.getID()); // Asia/Tokyo
final var zoneId = timeZone.toZoneId();
System.out.println(zoneId); // Asia/Tokyo
(Calendar から直接 ZonedDateTime に変換する API はなさそう?)
// Calendar(旧API) → ZonedDateTime(新API)
final var calendar = Calendar.getInstance();
calendar.setTimeInMillis(4102444800000L);
System.out.printf("%tc%n", calendar); // 金 1月 01 09:00:00 JST 2100
final var instant = calendar.toInstant();
System.out.println(instant); // 2100-01-01T00:00:00Z
final var zoneId = calendar.getTimeZone().toZoneId();
System.out.println(zoneId); // Asia/Tokyo
final var zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(zonedDateTime); // 2100-01-01T09:00+09:00[Asia/Tokyo]
新API → 旧API
// Instant(新API) → Date(旧API)
final var instant = Instant.ofEpochSecond(4102444800L, 123456789L);
System.out.println(instant); // 2100-01-01T00:00:00.123456789Z
final var date = Date.from(instant);
// ミリ秒より小さい桁は切り捨てられます。
System.out.println(date.getTime()); // 4102444800123
// ZoneId(新API) → TimeZone(旧API)
final var zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Tokyo
final var timeZone = TimeZone.getTimeZone(zoneId);
System.out.println(timeZone.getID()); // Asia/Tokyo
(ZonedDateTime から直接 Calendar に変換する API はなさそう?)
// ZonedDateTime(新API) -> Calendar(旧API)
final var zonedDateTime = ZonedDateTime.of(
2100, 1, 1, 9, 0, 0, 0, ZoneId.systemDefault());
System.out.println(zonedDateTime); // 2100-01-01T09:00+09:00[Asia/Tokyo]
final var instant = zonedDateTime.toInstant();
System.out.println(instant); // 2100-01-01T00:00:00Z
final var timeZone = TimeZone.getTimeZone(zonedDateTime.getZone());
System.out.println(timeZone.getID()); // Asia/Tokyo
final var calendar = Calendar.getInstance(timeZone);
calendar.setTime(Date.from(instant));
System.out.printf("%tc%n", calendar); // 金 1月 01 09:00:00 JST 2100
まとめ
本記事では、日付・時刻の新しい API と古い API の比較を簡単にご紹介しました。
使い慣れた古い API を、つい使ってしまう…気持ちはわかります。
とはいえ、古い API に問題があったからこそ新しい API が生まれたわけです。
いろいろと改善はされているはずです。
新しい API を使うことで、結果としてプログラムの品質があがり、テスト期間を短縮できる…かもしれません。
ぜひ有効に活用していきたいですね。
関連記事
- API 使用例
- Calendar (カレンダー)
- ChronoLocalDate
- ChronoLocalDateTime
- ChronoZonedDateTime
- Clock (時計)
- Date (日付・時刻)
- DateTimeException (日付・時刻の例外)
- DateTimeParseException (日付・時刻の解析例外)
- DayOfWeek (曜日)
- Duration (時間の量)
- Era (紀元)
- Instant (時点)
- InstantSource
- JapaneseDate (和暦を使った日付)
- LocalDate (日付・タイムゾーンなし)
- LocalDateTime (日時・タイムゾーンなし)
- LocalTime (時刻・タイムゾーンなし)
- Month (月)
- MonthDay (月・日)
- OffsetDateTime (日時・オフセットあり)
- OffsetTime (時刻・オフセットあり)
- Period (日付の量)
- Temporal
- TemporalAccessor
- TemporalAdjuster (日付・時刻の調整)
- TemporalAdjusters (日付・時刻の調整ユーティリティ)
- TimeZone (タイムゾーン)
- Year (年)
- YearMonth (年・月)
- ZonedDateTime (日時・タイムゾーンあり)
- ZoneId (タイムゾーンID)
- ZoneOffset (タイムゾーン・オフセット)