日付計算が「思ったより難しい」理由
日付計算は、一見「足し算・引き算だけ」に見えますが、実際には
月末の長さの違い、うるう年、タイムゾーン、夏時間(DST)、日付の境界の扱い
など、落とし穴だらけです。
java.time はこれらをかなりうまく扱ってくれますが、「どのクラスで」「どのメソッドを使って」計算するかを間違えると、平気でバグります。
ここでは、初心者が特につまずきやすいポイントを、例を交えながら整理していきます。
LocalDate と LocalDateTime と ZonedDateTime を混ぜない
日付だけを扱うなら LocalDate を使う
「締切日」「誕生日」「営業日」のように、「日付だけ」が意味を持つ場合は LocalDate を使います。
LocalDate today = LocalDate.of(2025, 1, 31);
LocalDate nextDay = today.plusDays(1);
System.out.println(today); // 2025-01-31
System.out.println(nextDay); // 2025-02-01
Javaここでは時刻もタイムゾーンも関係ありません。
「日付の足し算・引き算」だけをしたいなら、LocalDate に閉じ込めてしまうのが一番安全です。
時刻まで含めるときは「どの時間帯か」を意識する
「2025-03-01 10:00 から 3 時間後」といった計算をしたいとき、LocalDateTime を使うとこう書けます。
LocalDateTime start = LocalDateTime.of(2025, 3, 1, 10, 0);
LocalDateTime threeHoursLater = start.plusHours(3);
Javaただし、ここにはタイムゾーンがありません。
「日本時間で 3 時間後」と「ニューヨーク時間で 3 時間後」は、夏時間の有無などで意味が変わることがあります。
「現実世界の“瞬間”として正しく扱いたい」なら、ZonedDateTime を使って、必ず ZoneId を指定します。
ZonedDateTime startTokyo =
ZonedDateTime.of(2025, 3, 1, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
ZonedDateTime threeHoursLaterTokyo = startTokyo.plusHours(3);
Java日付計算のバグの多くは、「タイムゾーンを意識すべき場面で LocalDateTime を使ってしまう」ことから始まります。
月末・うるう年の計算に気をつける
plusMonths は「日付を丸める」ことがある
plusMonths は、存在しない日付に当たったとき、自動的に丸めます。
LocalDate d1 = LocalDate.of(2025, 1, 31);
LocalDate d2 = d1.plusMonths(1);
System.out.println(d1); // 2025-01-31
System.out.println(d2); // 2025-02-28
Java「1 月 31 日の 1 か月後」は「2 月 31 日」ですが、そんな日は存在しないので「2 月の最終日」に丸められます。
これは仕様として正しい動きですが、「常に 30 日後になる」と思っているとハマります。
「カレンダー上の“1 か月後”」を扱いたいなら plusMonths で OK。
「きっちり 30 日後」を扱いたいなら plusDays(30) を使うべきです。
うるう年も同じ罠を持っている
LocalDate d1 = LocalDate.of(2020, 2, 29); // うるう年
LocalDate d2 = d1.plusYears(1);
System.out.println(d1); // 2020-02-29
System.out.println(d2); // 2021-02-28
Java「1 年後」は「2 月 29 日」ではなく「2 月 28 日」になります。
これも「カレンダー上の 1 年後」としては自然ですが、「365 日後」とは違います。
「カレンダーの年・月・日としての差」を扱うなら Period(ofYears, ofMonths, ofDays)、
「純粋な日数・秒数としての差」を扱うなら Duration(ofDays, ofHours, ofSeconds)
という使い分けを意識すると、設計がぶれにくくなります。
夏時間(DST)と時間計算の落とし穴
「1 日後」と「24 時間後」は違うことがある
夏時間のある地域では、「1 日後」と「24 時間後」がズレることがあります。
例えば、ある地域で夏時間開始の日に、時計が 1 時間進むとします。
ZoneId zone = ZoneId.of("America/New_York");
// 夏時間開始前日の 1:30
ZonedDateTime before =
ZonedDateTime.of(2025, 3, 9, 1, 30, 0, 0, zone);
// 1 日後
ZonedDateTime plusOneDay = before.plusDays(1);
// 24 時間後
ZonedDateTime plus24Hours = before.plusHours(24);
JavaplusDays(1) は「カレンダー上で 1 日進める」ので、
日付と時刻の“見た目”を優先します。
plusHours(24) は「24 時間という Duration を足す」ので、
夏時間で 1 時間飛んだ分だけ、ローカル時刻がずれることがあります。
「カレンダー上で“翌日”を指したい」のか、
「きっちり 24 時間後の瞬間を指したい」のか、
目的によって plusDays と plusHours を使い分ける必要があります。
LocalDate で「日付だけ」に閉じ込めると安全
夏時間の影響を受けたくないなら、そもそも LocalDate で扱うのが一番安全です。
LocalDate today = LocalDate.now(zone);
LocalDate tomorrow = today.plusDays(1);
Java「日付だけ」を扱う設計にしておけば、夏時間による 1 時間のズレを気にしなくて済みます。
「日付と時刻を一緒に扱う必要があるか?」を最初に自分に問いかけると、バグの芽をかなり摘めます。
期間の計算:Period と Duration の違い
Period は「カレンダー的な期間」
Period は「年・月・日」の単位で期間を表します。
LocalDate start = LocalDate.of(2025, 1, 31);
Period oneMonth = Period.ofMonths(1);
LocalDate end = start.plus(oneMonth);
System.out.println(end); // 2025-02-28
Java「1 か月後」「3 年後」「2 か月 10 日後」といった、カレンダー感覚の期間を扱うのに向いています。
Duration は「時間的な長さ」
Duration は「秒・ナノ秒」の単位で期間を表します。
Instant start = Instant.now();
Duration tenSeconds = Duration.ofSeconds(10);
Instant end = start.plus(tenSeconds);
Java「10 秒後」「3 時間後」「24 時間後」といった、純粋な時間の長さを扱うのに向いています。
日付計算でよくあるバグは、
「カレンダー的な期間を扱いたいのに Duration を使ってしまう」
「純粋な時間の長さを扱いたいのに Period を使ってしまう」
という“単位の取り違え”です。
「何日間か?」の数え方(含む/含まない)
between の結果は「境界の扱い」に注意
ChronoUnit.DAYS.between などで日数差を取るとき、
「開始日を含むのか」「終了日を含むのか」を意識しないとズレます。
LocalDate d1 = LocalDate.of(2025, 1, 1);
LocalDate d2 = LocalDate.of(2025, 1, 10);
long days = ChronoUnit.DAYS.between(d1, d2);
System.out.println(days); // 9
Javaこれは「1 日から 10 日までの“差”」なので 9 日です。
しかし、「1 日から 10 日までの“日数”」を人間感覚で数えると 10 日ですよね。
「差」として扱うのか、「期間(日数)」として扱うのかで、+1 するかどうかが変わります。
仕様として「どちらを求めたいのか」を、コードを書く前に言葉でハッキリさせておくことが大事です。
まとめ:日付計算で自分が意識すべきチェックポイント
最後に、あなたが日付計算を書くときに、自分に問いかけてほしいポイントをまとめます。
「これは“日付だけ”の話か? それとも“時刻”も関係するか?」
→ 日付だけなら LocalDate に閉じ込める。
「タイムゾーンは関係するか? どこの時間として扱うべきか?」
→ 関係するなら ZonedDateTime + ZoneId を必ず使う。
「“1 か月後”“1 年後”は、カレンダー上の話か? それとも日数・時間の話か?」
→ カレンダーなら Period / plusMonths / plusYears、時間なら Duration / plusDays / plusHours。
「“翌日”を指したいのか? “24 時間後”を指したいのか?」
→ 夏時間の有無で結果が変わるので、どちらを求めているかを明確にする。
「日数差は“差”が欲しいのか? “日数”が欲しいのか?」
→ between の結果に +1 するかどうかを仕様として決める。
このあたりを意識できるようになると、日付計算のバグはかなり減ります。

