CodeGym /コース /JAVA 25 SELF /java.time API の概要、旧 API との違い

java.time API の概要、旧 API との違い

JAVA 25 SELF
レベル 13, レッスン 1
使用可能

1. 歴史的背景: Java 8 以前は日付をどう扱っていたか

昔(Java 8 以前)は、日付と時刻の扱いに java.util.Date、java.util.Calendar を使い、フォーマットには java.text.SimpleDateFormat を使っていました。世界中のプログラマーがため息をつき、歯ぎしりしながら次のようなコードを書いていたものです。

import java.util.Date;

Date now = new Date();
System.out.println(now); // "Wed Jun 05 14:15:22 MSK 2025" のような奇妙なものが出力される

一見簡単そうですが、実際はそう甘くありませんでした。旧 API の「喜び」の一部を挙げると次のとおりです。

  • Date は可変(mutable)オブジェクト。うっかり変更してしまい、バグの原因になりがち。
  • Date と Calendar の月は 0 始まり(1 月は 0、12 月は 11)。一方で日だけは 1 始まり。
  • SimpleDateFormat はスレッドセーフではない。2 つのスレッドが同時にフォーマットすると、予期せぬ結果になりうる。
  • 非常に多くのメソッドが @Deprecated(非推奨)で、IDE が常に黄色の警告を出してくる。
  • タイムゾーンの扱いは本当に苦痛。ローカル時刻と UTC を混同しやすく、夏時間/冬時間の切り替えはなおさら大変。

つらい例

import java.util.Date;

Date date = new Date(2025, 5, 1); // 2025 年、5 月(6 月?)、1 日
System.out.println(date); // 期待どおりではない!

2. java.time の登場: 新しいアプローチ

2014 年頃には、旧 API は不便なだけでなく危険だということが明らかになりました。そこで JSR‑310 仕様を実装した新しいパッケージ java.time が Java に登場しました。この API は人気ライブラリ Joda-Time に着想を得ており、瞬く間に事実上の標準となりました。

主なパッケージとクラス

  • java.time — 新しい日付と時刻のクラスが居並ぶ中核パッケージ。
  • java.time.format — 日付と時刻のフォーマット/パース用。
  • java.time.temporal — より高度な時間操作のための API。
  • java.time.zone — タイムゾーン関連。

新 API の主役たちは次のとおりです。

クラス 用途 使用例
LocalDate
日付のみ(年・月・日) 時刻なしの誕生日
LocalTime
時刻のみ(時・分・秒) 日付なしの会議時刻
LocalDateTime
タイムゾーンなしの日時 ローカルなイベント
ZonedDateTime
タイムゾーン付きの日時 ミンスクの現地時刻でのミーティング
Instant
UTC の絶対的な時間点 ログのイベントのタイムスタンプ
Duration
時間間隔(時・分・秒) 通話時間
Period
期間(年・月・日) 勤続年数、年齢

例: 新しいやり方で日付を作る

import java.time.LocalDate;

LocalDate today = LocalDate.now();
System.out.println(today); // 例: "2025-06-05"

3. 新 API の利点

不変(immutable)

java.time のクラスはすべて不変です。つまり、LocalDate オブジェクトを作成したら、それ自体を変更することはできません。操作(たとえば 1 日足す)は常に新しいオブジェクトを返します。

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);

System.out.println(today);    // 2025-06-05
System.out.println(tomorrow); // 2025-06-06

タイムゾーンを明示的に扱う

旧 API では、その日時がどのタイムゾーンなのかを忘れがちでした。java.time ではすべてが明示的です。タイムゾーンが必要なら ZonedDateTime、不要なら LocalDateTime を使います。

import java.time.ZonedDateTime;
import java.time.ZoneId;

ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]

計算や比較のための便利なメソッド

LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true

フォーマットとパース

フォーマットとパースは DateTimeFormatter で行います(詳細は次回の講義)。

import java.time.format.DateTimeFormatter;

LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"

4. 互換性: 旧コードとどう付き合うか

実世界では、とても頻繁に Date や Calendar を使うライブラリや古いシステムと連携する必要があります。幸い、新 API はレガシーに優しく、旧タイプと新タイプを相互に変換できます。

Date ↔ Instant の変換

import java.util.Date;
import java.time.Instant;

// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();

// Instant → Date
Date dateBack = Date.from(instant);

Calendar ↔ ZonedDateTime の変換

import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;

// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
        calendar.toInstant(),
        calendar.getTimeZone().toZoneId()
);

// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));

表: 旧クラスと新クラスの対応

旧クラス 新クラス コメント
java.util.Date
Instant
絶対時刻
java.util.Calendar
ZonedDateTime
タイムゾーン付きの日時
java.text.SimpleDateFormat
DateTimeFormatter
日付のフォーマット/パース

5. 実践: java.time を使ってみる

たとえば、誕生日を持つユーザーがいるとします。この日付を保存し、表示してみましょう。

import java.time.LocalDate;

public class UserProfile {
    private String name;
    private LocalDate birthDate;

    public UserProfile(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public void printProfile() {
        System.out.println("名前: " + name);
        System.out.println("生年月日: " + birthDate);
    }
}

public class Main {
    public static void main(String[] args) {
        UserProfile user = new UserProfile("Alisa", LocalDate.of(1998, 12, 25));
        user.printProfile();
    }
}

出力:

名前: Alisa
生年月日: 1998-12-25

6. 比較: 旧 API と新 API

例: 誕生日に 1 週間を足す

旧来の方法(Date/Calendar):

import java.util.Calendar;

Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // 面倒でわかりにくい

新しい方法(java.time):

import java.time.LocalDate;

LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01

新 API ではコードが短く、簡潔で、安全です。

7. java.time を使うときのよくある間違い

エラー1: オブジェクトが不変であることを忘れる。
date.plusDays(1); を呼んでも、結果を保存しなければ元の日時は変わりません。

エラー2: LocalDate と LocalDateTime を取り違える。
LocalDate は日付(年・月・日)のみ、LocalDateTime はさらに時刻も保持します。時と分も扱う必要がある場合は混同しないようにしましょう。

エラー3: 新しいプロジェクトで古いクラスを使ってしまう。
可能であれば常に java.time を使いましょう。旧来のクラスは互換性維持のためだけに必要です。

エラー4: タイムゾーンの扱いを誤る。
地域をまたいで重要なイベントを保存する場合は、ZonedDateTime、少なくとも Instant を使いましょう。LocalDateTime にはタイムゾーン情報が含まれません。

エラー5: LocalDate と LocalDateTime を直接比較しようとする。
これは別のデータ型なので、直接比較はできません。まず同じ型にそろえてください。

1
タスク
JAVA 25 SELF, レベル 13, レッスン 1
ロック未解除
カレンダーで本日をマークする 🗓️
カレンダーで本日をマークする 🗓️
1
タスク
JAVA 25 SELF, レベル 13, レッスン 1
ロック未解除
振り返り:昨日は何日でしたか? ⏪
振り返り:昨日は何日でしたか? ⏪
1
タスク
JAVA 25 SELF, レベル 13, レッスン 1
ロック未解除
未来を覗く:明日は何日? ⏩
未来を覗く:明日は何日? ⏩
1
タスク
JAVA 25 SELF, レベル 13, レッスン 1
ロック未解除
重要な歴史的瞬間の記録 📜
重要な歴史的瞬間の記録 📜
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION