Java Tips | 基本ユーティリティ:日付差分計算

Java Java
スポンサーリンク

日付差分計算は「どれくらい時間が経ったか」を正しく言語化する作業

業務システムでは、「締切まであと何日か」「利用期間は何ヶ月か」「滞在時間は何時間か」といった「差分」を扱う場面が本当に多いです。
このときに、単純に「ミリ秒の差を出して割り算する」ようなやり方をすると、うるう年や月の長さ、サマータイムなどで簡単に破綻します。

Java では、java.timePeriodDurationChronoUnit を使うことで、「日付の差」「日時の差」を安全かつ読みやすく計算できます。
ここでは、初心者向けに「どの型をいつ使うか」「どうユーティリティ化すると実務で効くか」を、例題付きでかみ砕いて説明していきます。


日付差分の基本:Period と ChronoUnit を使う

LocalDate 同士の「日数差」を出す

「開始日と終了日の間に何日あるか」を知りたいときは、LocalDateChronoUnit.DAYS の組み合わせがシンプルです。

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class DateDiffDaysExample {

    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2025, 1, 1);
        LocalDate end   = LocalDate.of(2025, 1, 14);

        long days = ChronoUnit.DAYS.between(start, end);
        System.out.println(days);  // 13
    }
}
Java

ここでのポイントは、「between(start, end) は『start から end までに何日“挟まっているか”』を返す」という感覚です。
2025-01-01 から 2025-01-14 までは 13 日分の差があるので、結果は 13 になります(両端を含めるかどうかは業務要件次第)。

この ChronoUnit.DAYS.between は、「とにかく日数差が欲しい」というときの鉄板パターンとして覚えておいてください。

年・月・日をまとめて扱いたいときは Period

「何年何ヶ月何日」という形で差分を表現したいときは、Period を使います。

import java.time.LocalDate;
import java.time.Period;

public class PeriodExample {

    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2020, 1, 1);
        LocalDate end   = LocalDate.of(2025, 3, 15);

        Period p = Period.between(start, end);

        System.out.println(p.getYears());  // 5
        System.out.println(p.getMonths()); // 2
        System.out.println(p.getDays());   // 14
    }
}
Java

この例では、「5 年 2 ヶ月 14 日」という差分になります。
ここで深掘りしたいのは、「Period は『カレンダー的な差分』を表す」という点です。

月の長さは 28〜31 日でバラバラ、うるう年もある──こうした「カレンダーの揺らぎ」をきちんと考慮したうえで、「年・月・日」の差を出してくれるのが Period です。
「契約期間」「勤続年数」「年齢」のように、「カレンダー感覚」が重要な差分には Period を使うのが自然です。


日時差分の基本:Duration と ChronoUnit を使う

LocalDateTime 同士の「時間差」「分差」を出す

「開始日時と終了日時の間に何時間あるか」「何分あるか」を知りたいときは、ChronoUnit.HOURSChronoUnit.MINUTES を使います。

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class DateTimeDiffExample {

    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2025, 1, 14, 9, 0);
        LocalDateTime end   = LocalDateTime.of(2025, 1, 14, 18, 30);

        long hours = ChronoUnit.HOURS.between(start, end);   // 9
        long minutes = ChronoUnit.MINUTES.between(start, end); // 570

        System.out.println(hours);
        System.out.println(minutes);
    }
}
Java

「勤務時間」「滞在時間」「処理時間」など、時間単位での差分が欲しいときに、この書き方はとても素直です。

Duration で「何時間何分何秒」をまとめて扱う

Duration は、「時間ベースの差分」を表すクラスです。

import java.time.Duration;
import java.time.LocalDateTime;

public class DurationExample {

    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2025, 1, 14, 9, 0);
        LocalDateTime end   = LocalDateTime.of(2025, 1, 14, 18, 30);

        Duration d = Duration.between(start, end);

        System.out.println(d.toHours());      // 9
        System.out.println(d.toMinutes());    // 570
        System.out.println(d.getSeconds());   // 34200
    }
}
Java

Duration は「秒」や「ナノ秒」を内部表現として持っていて、toHours()toMinutes() で好きな単位に変換できます。
「処理時間をミリ秒でログに出したい」「API のタイムアウトまでの残り時間を秒で管理したい」といった場面で非常に便利です。

ここでの重要ポイントは、「Period はカレンダー(日付)ベース、Duration は時間ベース」という切り分けです。
「何年何ヶ月何日」は Period、「何時間何分何秒」は Duration、と覚えておくと迷いません。


実務で使える「日付差分ユーティリティ」の形

日数差を出すユーティリティ

業務コードのあちこちで ChronoUnit.DAYS.between と書くのは少しうるさいので、ユーティリティにまとめてしまうのが定番です。

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public final class DateDiffs {

    private DateDiffs() {}

    public static long daysBetween(LocalDate start, LocalDate end) {
        if (start == null || end == null) {
            throw new IllegalArgumentException("start and end must not be null");
        }
        return ChronoUnit.DAYS.between(start, end);
    }
}
Java

使う側はこうなります。

long days = DateDiffs.daysBetween(startDate, endDate);
Java

ここで深掘りしたいのは、「null チェックや例外方針をユーティリティ側に閉じ込める」という設計です。
「null のときは 0 を返すのか、例外にするのか」といったポリシーを、ユーティリティの中で統一しておくと、呼び出し側のコードがシンプルになります。

営業日(平日)だけを数えるユーティリティのイメージ

実務では、「土日を除いた営業日数を知りたい」という要件もよく出てきます。
これは標準 API だけでは一発では出せないので、ループで日付を進めながらカウントする形になります。

import java.time.DayOfWeek;
import java.time.LocalDate;

public final class BusinessDays {

    private BusinessDays() {}

    public static long businessDaysBetween(LocalDate startInclusive, LocalDate endExclusive) {
        if (startInclusive == null || endExclusive == null) {
            throw new IllegalArgumentException("dates must not be null");
        }
        long days = 0;
        for (LocalDate d = startInclusive; d.isBefore(endExclusive); d = d.plusDays(1)) {
            DayOfWeek dow = d.getDayOfWeek();
            if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
                days++;
            }
        }
        return days;
    }
}
Java

この例では、開始日を含み、終了日は含まない形([start, end))で数えています。
祝日を除外したい場合は、別途「祝日カレンダー」を持っておき、それを参照しながらカウントすることになります。

ここでのポイントは、「業務ルール(営業日定義)をユーティリティに閉じ込める」ことです。
「土日だけ除く」「土日+祝日を除く」「会社独自の休業日も除く」など、プロジェクトごとのルールを一箇所に集約しておくと、仕様変更に強くなります。


差分の向き(マイナス)と端の含み方に注意する

start と end の順番で符号が変わる

ChronoUnit.DAYS.between(start, end) は、「start から end まで」の差を返します。
startend より後ろの日付なら、結果はマイナスになります。

LocalDate a = LocalDate.of(2025, 1, 14);
LocalDate b = LocalDate.of(2025, 1, 10);

long diff1 = ChronoUnit.DAYS.between(a, b); // -4
long diff2 = ChronoUnit.DAYS.between(b, a); // 4
Java

業務で「締切まであと何日」といった表示をするときは、マイナスをそのまま出すのか、絶対値を取るのか、0 未満は 0 とみなすのか、といった仕様を決めておく必要があります。

端を含めるかどうかを業務で決める

「2025-01-01 から 2025-01-14 までの期間は何日か?」という問いに対して、

ChronoUnit.DAYS.between(2025-01-01, 2025-01-14) は 13 を返します。

これは「1 日から 14 日の前日(13 日)までで 13 日分」という考え方です。
一方、「1 日と 14 日を両方含めて 14 日間」と数えたい要件もあります。

その場合は、

long daysInclusive = ChronoUnit.DAYS.between(start, end) + 1;
Java

のように、「両端を含めるなら +1 する」というルールをユーティリティ側に明示的に書いておくと、後から読んだ人にも意図が伝わります。


タイムゾーンをまたぐ差分計算の注意点

ZonedDateTime での差分は「どの時間軸で見るか」を意識する

タイムゾーン付きの日時(ZonedDateTime)同士の差分を取るときは、「UTC ベースで見るのか」「ローカル時間ベースで見るのか」を意識する必要があります。

import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class ZonedDiffExample {

    public static void main(String[] args) {
        ZonedDateTime tokyo = ZonedDateTime.of(2025, 1, 14, 9, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
        ZonedDateTime utc   = tokyo.withZoneSameInstant(ZoneId.of("UTC"));

        long hours = ChronoUnit.HOURS.between(utc, tokyo);
        System.out.println(hours);  // 0 ではなく、計算の仕方次第で変わり得る
    }
}
Java

実務では、「内部では UTC にそろえて差分を計算し、表示だけローカル時間にする」という設計が多いです。
「どの時間軸で差分を取るか」をチームで決め、それをユーティリティに落とし込んでおくと、安全に運用できます。


まとめ:日付差分計算で初心者が身につけるべき感覚

日付差分計算は、「なんとなくミリ秒を引き算する」ではなく、「どの単位で、どのルールで、何を知りたいのか」を設計する作業です。

カレンダー的な差分(年・月・日)は Period、時間的な差分(時間・分・秒)は DurationChronoUnit を使う。
日数差は ChronoUnit.DAYS.between を基本形として覚え、端を含めるかどうかは業務要件として明示する。
営業日や会社独自のカレンダーは、ループ+判定ロジックをユーティリティに閉じ込める。
タイムゾーンや UTC をまたぐ差分は、「どの時間軸で見るか」をチームで決めてから実装する。

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