5 * 60 * 1000——このコードを見て、一瞬で「5分」だとわかっただろうか。
実務でこの手のミリ秒計算に出会うたび、ゼロの数を数え直した経験がある人は少なくないはずだ。JavaのDurationクラスを使えば、この計算はDuration.ofMinutes(5)の一行で終わる。読んだ瞬間に「5分」だとわかるコードになる。
この記事でできるようになることは3つある。(1)of系・between・変換メソッドを使ったDurationの生成と計算、(2)Periodとの正しい使い分け、(3)処理時間計測やOverflow・負のDurationなど実務で踏みやすい落とし穴の回避、の3つだ。単位の勘違いで実際にバグを出した経験も交えて、コード例付きで解説する。
この記事でわかること
- Duration.of系・between・変換メソッドを使った時間の生成と計算
- PeriodとDurationの正しい使い分け
- 処理時間計測やOverflow・負のDurationなど実務のつまずきポイントの回避
PARTS LIST /目次
JavaのDurationとは?仕組みと役割をわかりやすく解説
JavaのDurationとは、2つの時刻の「間隔(期間)」を表すクラスです。
結論から言えば、数秒や数分といった「時間の長さ」を扱うなら、必ずこのクラスを使うべきです。
理由は、Durationを使うことで、人間が直感的に理解できる単位で時間をプログラムできるからです。
従来のように「5分」を表すために5 * 60 * 1000のような計算式をコードに書く必要がなくなります。
なお、Durationは日本語では「デュレーション」と読みます。「継続時間」「期間」を意味する英単語がそのまま採用されたクラス名です。
Durationが表すもの(時刻ではなく“時間量”)
Durationが保持しているのは「ある時点」ではなく「時間の量」です。例えば「10時00分」は時刻ですが、「30分間」は時間量です。
Durationはこの「30分間」や「5時間」「10秒」といった長さを管理します。
内部的には「秒(seconds)」と「ナノ秒(nanos)」の2つの値で時間を保持しています。
これにより、ミリ秒未満の短い計測から数千時間の長期間まで正確に表現できます。
DurationとLocalDateTimeの違いは何か
LocalDateTimeとDurationは、根本的に役割が異なります。
- LocalDateTime: 「2026年1月1日 12時00分」という点を表します。
- Duration: 「5時間30分」という線(長さ)を表します。
この2つは混同されがちです。
カレンダー上の特定の日時を指したい場合はLocalDateTimeを使い、その日時までの残り時間や、処理にかかった時間を表す場合にはDurationを選びます。
役割を明確に分けることで、バグの少ないコードが書けるようになります。
Durationが使われる典型シーン
開発現場において、Durationは以下のような場面で頻繁に登場します。
- タイムアウト設定: 「接続待ち受け時間は10秒まで」という制限時間を設定する場合。
- 処理時間の計測: 「このバッチ処理に何分かかったか」をログに出力する場合。
- トークンの有効期限: 「発行から1時間は有効」といった有効期間の計算。
- リトライ間隔: 「エラーが起きたら500ミリ秒待ってから再試行する」という待機時間の制御。
これらのシーンでは、数値を直接扱うよりもDurationオブジェクトとして渡すほうが、単位の取り違えを防げます。
私の実務経験では、REST APIクライアントのタイムアウト設定でDurationを最も頻繁に使用しています。
Spring BootのHTTPクライアントでは、接続タイムアウトと読み取りタイムアウトをDuration型で指定するのが標準的な書き方です。ただし、従来主流だったRestTemplateはSpring Framework 7.0で非推奨化されています。新規プロジェクトでは、同期通信にRestClient(Spring Boot 3.2以降)、非同期・ストリーミングにはWebClientを使うのが現在の標準です。
Durationの使い方|生成・変換・計算をコード例で解説
ここからは実際のコードを見ながら使い方を解説します。java.time.Durationをインポートして利用します。
基本的な操作はシンプルで、直感的に記述できる設計になっています。
Duration.ofで時間を生成する(ofSeconds / ofMinutes など)
Durationオブジェクトを生成するには、静的メソッドであるofシリーズを使います。
// 10秒のDurationを生成
Duration tenSeconds = Duration.ofSeconds(10);
// 5分のDurationを生成
Duration fiveMinutes = Duration.ofMinutes(5);
// 2時間のDurationを生成
Duration twoHours = Duration.ofHours(2);
// 500ミリ秒のDurationを生成
Duration halfSecond = Duration.ofMillis(500);
メソッド名に単位が含まれているため、コードを読んだだけで「どのくらいの時間量なのか」がひと目でわかります。
数字の「10」だけが書かれているコードとは、可読性に雲泥の差があります。
betweenで2つの日時からDurationを算出する
2つの日時の差分を求めたい場合は、Duration.between()を使います。開始と終了の日時さえ渡せば、差分を自動計算してくれます。
LocalDateTime start = LocalDateTime.of(2026, 1, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2026, 1, 1, 12, 30, 0);
// startとendの差(2時間30分)を算出
Duration diff = Duration.between(start, end);
このメソッドのおかげで、日を跨ぐ計算でも、時刻間の正確な差分を自前で実装する必要がなくなりました。
ただし、Durationは物理的な時間差(秒・ナノ秒)を計算するため、うるう年や月の日数差を考慮した「カレンダー上の期間」が必要な場合はPeriodを使います。
toMillis / toMinutes など変換メソッドの使い方
生成したDurationを、特定の単位(ミリ秒や分など)の数値に戻したい場合も多々あります。その際はtoシリーズのメソッドを使います。
Duration duration = Duration.ofMinutes(2);
// 全体をミリ秒に変換(120000が表示される)
long millis = duration.toMillis();
// 全体を秒に変換(120が表示される)
long seconds = duration.toSeconds();
// 全体を分に変換(2が表示される)
long minutes = duration.toMinutes();
ここで注意すべき点は、計算結果が整数(long型)で返されることです。
例えば「90秒」のDurationに対してtoMinutes()を実行すると、1.5ではなく「1」が返されます。
端数は切り捨てられる仕様を理解して使いましょう。
Duration.parseでISO-8601文字列からDurationを生成する
設定ファイルやAPIレスポンスから時間の値を文字列で受け取る場合、Duration.parse()が役立ちます。
ISO-8601形式の文字列をDurationオブジェクトに変換できます。
// "PT5M30S" → 5分30秒のDuration
Duration parsed = Duration.parse("PT5M30S");
// "PT2H" → 2時間のDuration
Duration twoHours = Duration.parse("PT2H");
// "PT0.5S" → 500ミリ秒のDuration
Duration halfSec = Duration.parse("PT0.5S");
「P」はISO-8601の期間指定子(period designator)、「T」は時間部分の開始を示す区切り文字です。
H(時間)、M(分)、S(秒)を組み合わせて表現します。
不正な形式を渡すとDateTimeParseExceptionがスローされるため、外部入力を受け取る場合はtry-catchで囲みましょう。
plus / minusで時間の加算・減算を行う
Durationはイミュータブル(不変)なオブジェクトです。加算や減算を行うと、元のオブジェクトは変更されず新しいDurationが返されます。
Duration base = Duration.ofMinutes(10);
// 5分を加算 → 15分
Duration added = base.plusMinutes(5);
// 3秒を減算 → 9分57秒
Duration subtracted = base.minusSeconds(3);
// 2倍にする → 20分
Duration doubled = base.multipliedBy(2);
// 半分にする → 5分
Duration halved = base.dividedBy(2);
特にmultipliedBy()とdividedBy()は、リトライ間隔の指数バックオフ(1秒→2秒→4秒と倍々で増やす)を実装する際に便利です。
DurationとPeriodの違い
結論として、秒・分・時間の計算にはDuration、日・月・年の計算にはPeriodを使います。
JavaのDate and Time APIには、時間の量を表すクラスがもう一つあります。それがPeriodです。
「どちらを使えばいいのか」と迷う開発者は少なくありません。それぞれの特性を理解して使い分ける必要があります。
時間単位(秒・ナノ秒)を扱うDuration
Durationは、物理的な時間の長さを扱います。「1日は必ず24時間であり、1時間は必ず60分である」という前提に基づいた計算を行います。
そのため、PCの内部時計やログのタイムスタンプ、ストップウォッチのような計測にはDurationが最適です。
ナノ秒単位の精密な計算が必要な場合は、迷わずこちらを選んでください。
日・月・年を扱うPeriod
一方、Periodはカレンダー上の期間(年月日)を扱います。「1か月」という期間は、28日の場合もあれば31日の場合もあります。
Periodはこのような「カレンダー特有の曖昧さ」を吸収するためのクラスです。
「3か月後の日付を求めたい」「誕生日まであと何か月何日か知りたい」という場合は、Periodを使います。なお、2つの日付の間の総日数(例: 73日)を知りたい場合は、PeriodではなくChronoUnit.DAYS.between()を使います。
これをDurationで計算すると、月ごとの日数の違いでズレが生じます。
使い分けの基準と判断ポイント
使い分けのルールはシンプルです。
- 秒、分、時間単位の計算ならDurationを使う。
- 日、月、年単位の計算ならPeriodを使う。
具体例を挙げると、「イベント開催まであと3日」でも、厳密に「72時間」として管理するならDuration、「カレンダー上で3マス先」という意味ならPeriodです。
要件が物理時間かカレンダー時間かで使い分けます。使い分けを図にすると、次のようになります。
実務では「3日後」のつもりでDuration.ofDays(3)を使い、月をまたぐ処理で意図しない日付になって混乱した、という相談を受けたことがある。Durationのdaysは常に24時間固定だが、業務要件でいう「日」はカレンダー上の日付を指すことが多い。要件が「暦の上での日付」なのか「物理的な時間の長さ」なのかを最初に確認する癖をつけておくと、こうした事故を防げる。
longではなくDurationを使うべき理由と注意点
結論として、時間を扱う変数・引数はlong型ではなくDuration型にすべきです。単位の曖昧さが消え、可読性と安全性が向上します。
long型の変数で時間を管理することも可能ですが、Durationクラスをあえて使うことには明確なメリットがあります。
可読性の高い時間計算ができる
最大のメリットはコードの読みやすさです。以下のようなコード比較を見てみましょう。
悪い例(long型で管理)long timeout = 300000; // これが5分だと即座にわかりますか?
long startTime = System.currentTimeMillis();
if (System.currentTimeMillis() - startTime > timeout) {
// タイムアウト処理
}
良い例(Durationで管理)
Duration timeout = Duration.ofMinutes(5);
// 直感的で誤解の余地がない
ofMinutes(5)と書かれていれば、誰が読んでも「5分」だと理解できます。
ゼロの数を数えて「3万ミリ秒か、30万ミリ秒か」と悩む時間がなくなります。コードの意図を明確に伝えるためにDurationは有効です。
longではなくDurationを使うべきケース
メソッドの引数に時間を渡す場合は、必ずDurationを使うべきです。
具体的には、void wait(long time)というメソッドがあったとします。このtimeは秒でしょうか、ミリ秒でしょうか。
ドキュメントを読まないとわかりません。しかし、void wait(Duration time)であれば、単位の曖昧さは完全に消滅します。
呼び出し側でDuration.ofSeconds(10)と渡せば、10秒待機することが確定するからです。
型安全性を高め、単位の勘違いによるバグを防ぐために、API設計では積極的にDurationを採用しましょう。
私が過去に関わったプロジェクトでは、メソッド引数のlong timeoutが秒なのかミリ秒なのかをめぐって実際にバグが発生しました。
呼び出し側が「秒」のつもりで30を渡したのに、受け取り側は「ミリ秒」として処理し、わずか30ミリ秒でタイムアウトしていたのです。
Duration型であれば、この種のバグは原理的に発生しません。
マイクロ秒・ナノ秒の扱いで注意すべきポイント
Durationはナノ秒まで扱えますが、すべてのシステムやデータベースがナノ秒に対応しているわけではありません。
Javaプログラム内で完結する計算なら問題ありませんが、データベースに保存したり、JSONとして外部APIに送信したりする際には桁落ちが発生します。
MySQLは5.6.4以降でマイクロ秒(6桁)まで対応していますが、カラム定義でDATETIME(6)のように精度を明示する必要があります。
PostgreSQLもマイクロ秒までの対応です。ナノ秒が必要な場合はアプリケーション側で文字列やBIGINTに変換して保存する設計が必要です。
システム全体でどの精度まで許容するかを事前に設計段階で決めておく必要があります。
実践例:処理時間計測にDurationを使う
実際の開発現場で最もよく使うパターンの一つが、プログラムの実行速度計測です。
パフォーマンスチューニングや障害調査において、正確な時間計測は欠かせません。
System.nanoTimeとの組み合わせ
処理時間を計測する場合、System.currentTimeMillis()ではなくSystem.nanoTime()を使うのが定石です。
OSの時計変更の影響を受けないからです。
long start = System.nanoTime();
// 計測したい重い処理を実行
doHeavyTask();
long end = System.nanoTime();
// 差分をDurationに変換
Duration executionTime = Duration.ofNanos(end - start);
計測したナノ秒の差分をDuration.ofNanos()に渡すことで、後の扱いが楽になります。
なお、System.currentTimeMillis()とSystem.nanoTime()の使い分けは、計測する時間のスケールによって判断します。
Durationをフォーマットして「5分10秒」形式の文字列にする
計測した時間をログ出力する際、単にtoString()を呼ぶと「PT5M10.345S」のようなISO-8601形式の文字列が出力されます。
これは機械には読みやすいですが、人間には読みづらい形式です。
日本人が読みやすい形式に変換するユーティリティメソッドを用意すると便利です。
long min = executionTime.toMinutes();
long sec = executionTime.toSecondsPart();
long millis = executionTime.toMillisPart();
String logMessage = String.format("処理時間: %d分 %d秒 %d", min, sec, millis);
// 出力例: 処理時間: 5分 10秒 345
Java 9以降であれば、toSecondsPart()のように、単位変換後の「端数部分」だけを取得するメソッドが使えます。
2026年現在の最新LTSであるJava 25にも含まれています(2026年3月には非LTS版のJava 26もリリース済み)。新規プロジェクトでは迷わず使えます。
レガシー環境でJava 8を使い続けている場合は、割り算の余り(%)を使って計算する必要があります。
toString() / ISO-8601形式のままでは読みにくい
PT5M1102.3453S
↓ 部品ごとにメソッドで取り出す
METHOD / 取り出すメソッドと値
↓ String.format で1行にまとめてログへ
toString()のISO-8601形式(PT5M10.345S)をログに出したまま放置すると、障害調査で秒数をぱっと読み取れない。実際、そういう場面に何度も遭遇している。ログ出力の設計段階で人間が読みやすい形式に変換しておくかどうかは、後の調査効率を大きく左右する。
処理時間の比較ロジックにDurationを使う
「処理Aが処理Bより長かった場合」といった比較ロジックも、Durationならスマートに書けます。
if (durationA.compareTo(durationB) > 0) {
// Aの方が時間がかかっている
}
数値を直接比較するよりも、意図が明確になります。
また、isZero()やisNegative()といった状態判定メソッドも用意されているため、条件分岐がすっきりと記述できます。
Durationを扱うときによくあるエラーと対処法
便利なDurationですが、使い方を誤るとエラーに遭遇します。事前に落とし穴を知っておけば回避できます。
結論として、Durationのエラーの多くはOverflowと負の値の扱いに集約されます。事前に対処法を押さえておけば防げます。
Overflow系エラーが起きるケース
Durationは秒をlong、ナノ秒部分をintで保持しているため、表せる長さの上限は約2,920億年(longの最大値の秒数)です。なお、toNanos()でlongのナノ秒に変換する場合の上限は約292年(約9.2×10^18ナノ秒)で、これを超えるDurationにtoNanos()を呼ぶとArithmeticExceptionが発生します(toMillis()も同様に、longのミリ秒を超えると発生します)。
通常のWebアプリケーション開発で上限に達することはまずありません。
ただし、テストコードで極端な値を渡すとArithmeticExceptionが発生する可能性があるため注意してください。
ChronoUnitとの併用時に注意する点
Durationから値を取得する際、get(TemporalUnit unit)メソッドが使えますが、ここで指定できる単位には制限があります。
// これはエラーになります
long months = duration.get(ChronoUnit.MONTHS);
Duration#get(TemporalUnit)で指定できる単位はChronoUnit.SECONDSとChronoUnit.NANOSの2つだけです。MONTHSやYEARSのような不定の長さの単位はもちろん、MINUTESやHOURSを指定した場合もUnsupportedTemporalTypeExceptionがスローされます。分・時間を取り出したいときはtoMinutes()やtoHoursPart()を使います。
このコードを実行するとUnsupportedTemporalTypeExceptionがスローされます。
「月」や「年」を扱いたい場合は、前述のPeriodクラスを使ってください。
時間計算の罠(負のDurationなど)
Durationは負の値(マイナスの時間)を持てます。
Duration negative = Duration.between(end, start); // 終了時刻と開始時刻を逆にする
計算順序を間違えると、意図せず負の値が生成されます。その後のロジックで「時間が正の数であること」を前提にしていると、バグの原因になります。
計算結果を受け取った後は、必要に応じてabs()メソッドを呼び出し、絶対値に変換する防御的プログラミングを推奨します。
また、Java 18以降ではisPositive()メソッドが追加されました。isNegative()と組み合わせることで、正負のチェックがより明確に書けます。
// 常に正の値にする
Duration safeDuration = duration.abs();
よくある質問
Q1. JavaのDurationとは何ですか?
A1. Durationは、秒とナノ秒に基づいて「時間の長さ(時間量)」を表すJavaのクラスです。「10時」のような時刻ではなく、「30分間」のような期間を扱う点が特徴で、java.time.Durationとしてインポートして使います。
Q2. Durationの読み方は?
A2. 「デュレーション」と読みます。「継続時間」「期間」を意味する英単語がそのままクラス名として採用されています。
Q3. DurationとPeriodの違いは何ですか?
A3. Durationは秒・分・時間といった物理的な時間量を扱い、Periodは年・月・日といったカレンダー上の期間を扱います。タイムアウトや処理時間の計測にはDuration、誕生日までの「あと何か月何日」のようなカレンダー上の期間にはPeriodを使います。
Q4. Duration.between()の使い方は?
A4. Duration.between(開始, 終了)の形式で、2つのLocalDateTimeやInstantを渡すと、その差分をDurationとして取得できます。開始と終了を逆にすると負のDurationが返されるため、順序に注意が必要です。
Q5. DurationをString(文字列)に変換するには?
A5. toString()を呼ぶとPT5M10.345SのようなISO-8601形式の文字列になります。人間が読みやすい「5分10秒」形式にしたい場合は、toMinutes()やtoSecondsPart()で単位ごとの値を取り出して自前でフォーマットします。
Q6. Duration.ofMillisの使い方は?
A6. Duration.ofMillis(500)のように、ミリ秒数を引数に渡すとその長さのDurationインスタンスが生成されます。ofSeconds・ofMinutes・ofHoursなど、単位ごとに専用のofメソッドが用意されています。
Q7. DurationとLocalDateTimeの違いは何ですか?
A7. LocalDateTimeは「2026年1月1日12時」のような時刻の一点(点)を表し、Durationはその2点間の長さ(線)を表します。役割が異なるため、カレンダー上の日時を扱うか、時間の長さを扱うかで使い分けます。
Q8. Durationで負の値は扱えますか?
A8. はい、扱えます。Duration.between()で開始と終了の順序を逆にすると負のDurationが生成されます。負の値を避けたい場合は、abs()メソッドで絶対値に変換できます。
まとめ:Java Durationで時間計算を安全かつ簡潔に
Durationクラスは、Javaにおける時間計算の常識を変えました。
理由は、開発者が行うべき面倒な単位変換や計算ロジックを、APIがすべて肩代わりしてくれるからです。
今回紹介した内容を振り返ります。
Durationは「時間の量(長さ)」を表すクラスであり、時刻を表すものではありません。ofMinutesやbetweenメソッドを使うことで、直感的に時間を生成・算出できます。- カレンダー計算(年月日)には
Periodを使い、物理時間(時分秒)にはDurationを使います。 long型の変数で時間を持ち回るよりも、圧倒的に可読性と安全性が向上します。
それでも実務のコードレビューでは、いまだにlong型で時間を持ち回るコードを見かける。「動いているから」という理由で放置されがちだが、Durationへの置き換えはリスクの低いリファクタリングであり、後回しにする理由は薄いというのが筆者の考えだ。
かつての私のように、ミリ秒の計算でバグを出して頭を抱える必要はもうありません。
明日からのコーディングでは、変数の型をlongからDurationに変えてみてください。
それだけで、あなたのコードは驚くほど美しく、堅牢になるはずです。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-14 | 雰囲気だけの挿絵を外した |
| 3 | 2026-09-16 | 2026年9月時点の情報で全体を見直し |
| 2 | 2026-07-09 | 2026年7月時点の情報で全体を見直し |
| 1 | 2025-11-19 | 初版 |




