本ページはプロモーションが含まれています

Java入門

Java Durationとは?時間計算が10倍楽になる使い方

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

Javaでの時間計算に苦戦していませんか。私はかつて、日付や時間の計算処理で何度もバグを出して苦しみました。特にDateクラスやCalendarクラスを使っていた頃は、ミリ秒単位の計算で桁数を間違えるミスが絶えませんでした。しかし、Java8から導入されたDate and Time API、特にDurationクラスを知ってからは世界が変わりました。

この記事を読めば、時間の「量」を扱うDurationクラスのすべてがわかります。正確な時間計算の方法から、よくあるミスの回避法まで習得可能です。これまでの複雑な時間の足し算や引き算から解放されましょう。

JavaのDurationとは?仕組みと役割をわかりやすく解説

JavaのDurationとは、2つの時刻の「間隔(期間)」を表すクラスです。結論から言えば、数秒や数分といった「時間の長さ」を扱うなら、必ずこのクラスを使うべきです。

理由は、Durationを使うことで、人間が直感的に理解できる単位で時間をプログラムできるからです。従来のように「5分」を表すために5 * 60 * 1000のような計算式をコードに書く必要がなくなります。

Durationが表すもの(時刻ではなく“時間量”)

Durationが保持しているのは「ある時点」ではなく「時間の量」です。例えば「10時00分」は時刻ですが、「30分間」は時間量です。Durationはこの「30分間」や「5時間」「10秒」といった長さを管理します。

内部的には「秒(seconds)」と「ナノ秒(nanos)」の2つの値で時間を保持しています。これにより、非常に短い時間の計測から、数千時間に及ぶ長い期間までを正確に表現可能です。物理的な時間の長さを厳密に扱うために設計されています。

LocalDateTimeとの違いは何か

LocalDateTimeDurationは、根本的に役割が異なります。

  • LocalDateTime: 「2023年1月1日 12時00分」というを表します。
  • Duration: 「5時間30分」という線(長さ)を表します。

初心者はこの違いを混同しがちです。カレンダー上の特定の日時を指したい場合はLocalDateTimeを使い、その日時までの残り時間や、処理にかかった時間を表す場合にはDurationを選びます。役割を明確に分けることで、バグの少ないコードが書けるようになります。

Durationが使われる典型シーン

開発現場において、Durationは以下のような場面で頻繁に登場します。

  • タイムアウト設定: 「接続待ち受け時間は10秒まで」という制限時間を設定する場合。
  • 処理時間の計測: 「このバッチ処理に何分かかったか」をログに出力する場合。
  • トークンの有効期限: 「発行から1時間は有効」といった有効期間の計算。
  • リトライ間隔: 「エラーが起きたら500ミリ秒待ってから再試行する」という待機時間の制御。

これらのシーンでは、数値を直接扱うよりもDurationオブジェクトとして渡すほうが、単位の取り違えを防げます。

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(2023, 1, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2023, 1, 1, 12, 30, 0);

// startとendの差(2時間30分)を算出
Duration diff = Duration.between(start, end);

このメソッドのおかげで、日を跨ぐ計算や、うるう年を考慮すべき複雑な計算ロジックを自前で実装する必要がなくなりました。すべてJavaのAPIにお任せできます。

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とPeriodの違い

JavaのDate and Time APIには、時間の量を表すクラスがもう一つあります。それがPeriodです。「どちらを使えばいいのか」と迷う開発者は少なくありません。それぞれの特性を理解して使い分ける必要があります。

時間単位(秒・ナノ秒)を扱うDuration

Durationは、物理的な時間の長さを扱います。「1日は必ず24時間であり、1時間は必ず60分である」という前提に基づいた計算を行います。

そのため、PCの内部時計やログのタイムスタンプ、ストップウォッチのような計測にはDurationが最適です。ナノ秒単位の精密な計算が必要な場合は、迷わずこちらを選んでください。

日・月・年を扱うPeriod

一方、Periodはカレンダー上の期間(年月日)を扱います。「1か月」という期間は、28日の場合もあれば31日の場合もあります。Periodはこのような「カレンダー特有の曖昧さ」を吸収するためのクラスです。

「3か月後の日付を求めたい」「誕生日まであと何日か知りたい」という場合は、Periodを使います。これをDurationで計算しようとすると、月ごとの日数の違いでズレが生じる可能性があります。

使い分けの基準と判断ポイント

使い分けのルールはシンプルです。

  1. 秒、分、時間単位の計算ならDurationを使う。
  2. 日、月、年単位の計算ならPeriodを使う。

例えば「イベント開催まであと3日」という表現でも、厳密に「72時間」として管理したいならDurationを使います。逆に「カレンダー上で3マス先」という意味ならPeriodを使います。システムが何を基準に時間を管理しているかに合わせて選択します。

Durationを使うメリットと注意点

なぜ、わざわざDurationクラスを使うのでしょうか。単純なlong型の変数で時間を管理することも技術的には可能です。しかし、あえてオブジェクトとして扱うことには大きなメリットがあります。

可読性の高い時間計算ができる

最大のメリットはコードの読みやすさです。以下のようなコード比較を見てみましょう。

悪い例(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を採用しましょう。

マイクロ秒・ナノ秒の扱いで注意すべきポイント

Durationはナノ秒まで扱えますが、すべてのシステムやデータベースがナノ秒に対応しているわけではありません。

Javaプログラム内で完結する計算なら問題ありませんが、データベースに保存したり、JSONとして外部APIに送信したりする際に、桁落ちが発生することがあります。MySQLなどの古いバージョンではミリ秒までしか保存できない設定になっていることも多いです。

システム全体でどの精度まで許容するかを事前に設計段階で決めておく必要があります。

実践例:処理時間(実行時間)の計測に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()に渡すことで、後の扱いが非常に楽になります。

ログに出すためのフォーマット変換

計測した時間をログ出力する際、単に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

Java9以降であれば、toSecondsPart()のように、単位変換後の「端数部分」だけを取得するメソッドが使えます。Java8の場合は、割り算の余り(%)を使って計算する必要があります。

処理時間の比較ロジックにDurationを使う

「処理Aが処理Bより長かった場合」といった比較ロジックも、Durationならスマートに書けます。

if (durationA.compareTo(durationB) > 0) {
    // Aの方が時間がかかっている
}

数値を直接比較するよりも、意図が明確になります。また、isZero()isNegative()といった状態判定メソッドも用意されているため、条件分岐がすっきりと記述できます。

Durationを扱うときによくあるエラーと対処法

便利に見えるDurationですが、使い方を誤ると予期せぬエラーに遭遇することがあります。事前に落とし穴を知っておけば、回避可能です。

Overflow系エラーが起きるケース

Durationは内部でlong型を使用していますが、扱える時間の長さには上限があります。具体的には約292年分のナノ秒までです。

通常のWebアプリケーション開発でこの上限を超えることは稀ですが、天文学的な計算や、理論上の極端なケースをテストする際にArithmeticExceptionが発生する可能性があります。無限に近い時間を扱いたい場合は注意が必要です。

ChronoUnitとの併用時に注意する点

Durationから値を取得する際、get(TemporalUnit unit)メソッドを使うことができますが、ここで指定できる単位には制限があります。

// これはエラーになります
long months = duration.get(ChronoUnit.MONTHS);

Durationはあくまで「物理的な時間」を扱うため、「月」や「年」といった不定の長さを持つ単位には変換できません。このコードを実行するとUnsupportedTemporalTypeExceptionがスローされます。「月」や「年」を扱いたい場合は、前述のPeriodクラスを使ってください。

時間計算の罠(負のDurationなど)

Durationは負の値(マイナスの時間)を持つことができます。

Duration negative = Duration.between(end, start); // 終了時刻と開始時刻を逆にする

計算順序を間違えると、意図せず負の値が生成されます。その後のロジックで「時間が正の数であること」を前提にしていると、バグの原因になります。計算結果を受け取った後は、必要に応じてabs()メソッドを呼び出し、絶対値に変換する防御的プログラミングを推奨します。

// 常に正の値にする
Duration safeDuration = duration.abs();

まとめ:Durationを使いこなすと時間計算が安全で簡単になる

Durationクラスは、Javaにおける時間計算の常識を変えました。

理由は、開発者が行うべき面倒な単位変換や計算ロジックを、APIがすべて肩代わりしてくれるからです。

今回紹介した内容を振り返ります。

  • Durationは「時間の量(長さ)」を表すクラスであり、時刻を表すものではありません。
  • ofMinutesbetweenメソッドを使うことで、直感的に時間を生成・算出できます。
  • カレンダー計算(年月日)にはPeriodを使い、物理時間(時分秒)にはDurationを使います。
  • long型の変数で時間を持ち回るよりも、圧倒的に可読性と安全性が向上します。

かつての私のように、ミリ秒の計算でバグを出して頭を抱える必要はもうありません。明日からのコーディングでは、変数の型をlongからDurationに変えてみてください。それだけで、あなたのコードは驚くほど美しく、堅牢になるはずです。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門