Duration で時間差を計算
このレッスンで分かること
Durationは時間の長さ (秒 / ナノ秒精度) を表す。日付ではなく 量Duration.between(start, end)で 2 つのInstant/LocalTime間の差を取るDuration.ofMinutes(15)Duration.ofHours(2)で生成、plusminusで加減- 日付ベースの差なら
Period、ナノ精度の差ならDurationを使い分け
Duration で時間差を計算 とは
DurationとPeriod、ChronoUnit.betweenの違いを整理しながら、2 つの日付の差を日数で計算するメソッドを書いてみよう。
「時間の長さ」を表す型 ─ Duration と Period
プログラムで時間を扱うときに、現在の日時 を表す型と、時間の長さ を表す型は別物だ、という発想がとても大事です。Java では「いつ?」を表す型として LocalDate (日付だけ) や LocalDateTime (日付と時刻) があり、「どれくらいの長さ?」を表す型として Duration と Period の 2 種類が用意されています。両方とも java.time パッケージに入っています。
入門コースで
int days = 30;のように「日数をintで持つ」やり方を見てきたかもしれません。それでも動きますが、DurationやPeriodを使うと「これは時間の長さです」という意味がコード上に明示できて、計算ミスがぐっと減ります。
Duration と Period は名前が似ていてとても紛らわしいのですが、役割がはっきり分かれています。ざっくり覚えるとしたら下記のとおりです。
Durationは 時 / 分 / 秒 / ナノ秒 の長さを扱う。LocalTimeやLocalDateTime、Instantと相性が良いPeriodは 年 / 月 / 日 の長さを扱う。LocalDateと相性が良い
言い換えると、Duration は「ストップウォッチで測れるくらいの粒度」、Period は「カレンダー上でめくる単位」と覚えると間違えにくいです。たとえば「2 時間 30 分後にミーティング」は Duration、「3 ヶ月後の同じ日付」は Period、というイメージです。
本章では、まずこの 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-01d2 = 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.between が 3.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
}
}Duration を System.out.println すると PT3H30M のように ISO 8601 の Duration 表記が出てきます。これは「Period of Time 3 Hours 30 Minutes」の略で、ログや API のレスポンスでもよく見る形式です。
Duration.betweenにLocalDateを渡すと例外になります。Durationは時 / 分 / 秒の世界、LocalDateは時刻を持たないので、両者は混ぜられないからです。日付の差を時間で測りたいときは、atStartOfDay()でLocalDateTimeに変換してから渡しましょう。
Instant との関係
Instant は「UTC を基準にした世界共通の瞬間」を表す型で、サーバー側のログや API ではこちらを使うことが増えています。Duration は Instant とも相性が良く、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)は 型エラー になります。PeriodはLocalDate専用で、LocalTimeやLocalDateTimeを渡せません。「ストップウォッチ的な短い時間」を測りたいときは必ずDurationを使いますbetweenの引数順を逆にして符号がおかしくなる —ChronoUnit.DAYS.between(start, end)はend - startを返します。startのほうが未来だと 負の値 になります。「絶対値が欲しい」のか「向きを保ちたいのか」を意識して書きましょう。Math.abs(...)で絶対値を取るのは簡単ですが、「過去か未来か」の情報を捨てる前に、本当にそれでいいかを一度立ち止まって考えるのが大事です
もう 1 つよくあるのが、Duration.ofHours(1).plus(Duration.ofMinutes(30)) のように足し算したいときに、+ 演算子を使ってしまうミスです。Duration も Period も イミュータブル なので、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 つの日付の差を 日数 で返します。やることは下記のとおりです。
- ファイル先頭で
import java.time.LocalDate;とimport java.time.temporal.ChronoUnit;を書く LocalDate.of(year1, month1, day1)で 1 つ目の日付を作る- 同じ要領で 2 つ目の日付も作る
ChronoUnit.DAYS.between(d1, d2)で日数差を取り、(int)でキャストしてreturnする
戻り値の型は int ですが、ChronoUnit.DAYS.between は long を返すことに注意してください。(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.between や ChronoUnit.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 つができることを確認しましょう。
- Duration の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. Duration とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- ファイル先頭で
import java.time.LocalDate;とimport java.time.temporal.ChronoUnit;を書くこと LocalDate.of(year, month, day)を 2 回呼んで 2 つのLocalDateを作ること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