Java | Java 詳細・モダン文法:日付・時刻 API – DateTime API 設計思想

Java Java
スポンサーリンク

なぜ新しい DateTime API が必要だったのか

まず一番大事な背景からいきます。
Java 8 で導入された java.time(DateTime API)は、「古い日付 API(java.util.Date / java.util.Calendar)がつらすぎたから」生まれました。

古い API は、

  • 月が 0 始まりで分かりにくい
  • ミュータブルで、いつどこで値が変わるか追いにくい
  • スレッドセーフではない
  • タイムゾーンや夏時間を扱うのが難しい

といった問題を抱えていて、「日付・時刻」という本来シンプルであってほしいものが、コード上ではやたらと壊れやすく、読みにくい存在になっていました。

新しい DateTime API の設計思想は、ざっくり言うとこうです。

「人間にとって自然な日付・時刻の概念を、
 安全で、読みやすく、間違えにくい形でコードに落とし込む」

このゴールに向けて、いくつかの重要な考え方が組み込まれています。


設計思想1:不変(immutable)であること

なぜ「不変」がそんなに大事なのか

LocalDateLocalDateTimeZonedDateTime など、java.time のクラスは基本的にすべて不変(immutable)です。
一度作ったインスタンスの中身は変わりません。

例えば、こうです。

LocalDate today = LocalDate.of(2025, 1, 18);
LocalDate tomorrow = today.plusDays(1);

System.out.println(today);    // 2025-01-18
System.out.println(tomorrow); // 2025-01-19
Java

plusDays は「今日を書き換える」のではなく、「新しい日付を返す」メソッドです。

これがなぜ大事かというと、

  • どこか別のメソッドで勝手に日付が変えられる心配がない
  • 複数スレッドから同じインスタンスを使っても安全
  • 「いつ値が変わるのか」を追いかけなくてよくなる

からです。

古い Date / Calendar はミュータブルだったので、

Calendar cal = ...;
cal.add(Calendar.DAY_OF_MONTH, 1); // ここで cal 自体が書き換わる
Java

のように、「元のオブジェクトがいつの間にか変わっている」状態が簡単に起きました。
新しい API は、「日付・時刻は“値”として扱うべき」という思想に立って、すべて不変にしています。


設計思想2:概念ごとにクラスを分ける

「日付だけ」「時刻だけ」「タイムゾーン付き」を分ける

java.time には、いろいろなクラスがありますが、それぞれ「何を表しているか」がはっきり分かれています。

代表的なものだけ挙げると、

  • LocalDate:日付だけ(年・月・日)
  • LocalTime:時刻だけ(時・分・秒)
  • LocalDateTime:日付+時刻(タイムゾーンなし)
  • ZonedDateTime:日付+時刻+タイムゾーン
  • Instant:UTC 基準の「瞬間」(機械向けの時刻)

という感じです。

例えば、「誕生日」はタイムゾーン関係ないので LocalDate が自然です。

LocalDate birthday = LocalDate.of(1990, 4, 1);
Java

一方、「会議の開始時刻(日本時間)」はタイムゾーン込みで考えたいので、ZonedDateTime が適しています。

ZonedDateTime meeting =
        ZonedDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
Java

古い Date は「何を表しているのか」が曖昧でした。
Date 1 個で「ローカル時間なのか UTC なのか」「タイムゾーンはどこなのか」がコードから読み取れません。

新しい API は、「概念ごとにクラスを分けることで、意図を型で表現する」という設計になっています。


設計思想3:人間向けの時間と機械向けの時間を分ける

Instant と LocalDateTime / ZonedDateTime の役割分担

Instant は、「1970-01-01T00:00:00Z からの経過秒数」という、機械的な時刻を表します。

Instant now = Instant.now();
Java

これは「世界共通の一点」を表すのに向いていますが、人間にとっては読みにくいです。

一方で、LocalDateTimeZonedDateTime は、「カレンダー上の日時」を表します。

LocalDateTime localNow = LocalDateTime.now();
ZonedDateTime tokyoNow = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
Java

設計思想としては、

  • 保存・通信・ログなど「システム内部のやり取り」には Instant(+必要ならタイムゾーン情報)
  • 画面表示やビジネスロジックなど「人間の世界の時間」には LocalDateTime / ZonedDateTime

という役割分担を想定しています。

これにより、

「DB には UTC の Instant で保存し、
 アプリ側でユーザーのタイムゾーンに変換して表示する」

といった設計がやりやすくなります。


設計思想4:操作を「宣言的」に書ける API

plus / minus / with で「何をしたいか」を表現する

新しい DateTime API は、操作メソッドの名前がとても素直です。

LocalDate today = LocalDate.now();

LocalDate nextWeek = today.plusWeeks(1);
LocalDate firstDayOfMonth = today.withDayOfMonth(1);
LocalDate nextMonthSameDay = today.plusMonths(1);
Java

plusXxx / minusXxx / withXxx というパターンで、

  • 何を足したいのか(plusDays, plusWeeks, plusMonths, …)
  • 何を変えたいのか(withYear, withMonth, withDayOfMonth, …)

がそのままメソッド名に出ています。

古い Calendar だと、こうでした。

Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 7);
cal.set(Calendar.DAY_OF_MONTH, 1);
Java

「何をしているか」は分かりますが、

  • ミュータブルである
  • 定数の指定が分かりにくい
  • メソッドチェーンしづらい

など、宣言的とは言い難い書き味でした。

新しい API は、「日付・時刻の操作を“文章のように”読めるようにする」という方向で設計されています。


設計思想5:フォーマットとパースを明示的に扱う

DateTimeFormatter で「どの形式か」をはっきりさせる

日付文字列との変換も、設計がかなり整理されています。

LocalDate date = LocalDate.of(2025, 1, 18);

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String text = date.format(formatter);          // "2025/01/18"

LocalDate parsed = LocalDate.parse("2025/01/18", formatter);
Java

ポイントは、

  • フォーマットのパターンを DateTimeFormatter として明示的に持つ
  • format / parse の両方で同じフォーマッタを使える

というところです。

古い SimpleDateFormat はミュータブルでスレッドセーフではなく、

  • static で使い回すとバグる
  • 毎回 new するとパフォーマンスが悪い

という厄介な存在でした。

新しい DateTimeFormatter は不変でスレッドセーフなので、

static final DateTimeFormatter FORMATTER =
        DateTimeFormatter.ofPattern("yyyy/MM/dd");
Java

のように定数として安全に使い回せます。


設計思想6:「期間」と「瞬間」を分けて考える

Period と Duration の違い

java.time には、「期間」を表すクラスも用意されています。

  • Period:年・月・日単位の期間(人間のカレンダー感覚)
  • Duration:秒・ナノ秒単位の期間(機械的な時間)

例えば、「2 日後」は Period で表現できます。

Period twoDays = Period.ofDays(2);
LocalDate today = LocalDate.now();
LocalDate twoDaysLater = today.plus(twoDays);
Java

一方、「30 秒後」は Duration です。

Duration thirtySeconds = Duration.ofSeconds(30);
Instant now = Instant.now();
Instant later = now.plus(thirtySeconds);
Java

「日付の差」と「時刻の差」を、

  • カレンダー的に扱いたいのか
  • 純粋な時間として扱いたいのか

でクラスを分けているのがポイントです。


まとめ:DateTime API の設計思想を自分の言葉で言うなら

あなたの言葉でまとめると、こうなります。

「新しい DateTime API(java.time)は、
 古い Date / Calendar の“分かりにくい・壊れやすい・スレッドセーフでない”世界から脱出するために、

  • すべて不変にして安全にし
  • 日付・時刻・タイムゾーン・瞬間・期間などの概念をクラスごとに分け
  • 人間向けの時間と機械向けの時間を分離し
  • plus / with / format / parse などのメソッドで“何をしたいか”をそのまま書けるようにした

“日付・時刻を、型と API で正しく表現するための設計”になっている。」

タイトルとURLをコピーしました