Duration で時間差を計算

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • Duration は時間の長さ (秒 / ナノ秒精度) を表す。日付ではなく
  • Duration.between(start, end) で 2 つの Instant / LocalTime 間の差を取る
  • Duration.ofMinutes(15) Duration.ofHours(2) で生成、plus minus で加減
  • 日付ベースの差なら Period、ナノ精度の差なら Duration を使い分け

Duration で時間差を計算 とは

DurationPeriodChronoUnit.between の違いを整理しながら、2 つの日付の差を日数で計算するメソッドを書いてみよう。

「時間の長さ」を表す型 ─ DurationPeriod

プログラムで時間を扱うときに、現在の日時 を表す型と、時間の長さ を表す型は別物だ、という発想がとても大事です。Java では「いつ?」を表す型として LocalDate (日付だけ) や LocalDateTime (日付と時刻) があり、「どれくらいの長さ?」を表す型として DurationPeriod の 2 種類が用意されています。両方とも java.time パッケージに入っています。

入門コースで int days = 30; のように「日数を int で持つ」やり方を見てきたかもしれません。それでも動きますが、DurationPeriod を使うと「これは時間の長さです」という意味がコード上に明示できて、計算ミスがぐっと減ります。

DurationPeriod は名前が似ていてとても紛らわしいのですが、役割がはっきり分かれています。ざっくり覚えるとしたら下記のとおりです。

  • Duration時 / 分 / 秒 / ナノ秒 の長さを扱う。LocalTimeLocalDateTimeInstant と相性が良い
  • Period年 / 月 / 日 の長さを扱う。LocalDate と相性が良い

言い換えると、Duration は「ストップウォッチで測れるくらいの粒度」、Period は「カレンダー上でめくる単位」と覚えると間違えにくいです。たとえば「2 時間 30 分後にミーティング」は Duration、「3 ヶ月後の同じ日付」は Period、というイメージです。

diagram (will load when visible)

本章では、まずこの 2 つの違いを押さえたうえで、もっと汎用的に使える ChronoUnit.between のやり方を学び、最後に課題で 2 つの日付の差を日数で計算してみます。

LocalDate の差を取る 3 つのやり方

2 つの LocalDate の差を「日数」で取りたい、というのは業務でよく出てくる要件です。「予約日から 7 日 以内ならキャンセル可」「会員登録から 30 日 経過したらメール送信」など、Web アプリでは日常茶飯事です。Java では大きく 3 つのやり方があります。

Java

import java.time.LocalDate; import java.time.Period; import java.time.temporal.ChronoUnit; public class DiffDemo { public static void main(String[] args) { LocalDate d1 = LocalDate.of(2026, 5, 1); LocalDate d2 = LocalDate.of(2026, 5, 19); // 1) Period.between (年・月・日に分解される) Period p = Period.between(d1, d2); System.out.println(p.getDays()); // 18 // 2) ChronoUnit.DAYS.between (合計日数) long days = ChronoUnit.DAYS.between(d1, d2); System.out.println(days); // 18 // 3) toEpochDay() の引き算 (合計日数) long diff = d2.toEpochDay() - d1.toEpochDay(); System.out.println(diff); // 18 } }

この中で、シンプルかつ意図が読み取りやすいのが 2 番目の ChronoUnit.DAYS.between(d1, d2) です。Period.between は「3 ヶ月 + 2 日」のように年月日に分解された値を返すので、純粋な「合計日数」が欲しいときには ChronoUnit のほうが向いています。

Period.between(d1, d2).getDays()その月の日付の差 しか返さない (年と月を除いた値) ので、d1 = 2024-01-01 d2 = 2024-12-31 の場合は 30 のような値になります。「合計で何日?」を聞きたいときに Period を使うと痛い目を見ます。

ChronoUnit で単位を選ぶ

ChronoUnit は「時間の単位」を列挙した enum です。DAYS 以外にも HOURS MINUTES SECONDS MONTHS YEARS などがあり、between(start, end) メソッドで 2 点間の差を その単位で 取得できます。

Java

import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; public class ChronoDemo { public static void main(String[] args) { LocalDateTime start = LocalDateTime.of(2026, 5, 19, 9, 0); LocalDateTime end = LocalDateTime.of(2026, 5, 19, 12, 30); long minutes = ChronoUnit.MINUTES.between(start, end); // 210 long hours = ChronoUnit.HOURS.between(start, end); // 3 long days = ChronoUnit.DAYS.between(start, end); // 0 } }

注目すべきは、ChronoUnit.HOURS.between3.5 ではなく 3 を返す ことです。between小数を切り捨てる、つまり「その単位で完全に含まれる回数」を返す仕様だからです。小数の時間が欲しい場合は、Duration.between(...).toMinutes() で分に変換してから割り算するのがセオリーです。

Duration を直接使うパターン

Duration は時刻 (LocalTime / LocalDateTime / Instant) の差を扱うのが本業です。Duration.between(start, end) を呼ぶと Duration オブジェクトが返り、そこから toSeconds() toMinutes() toHours() などで取り出せます。

Java

import java.time.Duration; import java.time.LocalDateTime; public class DurationDemo { public static void main(String[] args) { LocalDateTime start = LocalDateTime.of(2026, 5, 19, 9, 0); LocalDateTime end = LocalDateTime.of(2026, 5, 19, 12, 30); Duration d = Duration.between(start, end); System.out.println(d.toMinutes()); // 210 System.out.println(d.toHours()); // 3 System.out.println(d); // PT3H30M } }

DurationSystem.out.println すると PT3H30M のように ISO 8601Duration 表記が出てきます。これは「Period of Time 3 Hours 30 Minutes」の略で、ログや API のレスポンスでもよく見る形式です。

Duration.betweenLocalDate を渡すと例外になります。Duration は時 / 分 / 秒の世界、LocalDate は時刻を持たないので、両者は混ぜられないからです。日付の差を時間で測りたいときは、atStartOfDay()LocalDateTime に変換してから渡しましょう。

diagram (will load when visible)

Instant との関係

Instant は「UTC を基準にした世界共通の瞬間」を表す型で、サーバー側のログや API ではこちらを使うことが増えています。DurationInstant とも相性が良く、Duration.between(instant1, instant2) で経過時間を取れます。Duration 自体はタイムゾーンを持たないので、世界中どこで計算しても同じ値になります。

Java

import java.time.Duration; import java.time.Instant; public class InstantDemo { public static void main(String[] args) throws InterruptedException { Instant t1 = Instant.now(); Thread.sleep(1500); Instant t2 = Instant.now(); Duration d = Duration.between(t1, t2); System.out.println(d.toMillis() + " ms"); // 約 1500 ms } }

この「Instant で計測 → Duration で長さを表す」というパターンは、処理時間の計測や タイムアウト 判定で頻繁に出てきます。

よくある間違い

ここで、Duration Period ChronoUnit まわりでつまずきやすいポイントを 3 つ整理しておきます。事前に知っておくだけで、UnsupportedTemporalTypeException のような怖そうな例外に出くわす回数が激減します。

  • Duration に年や月の長さを聞かないDuration は最大単位が「日」ですが、toDays() の戻り値も「秒数を 86400 で割った値」にすぎず、Duration 自体は「年 / 月」の概念を持っていません。Duration.ofDays(60).toMonths() のようなメソッドはなく、書こうとしてもコンパイルエラーになります。年・月単位の差を取りたいときは Period または ChronoUnit.MONTHS.between を使いましょう
  • Period で秒や時間を扱おうとする — 逆に、Period.between(localTime1, localTime2)型エラー になります。PeriodLocalDate 専用で、LocalTimeLocalDateTime を渡せません。「ストップウォッチ的な短い時間」を測りたいときは必ず Duration を使います
  • between の引数順を逆にして符号がおかしくなるChronoUnit.DAYS.between(start, end)end - start を返します。start のほうが未来だと 負の値 になります。「絶対値が欲しい」のか「向きを保ちたいのか」を意識して書きましょう。Math.abs(...) で絶対値を取るのは簡単ですが、「過去か未来か」の情報を捨てる前に、本当にそれでいいかを一度立ち止まって考えるのが大事です

もう 1 つよくあるのが、Duration.ofHours(1).plus(Duration.ofMinutes(30)) のように足し算したいときに、+ 演算子を使ってしまうミスです。DurationPeriodイミュータブル なので、plus minus のメソッドで新しいインスタンスを作る、という流儀になります。d.plus(...)d 自体は変わらないことに注意してください。

Java の java.time パッケージは、基本的にすべてイミュータブルです。String と同じで「変えると新しいインスタンスが返る」と覚えておくと、思わぬバグを防げます。

実務で遭遇するパターン

中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。

  • API レスポンス時間の計測Duration.between(start, end).toMillis() で ms 単位の応答時間
  • タイムアウト指定httpClient.connectTimeout(Duration.ofSeconds(5)) のような設定
  • SLA 監視Duration.ofMinutes(15) を閾値として超過したらアラート
  • スリープ / リトライ間隔Thread.sleep(Duration.ofMillis(500).toMillis()) のような書き方

コードレビューで指摘されがちなポイント

PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。

  • long ms を直接持ち回している ─ 型がなくて 何 ms なのか可読性低下、Duration を使うように修正
  • Period と混同 ─ 月単位 / 日単位の差なら Period、秒単位なら Duration
  • マジックナンバーDuration.ofSeconds(86400) ではなく Duration.ofDays(1) のほうが意図明確
  • Duration.toString() を直接ユーザーに見せるPT3H30M のような ISO 8601 表記。フォーマットして表示

パフォーマンス考慮事項

  • 生成 / 計算ともに軽量 ─ ナノ秒オーダー、無視できる
  • Duration はイミュータブル ─ スレッドセーフ、static final で共有可
  • マイクロ秒 / ナノ秒精度 ─ Java 8 の LocalDateTime でも Duration でナノ秒差は取れる
  • OS / VM の時計精度 ─ Windows の System.currentTimeMillis は 10〜15 ms 単位、System.nanoTime ならマイクロ秒
この章のポイント

ここまでの要点 Duration は時間の量、Period は日付の差。ofSeconds ofMinutes 等で生成、between で差を取り、plus / minus で計算。

やってみよう

それでは今回の課題に挑戦してみましょう。Solution.daysBetween(int year1, int month1, int day1, int year2, int month2, int day2) を実装して、2 つの日付の差を 日数 で返します。やることは下記のとおりです。

  1. ファイル先頭で import java.time.LocalDate;import java.time.temporal.ChronoUnit; を書く
  2. LocalDate.of(year1, month1, day1) で 1 つ目の日付を作る
  3. 同じ要領で 2 つ目の日付も作る
  4. ChronoUnit.DAYS.between(d1, d2) で日数差を取り、(int) でキャストして return する

戻り値の型は int ですが、ChronoUnit.DAYS.betweenlong を返すことに注意してください。(int) で明示的にキャストしてあげる必要があります。実用的にはまず int の範囲を超えることはないので、キャストして問題ありません (Java の int は約 21 億 まで、日数換算で 580 万年 以上です)。

テストでは下記のようなケースを確認します。

  • daysBetween(2026, 5, 1, 2026, 5, 19)18 (同じ月内の差)
  • daysBetween(2024, 1, 1, 2024, 12, 31)365 (2024 年はうるう年なので 1 月 1 日から 12 月 31 日まで 365 日)
  • daysBetween(2026, 5, 19, 2026, 5, 19)0 (同じ日)
  • daysBetween(2026, 5, 19, 2026, 5, 1)-18 (引数順が逆 → 負の値)

慣れてきたら、ChronoUnit.MONTHS.betweenChronoUnit.YEARS.between を呼んでみたり、Period.between(d1, d2).getDays() との違いを実験してみてください。「合計で何日?」と「年月日に分解した日?」の挙動の違いが体感できると、Duration Period ChronoUnit の使い分けが一気にクリアになります。

次のレッスンでは、文字列と日付の相互変換 (DateTimeFormatter) に踏み込んでいきます。ここまでで「日付を作る → 計算する → 比較する」の流れをしっかり身につけておきましょう。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は 日付の書式化とパース で、日付の書式化とパース を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. Duration の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. Duration とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. ファイル先頭で import java.time.LocalDate;import java.time.temporal.ChronoUnit; を書くこと
  2. LocalDate.of(year, month, day) を 2 回呼んで 2 つの LocalDate を作ること
  3. ChronoUnit.DAYS.between(d1, d2) の戻り値 (long) を (int) でキャストして返すこと

入出力例

test-cases.txt

daysBetween(2026, 5, 1, 2026, 5, 19)18 daysBetween(2024, 1, 1, 2024, 12, 31)365 daysBetween(2026, 5, 19, 2026, 5, 19)0 daysBetween(2026, 5, 19, 2026, 5, 1)-18 daysBetween(2025, 12, 31, 2026, 1, 1)1

ヒント

main.java
main.java
学習モード

メモ

Duration で時間差を計算

⌘S で保存