Java で2つの日付の差を求めるには、総日数や総月数なら ChronoUnit.DAYS.between(開始, 終了)、「1年2か月5日」のような年月日の組なら Period.between、時間・分・秒なら Duration.between を使います。 3つは名前が似ていますが、返す値の意味が異なります。たとえば Period.getDays() は総日数ではなく、「年と月を取り除いた残りの日数」です。

この記事では、同じ2つの日付を3つの方法に通した結果を1枚の比較表にしました。月末、2月29日、逆順、夏時間、型の組み合わせで起きる例外まで、すべて実行結果つきで確認します。

確認環境:Ubuntu 24.04 / OpenJDK 21.0.10。出力はすべて java ファイル名.java で実行した結果です。

やりたいこと別の早見表

やりたいこと 書き方 結果の例
総日数 ChronoUnit.DAYS.between(a, b) 2026-01-15 → 2027-03-20 で 429
総月数(切り捨て) ChronoUnit.MONTHS.between(a, b) 同じ2日付で 14
満年数・年齢 ChronoUnit.YEARS.between(a, b) または Period.between(a, b).getYears() 同じ2日付で 1
「○年○か月○日」 Period.between(a, b) の getYears() getMonths() getDays() 1年2か月5日(P1Y2M5D)
時間・分・秒の差 Duration.between(日時1, 日時2) PT57H15M20S → 2日9時間15分20秒
LocalDate どうしを Duration で Duration.between(a.atStartOfDay(), b.atStartOfDay()) PT48H
両端を含む日数 ChronoUnit.DAYS.between(a, b) + 1 1/1〜1/31 で 31
差の絶対値 Math.abs(ChronoUnit.DAYS.between(a, b)) -282 → 282
夏時間を考慮した時間差 Duration.between(ZonedDateTime, ZonedDateTime) 24時間ではなく 23時間/25時間
土日を除く日数 a.datesUntil(b).filter(平日).count() 2026年10月で 22

どの方法も「開始を含み、終了を含まない」数え方です。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java 日付の差分計算|ChronoUnit・Period・Duration」

3つの方法を同じ日付で比べる

方法 返すもの 受け取れる型 向いている用途
ChronoUnit.XXX.between 指定した単位での整数(long) LocalDate、LocalDateTime、ZonedDateTime、Instant など 総日数、総月数、満年数
Period.between 年・月・日の組(Period) LocalDate のみ 年齢、「○年○か月○日」の表示
Duration.between 秒とナノ秒の量(Duration) 時刻を持つ型(LocalDateTime、ZonedDateTime、Instant、LocalTime) 経過時間、所要時間
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class Compare {
    public static void main(String[] args) {
        String[][] pairs = {
            {"2026-01-01", "2026-01-31"}, {"2026-01-31", "2026-02-28"}, {"2026-01-31", "2026-03-01"},
            {"2026-01-15", "2027-03-20"}, {"2024-02-29", "2025-02-28"}, {"2024-02-29", "2028-02-29"},
            {"2025-10-03", "2026-10-03"}, {"2026-10-03", "2025-12-25"},
        };
        System.out.println("| 開始 → 終了 | DAYS | MONTHS | YEARS | Period | getDays() | Duration |");
        System.out.println("|---|---|---|---|---|---|---|");
        for (String[] p : pairs) {
            LocalDate a = LocalDate.parse(p[0]);
            LocalDate b = LocalDate.parse(p[1]);
            Period period = Period.between(a, b);
            // Duration は時刻が必要なので、その日の 0時(LocalDateTime)に直してから渡す
            Duration duration = Duration.between(a.atStartOfDay(), b.atStartOfDay());
            System.out.println("| " + a + " → " + b
                    + " | " + ChronoUnit.DAYS.between(a, b)
                    + " | " + ChronoUnit.MONTHS.between(a, b)
                    + " | " + ChronoUnit.YEARS.between(a, b)
                    + " | " + period
                    + " | " + period.getDays()
                    + " | " + duration + " |");
        }
    }
}

実行結果(表の形で出力しています):

開始 → 終了 DAYS MONTHS YEARS Period getDays() Duration
2026-01-01 → 2026-01-31 30 0 0 P30D 30 PT720H
2026-01-31 → 2026-02-28 28 0 0 P28D 28 PT672H
2026-01-31 → 2026-03-01 29 1 0 P1M1D 1 PT696H
2026-01-15 → 2027-03-20 429 14 1 P1Y2M5D 5 PT10296H
2024-02-29 → 2025-02-28 365 11 0 P11M30D 30 PT8760H
2024-02-29 → 2028-02-29 1461 48 4 P4Y 0 PT35064H
2025-10-03 → 2026-10-03 365 12 1 P1Y 0 PT8760H
2026-10-03 → 2025-12-25 -282 -9 0 P-9M-9D -9 PT-6768H

この表から読み取れることは次のとおりです。

  • getDays() は総日数とは別の値です。 2026-01-15 → 2027-03-20 は総日数 429 に対して getDays() は 5、4年ちょうどの行は 1461 に対して 0 です。
  • 1/31 → 2/28 は MONTHS が 0。 終了日の「日」(28)が開始日の「日」(31)に届かないため、1か月と数えません。
  • 2024-02-29 → 2025-02-28 は YEARS が 0(Period は P11M30D)。総日数は 365 でも、満1年にはなりません。
  • Duration の1日は常に24時間として計算されます(PT720H は 30日 × 24時間)。

Period.getDays() は総日数ではない

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

public class PeriodDays {
    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2026, 1, 15);
        LocalDate end = LocalDate.of(2027, 3, 20);

        Period p = Period.between(start, end);
        System.out.println("Period            = " + p);
        System.out.println("getYears()        = " + p.getYears());
        System.out.println("getMonths()       = " + p.getMonths());
        System.out.println("getDays()         = " + p.getDays() + "  ← 「1年2か月と5日」の 5");
        System.out.println("toTotalMonths()   = " + p.toTotalMonths());
        System.out.println("総日数 DAYS       = " + ChronoUnit.DAYS.between(start, end));
        System.out.println("総月数 MONTHS     = " + ChronoUnit.MONTHS.between(start, end));
        System.out.println("start.until(end)  = " + start.until(end));
        System.out.println("until(end, DAYS)  = " + start.until(end, ChronoUnit.DAYS));
        System.out.printf("表示用             = %d年%dか月%d日%n", p.getYears(), p.getMonths(), p.getDays());

        // Period は足し戻せる:start + Period = end
        System.out.println("start.plus(p)     = " + start.plus(p));

        // ofDays で作った Period は月に繰り上がらない
        Period d45 = Period.ofDays(45);
        System.out.println("Period.ofDays(45) = " + d45 + " / normalized() = " + d45.normalized());
        Period m14 = Period.ofMonths(14);
        System.out.println("Period.ofMonths(14) = " + m14 + " / normalized() = " + m14.normalized());
    }
}

実行結果:

Period            = P1Y2M5D
getYears()        = 1
getMonths()       = 2
getDays()         = 5  ← 「1年2か月と5日」の 5
toTotalMonths()   = 14
総日数 DAYS       = 429
総月数 MONTHS     = 14
start.until(end)  = P1Y2M5D
until(end, DAYS)  = 429
表示用             = 1年2か月5日
start.plus(p)     = 2027-03-20
Period.ofDays(45) = P45D / normalized() = P45D
Period.ofMonths(14) = P14M / normalized() = P1Y2M

Period は「年・月・日」の3つの数を別々に持つ型です。公式Javadoc では、Period.between は「まず完全な月数を取り除き、残りの日数を計算し、月数を12か月=1年として年と月に分ける」と説明されています。

ほしい値 使うもの
総日数 ChronoUnit.DAYS.between(a, b)
総月数 ChronoUnit.MONTHS.between(a, b) または period.toTotalMonths()
「○年○か月○日」の各部分 getYears() getMonths() getDays()

normalized() は月を年へ繰り上げますが(P14M → P1Y2M)、日を月へは繰り上げません(P45D のまま)。1か月の日数が月によって違うためです。

月末・2月29日・切り上げ・逆順の動きを確認する

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

public class Edge {
    static int age(LocalDate birthday, LocalDate today) {
        return Period.between(birthday, today).getYears();
    }

    // 1日でも端数があれば 1か月と数える(月数の切り上げ)
    static long monthsCeil(LocalDate start, LocalDate end) {
        long months = ChronoUnit.MONTHS.between(start, end);
        return start.plusMonths(months).isBefore(end) ? months + 1 : months;
    }

    public static void main(String[] args) {
        System.out.println("[1] 1/31 → 2/28 は「0か月と28日」");
        LocalDate jan31 = LocalDate.of(2026, 1, 31);
        LocalDate feb28 = LocalDate.of(2026, 2, 28);
        System.out.println("MONTHS.between      = " + ChronoUnit.MONTHS.between(jan31, feb28));
        System.out.println("Period.between      = " + Period.between(jan31, feb28));
        System.out.println("1/31.plusMonths(1)  = " + jan31.plusMonths(1) + "  ← 加算は月末に丸められる");
        System.out.println("1/28 → 2/28         = " + Period.between(LocalDate.of(2026, 1, 28), feb28));
        System.out.println("2/28 → 3/31         = " + Period.between(feb28, LocalDate.of(2026, 3, 31)));

        System.out.println("[2] 2月29日生まれの年齢");
        LocalDate birthday = LocalDate.of(2000, 2, 29);
        for (String s : new String[] {"2026-02-27", "2026-02-28", "2026-03-01", "2028-02-28", "2028-02-29"}) {
            LocalDate today = LocalDate.parse(s);
            System.out.println(s + " 時点 : " + age(birthday, today) + "歳  (YEARS.between="
                    + ChronoUnit.YEARS.between(birthday, today) + ")");
        }

        System.out.println("[3] 月数の切り捨てと切り上げ");
        LocalDate start = LocalDate.of(2026, 1, 15);
        for (String s : new String[] {"2026-02-14", "2026-02-15", "2026-02-16", "2026-04-15"}) {
            LocalDate end = LocalDate.parse(s);
            System.out.println("1/15 → " + s + " : 切り捨て=" + ChronoUnit.MONTHS.between(start, end)
                    + " 切り上げ=" + monthsCeil(start, end));
        }

        System.out.println("[4] 逆順はマイナス");
        LocalDate a = LocalDate.of(2026, 10, 3);
        LocalDate b = LocalDate.of(2025, 12, 25);
        long days = ChronoUnit.DAYS.between(a, b);
        Period p = Period.between(a, b);
        System.out.println("DAYS.between        = " + days + " / Math.abs = " + Math.abs(days));
        System.out.println("Period.between      = " + p + " / isNegative = " + p.isNegative());
        System.out.println("Period.negated()    = " + p.negated());

        System.out.println("[5] 両端を含めて数える(1日〜31日は31日間)");
        LocalDate first = LocalDate.of(2026, 1, 1);
        LocalDate last = LocalDate.of(2026, 1, 31);
        System.out.println("between             = " + ChronoUnit.DAYS.between(first, last));
        System.out.println("between + 1         = " + (ChronoUnit.DAYS.between(first, last) + 1));
        System.out.println("終了日の翌日まで    = " + ChronoUnit.DAYS.between(first, last.plusDays(1)));

        System.out.println("[6] うるう年をまたぐ1年間の日数");
        System.out.println("2023-03-01 → 2024-03-01 = " + ChronoUnit.DAYS.between(LocalDate.of(2023, 3, 1), LocalDate.of(2024, 3, 1)));
        System.out.println("2025-03-01 → 2026-03-01 = " + ChronoUnit.DAYS.between(LocalDate.of(2025, 3, 1), LocalDate.of(2026, 3, 1)));
    }
}

実行結果:

[1] 1/31 → 2/28 は「0か月と28日」
MONTHS.between      = 0
Period.between      = P28D
1/31.plusMonths(1)  = 2026-02-28  ← 加算は月末に丸められる
1/28 → 2/28         = P1M
2/28 → 3/31         = P1M3D
[2] 2月29日生まれの年齢
2026-02-27 時点 : 25歳  (YEARS.between=25)
2026-02-28 時点 : 25歳  (YEARS.between=25)
2026-03-01 時点 : 26歳  (YEARS.between=26)
2028-02-28 時点 : 27歳  (YEARS.between=27)
2028-02-29 時点 : 28歳  (YEARS.between=28)
[3] 月数の切り捨てと切り上げ
1/15 → 2026-02-14 : 切り捨て=0 切り上げ=1
1/15 → 2026-02-15 : 切り捨て=1 切り上げ=1
1/15 → 2026-02-16 : 切り捨て=1 切り上げ=2
1/15 → 2026-04-15 : 切り捨て=3 切り上げ=3
[4] 逆順はマイナス
DAYS.between        = -282 / Math.abs = 282
Period.between      = P-9M-9D / isNegative = true
Period.negated()    = P9M9D
[5] 両端を含めて数える(1日〜31日は31日間)
between             = 30
between + 1         = 31
終了日の翌日まで    = 31
[6] うるう年をまたぐ1年間の日数
2023-03-01 → 2024-03-01 = 366
2025-03-01 → 2026-03-01 = 365

1/31 → 2/28 と月末の扱い

差分の計算は「終了日の日 ≧ 開始日の日」のときだけ1か月と数えます。 一方、加算の plusMonths(1) は 1/31 を 2/28 に丸めます。そのため「1/31 に1か月足すと 2/28」でも「1/31 から 2/28 までは0か月」となり、足し算と引き算が対称になりません。月末どうしを「1か月」と数えたい場合は、開始日が月末かどうかを start.lengthOfMonth() == start.getDayOfMonth() で判定し、独自の規則として実装します。

2月29日生まれの年齢

Period.between(誕生日, 今日).getYears() と ChronoUnit.YEARS.between は同じ値になります。2000-02-29 生まれの場合、うるう年ではない 2026年は 2/28 の時点で 25、3/1 に 26 になりました。 うるう年の 2028年は 2/29 に 28 になります。2/28 の時点で年齢を加算したい規則がある場合は、その規則をコードで明示します。

月数の切り上げ

MONTHS.between は切り捨てです。端数が1日でもあれば1か月と数える場合は、start.plusMonths(months) が終了日より前かどうかで 1 を足します(コード中の monthsCeil)。1/15 → 2/16 は切り捨てで 1、切り上げで 2 になりました。

逆順はマイナスになる

開始と終了を逆に渡すと、例外にはならず、マイナスの値が返ります。Period は年・月・日のすべてに同じ符号が付きます(P-9M-9D)。順序が決まっていない入力には Math.abs や period.negated()、Duration なら abs() を使います。

LocalDate に Duration を使うと UnsupportedTemporalTypeException

型の組み合わせで起きる例外と、時刻を含む型での数え方を確認します。

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.function.Supplier;

public class Errors {
    static void run(String label, Supplier<Object> s) {
        try {
            System.out.println(label + " → " + s.get());
        } catch (RuntimeException e) {
            System.out.println(label + " → " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        LocalDate d1 = LocalDate.of(2026, 10, 1);
        LocalDate d2 = LocalDate.of(2026, 10, 3);
        LocalDateTime t1 = LocalDateTime.of(2026, 10, 1, 23, 0);
        LocalDateTime t2 = LocalDateTime.of(2026, 10, 2, 1, 0);

        System.out.println("[1] LocalDate に Duration・時間の単位を使う");
        run("Duration.between(LocalDate, LocalDate)", () -> Duration.between(d1, d2));
        run("ChronoUnit.HOURS.between(LocalDate, LocalDate)", () -> ChronoUnit.HOURS.between(d1, d2));
        run("直し方: atStartOfDay() にしてから", () -> Duration.between(d1.atStartOfDay(), d2.atStartOfDay()));
        run("直し方: DAYS.between * 24", () -> ChronoUnit.DAYS.between(d1, d2) * 24);

        System.out.println("[2] 型が混ざったとき(終了側が開始側の型に変換される)");
        run("DAYS.between(LocalDate, LocalDateTime)", () -> ChronoUnit.DAYS.between(d1, t2));
        run("DAYS.between(LocalDateTime, LocalDate)", () -> ChronoUnit.DAYS.between(t1, d2));

        System.out.println("[3] LocalDateTime の DAYS は「24時間たったか」で数える");
        run("10/1 23:00 → 10/2 01:00 の DAYS", () -> ChronoUnit.DAYS.between(t1, t2));
        run("HOURS", () -> ChronoUnit.HOURS.between(t1, t2));
        run("日付で数える: toLocalDate() 同士", () -> ChronoUnit.DAYS.between(t1.toLocalDate(), t2.toLocalDate()));

        System.out.println("[4] Duration の取り出し");
        Duration d = Duration.between(LocalDateTime.of(2026, 10, 1, 9, 30), LocalDateTime.of(2026, 10, 3, 18, 45, 20));
        run("Duration", () -> d);
        run("toDays / toHours / toMinutes", () -> d.toDays() + " / " + d.toHours() + " / " + d.toMinutes());
        run("表示用", () -> String.format("%d日%d時間%d分%d秒", d.toDaysPart(), d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart()));
    }
}

実行結果:

[1] LocalDate に Duration・時間の単位を使う
Duration.between(LocalDate, LocalDate) → java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
ChronoUnit.HOURS.between(LocalDate, LocalDate) → java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Hours
直し方: atStartOfDay() にしてから → PT48H
直し方: DAYS.between * 24 → 48
[2] 型が混ざったとき(終了側が開始側の型に変換される)
DAYS.between(LocalDate, LocalDateTime) → 1
DAYS.between(LocalDateTime, LocalDate) → java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: 2026-10-03 of type java.time.LocalDate
[3] LocalDateTime の DAYS は「24時間たったか」で数える
10/1 23:00 → 10/2 01:00 の DAYS → 0
HOURS → 2
日付で数える: toLocalDate() 同士 → 1
[4] Duration の取り出し
Duration → PT57H15M20S
toDays / toHours / toMinutes → 2 / 57 / 3435
表示用 → 2日9時間15分20秒
起きること 理由 こうするとよい
Duration.between(LocalDate, LocalDate) が Unsupported unit: Seconds Duration は秒の単位を扱える型が必要で、LocalDate は時刻を持たない atStartOfDay() で LocalDateTime にする。日数だけなら ChronoUnit.DAYS.between
HOURS.between(LocalDate, LocalDate) が Unsupported unit: Hours 同上 DAYS.between(...) * 24、または時刻を持つ型で計算
DAYS.between(LocalDateTime, LocalDate) が DateTimeException 終了側を開始側の型(LocalDateTime)に変換できない 両方を同じ型にそろえる
23:00 → 翌 01:00 の DAYS が 0 単位が「完全に」経過した回数を数える(24時間たっていない) 日付の差なら toLocalDate() どうしで計算

Period.between は引数の型が LocalDate に決まっているため、LocalDateTime を渡すとコンパイルの段階で止まります。toLocalDate() で日付にしてから渡します。

Duration から日・時間・分を取り出すときは、toHours() は総時間(57)、toHoursPart() は日を除いた残りの時間(9) です。表示には toDaysPart() toHoursPart() toMinutesPart() toSecondsPart() を組み合わせます。

夏時間のあるゾーンでは LocalDateTime と ZonedDateTime で時間差が変わる

LocalDateTime はタイムゾーンを持たないため、カレンダーと時計の数字だけで差を計算します。ZonedDateTime は実際に経過した時間で計算します。夏時間の切り替えをまたぐ2つの日時で比べます。

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

public class Dst {
    static void show(String label, LocalDateTime from, LocalDateTime to, ZoneId zone) {
        ZonedDateTime zf = from.atZone(zone);
        ZonedDateTime zt = to.atZone(zone);
        System.out.println(label + "(" + zone + ")");
        System.out.println("  " + zf + " → " + zt);
        System.out.println("  LocalDateTime : HOURS=" + ChronoUnit.HOURS.between(from, to)
                + " Duration=" + Duration.between(from, to));
        System.out.println("  ZonedDateTime : HOURS=" + ChronoUnit.HOURS.between(zf, zt)
                + " Duration=" + Duration.between(zf, zt)
                + " DAYS=" + ChronoUnit.DAYS.between(zf, zt));
    }

    public static void main(String[] args) {
        ZoneId ny = ZoneId.of("America/New_York");
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        // 2026年の夏時間:3月8日に始まり、11月1日に終わる
        show("夏時間の開始をまたぐ", LocalDateTime.of(2026, 3, 7, 12, 0), LocalDateTime.of(2026, 3, 8, 12, 0), ny);
        show("夏時間の終了をまたぐ", LocalDateTime.of(2026, 10, 31, 12, 0), LocalDateTime.of(2026, 11, 1, 12, 0), ny);
        show("夏時間のないゾーン", LocalDateTime.of(2026, 3, 7, 12, 0), LocalDateTime.of(2026, 3, 8, 12, 0), tokyo);

        // 存在しない時刻(2:30)は、切り替え後の時刻にずれる
        System.out.println("3/8 02:30 を New_York で解釈 → " + LocalDateTime.of(2026, 3, 8, 2, 30).atZone(ny));

        // 異なるゾーンどうしの差は、同じ瞬間に直して計算される
        ZonedDateTime a = ZonedDateTime.of(2026, 10, 3, 9, 0, 0, 0, tokyo);
        ZonedDateTime b = ZonedDateTime.of(2026, 10, 3, 9, 0, 0, 0, ny);
        System.out.println("東京 9:00 → New_York 9:00 = " + Duration.between(a, b));
    }
}

実行結果:

夏時間の開始をまたぐ(America/New_York)
  2026-03-07T12:00-05:00[America/New_York] → 2026-03-08T12:00-04:00[America/New_York]
  LocalDateTime : HOURS=24 Duration=PT24H
  ZonedDateTime : HOURS=23 Duration=PT23H DAYS=1
夏時間の終了をまたぐ(America/New_York)
  2026-10-31T12:00-04:00[America/New_York] → 2026-11-01T12:00-05:00[America/New_York]
  LocalDateTime : HOURS=24 Duration=PT24H
  ZonedDateTime : HOURS=25 Duration=PT25H DAYS=1
夏時間のないゾーン(Asia/Tokyo)
  2026-03-07T12:00+09:00[Asia/Tokyo] → 2026-03-08T12:00+09:00[Asia/Tokyo]
  LocalDateTime : HOURS=24 Duration=PT24H
  ZonedDateTime : HOURS=24 Duration=PT24H DAYS=1
3/8 02:30 を New_York で解釈 → 2026-03-08T03:30-04:00[America/New_York]
東京 9:00 → New_York 9:00 = PT13H
区間(New_York) LocalDateTime ZonedDateTime DAYS(ZonedDateTime)
3/7 12:00 → 3/8 12:00(夏時間の開始) 24時間 23時間 1
10/31 12:00 → 11/1 12:00(夏時間の終了) 24時間 25時間 1

実際に経過した時間が必要なら ZonedDateTime か Instant、カレンダー上の日数が必要なら DAYS や LocalDate を使います。ZonedDateTime の DAYS.between は、23時間や25時間の日でも 1 と数えました。日本時間(Asia/Tokyo)では、今回の区間でどちらの型でも 24時間です。サーバーのタイムゾーンが UTC、利用者が別の地域、という構成では、どのゾーンの「日付」で差を取るのかを先に決めて、atZone(zone).toLocalDate() で日付にしてから計算すると結果が安定します。

営業日数(土日を除く日数)を数える

標準ライブラリには営業日を数えるメソッドが無いため、自分で書きます。3つの方法と、「n 営業日後」を求めるコードです。

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Set;

public class BusinessDays {
    static boolean isWeekend(LocalDate d) {
        DayOfWeek w = d.getDayOfWeek();
        return w == DayOfWeek.SATURDAY || w == DayOfWeek.SUNDAY;
    }

    // 方法1:1日ずつ数える(start を含み、end を含まない)
    static long countLoop(LocalDate start, LocalDate end) {
        long count = 0;
        for (LocalDate d = start; d.isBefore(end); d = d.plusDays(1)) {
            if (!isWeekend(d)) count++;
        }
        return count;
    }

    // 方法2:datesUntil(Java 9 以降)で Stream にする。休みの日の一覧も除外できる
    static long countStream(LocalDate start, LocalDate end, Set<LocalDate> holidays) {
        return start.datesUntil(end)
                .filter(d -> !isWeekend(d))
                .filter(d -> !holidays.contains(d))
                .count();
    }

    // 方法3:週単位で計算する(期間が長くても一定の手数)
    static long countFormula(LocalDate start, LocalDate end) {
        long days = ChronoUnit.DAYS.between(start, end);
        long weeks = days / 7;
        long count = weeks * 5;
        LocalDate d = start.plusWeeks(weeks);
        while (d.isBefore(end)) {
            if (!isWeekend(d)) count++;
            d = d.plusDays(1);
        }
        return count;
    }

    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2026, 10, 1);   // 木曜
        LocalDate end = LocalDate.of(2026, 11, 1);     // 10月の1か月分(11/1 は含まない)
        Set<LocalDate> holidays = Set.of(LocalDate.of(2026, 10, 12), LocalDate.of(2026, 10, 30));

        System.out.println("全日数               = " + ChronoUnit.DAYS.between(start, end));
        System.out.println("土日を除く(ループ) = " + countLoop(start, end));
        System.out.println("土日を除く(Stream) = " + countStream(start, end, Set.of()));
        System.out.println("土日を除く(週単位) = " + countFormula(start, end));
        System.out.println("休みの日も除く       = " + countStream(start, end, holidays));

        LocalDate longEnd = LocalDate.of(2036, 10, 1);
        System.out.println("10年分 ループ=" + countLoop(start, longEnd) + " 週単位=" + countFormula(start, longEnd));

        // 3営業日後の日付を求める
        LocalDate d = LocalDate.of(2026, 10, 2);       // 金曜
        int remaining = 3;
        while (remaining > 0) {
            d = d.plusDays(1);
            if (!isWeekend(d)) remaining--;
        }
        System.out.println("10/2(金) の3営業日後  = " + d + " (" + d.getDayOfWeek() + ")");
    }
}

実行結果:

全日数               = 31
土日を除く(ループ) = 22
土日を除く(Stream) = 22
土日を除く(週単位) = 22
休みの日も除く       = 20
10年分 ループ=2609 週単位=2609
10/2(金) の3営業日後  = 2026-10-07 (WEDNESDAY)
方法 特徴 向いている場面
ループ 読みやすい。どの Java バージョンでも書ける 数か月〜数年の範囲
datesUntil と Stream 休みの日の一覧(Set)を filter で足しやすい 祝日や独自の休みを除きたいとき
週単位の計算 期間の長さに関係なく、ループは最大6回 大量の行で繰り返し計算するとき

祝日は年によって変わるため、Set などで外から渡す形にしておくと、データの差し替えだけで対応できます。3つの方法が同じ結果(22日、10年分で 2609日)になることも出力で確認できます。

どれを使うかの判断表

状況 選ぶもの
「あと何日」「何日経過」 ChronoUnit.DAYS.between(LocalDate)
年齢、勤続年数 ChronoUnit.YEARS.between または Period.between(...).getYears()
「○年○か月○日」の表示 Period.between
契約月数など、端数を1か月と数える MONTHS.between + 切り上げの判定
処理時間、滞在時間 Duration.between(Instant や LocalDateTime)
地域をまたぐ・夏時間がある ZonedDateTime または Instant で Duration.between
平日だけ数える datesUntil と filter、または週単位の計算

よくある質問

Q. ChronoUnit.DAYS.between と Period.between(...).getDays() は同じですか? A. 別の値です。前者は総日数、後者は年と月を取り除いた残りの日数です。2026-01-15 → 2027-03-20 では 429 と 5 になりました。1か月未満の区間ではたまたま同じ値になるため、テストでは月をまたぐ日付で確認します。

Q. start.until(end) と between の違いは? A. 同じ計算です。start.until(end) は Period.between(start, end)、start.until(end, ChronoUnit.DAYS) は ChronoUnit.DAYS.between(start, end) と同じ結果になります(公式Javadoc にも等価と書かれています)。

Q. 終了日も含めて数えたいときは? A. 結果に 1 を足すか、終了日に plusDays(1) してから渡します。1/1〜1/31 は between では 30、両端を含めると 31 です。

Q. LocalDateTime どうしで日数を数えると 1日少なくなります。 A. DAYS.between は24時間が完全に経過した回数を数えるためです。10/1 23:00 → 10/2 01:00 は 0 になります。カレンダー上の日付の差がほしいときは、toLocalDate() で日付にしてから計算します。

まとめ

  • 総日数・総月数・満年数は ChronoUnit.XXX.between、年月日の組は Period.between、時間の量は Duration.between
  • Period.getDays() は総日数ではなく、年と月を除いた残りの日数
  • 月は「終了日の日 ≧ 開始日の日」で1か月。1/31 → 2/28 は 0か月28日
  • 2/29 生まれは、うるう年でない年は 3/1 に年齢が増える(Period・YEARS の計算結果)
  • 逆順はマイナス。例外にはならないので、必要なら Math.abs や negated()
  • LocalDate に Duration や HOURS を使うと UnsupportedTemporalTypeException。atStartOfDay() で時刻を付ける
  • 夏時間のあるゾーンでは、ZonedDateTime の時間差が 23時間/25時間になる
  • 土日を除く日数は datesUntil と filter で書け、休みの日の Set も除外できる

参考・出典

  • Java SE 21 API ドキュメント「ChronoUnit」(between の数え方、until との等価) https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/temporal/ChronoUnit.html
  • Java SE 21 API ドキュメント「Period」(between の計算方法) https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Period.html
  • Java SE 21 API ドキュメント「Duration」(between が必要とする単位、1日=24時間) https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Duration.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java 日付の差分計算|ChronoUnit・Period・Duration」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?