AIによる要約
LocalDateは不変オブジェクトです。plusDaysは元のdateを書き換えず、計算後の新しいLocalDateを返します。そのためdate.plusDays(1)だけではdateは変わりません。date = date.plusDays(1)のように戻り値を受け取ります。月末やうるう年はAPIが処理しますが、営業日、締日、タイムゾーンは別の業務ルールなので、単純な日数加算と混同しないことが重要です。
この記事はXへ投稿したJavaクイズの詳しい解説です。まず問題を解きたい方は、Xの元ポストを開くことができます。


日付を1日進めるコードは、バッチ対象日、請求期限、検索範囲などで頻繁に使います。plusDaysという名前から、変数の中身がその場で更新されるように感じますが、LocalDateは不変です。
戻り値を受け取らないコードはコンパイルも通り、例外も出ません。そのため障害時に気付きにくく、ループなら同じ日付を処理し続ける可能性もあります。日付APIの契約と業務日付のルールを分けて確認します。
この記事のポイント
- LocalDateは不変オブジェクト
- plusDaysは新しいLocalDateを返す
- 戻り値を代入しないと元の日付は変わらない
- 月末・うるう年はAPIが処理する
- 営業日や締日は別の業務ルールとして実装する
まずはXのJavaクイズを確認する

LocalDate date = LocalDate.of(2026, 8, 13);
date.plusDays(1);
System.out.println(date);答えは2026-08-13です。plusDaysは翌日のLocalDateを返しますが、元のdateは変更しません。

LocalDateは不変オブジェクト
不変オブジェクトは、作成後に内部の値が変わりません。LocalDateの年・月・日はsetterで変更できず、加算や変更メソッドは新しいインスタンスを返します。
LocalDate original = LocalDate.of(2026, 8, 13);
LocalDate next = original.plusDays(1);
System.out.println(original); // 2026-08-13
System.out.println(next); // 2026-08-14文字列のreplaceやtoUpperCaseでも同じ考え方があります。戻り値があるメソッドを呼んだら、その値を使っているか確認する習慣を付けます。
正しくは戻り値を代入する
同じ変数名で次の日付を使うなら、戻り値を再代入します。元の日付も必要なら別変数へ保持します。
date = date.plusDays(1);
// または意図が分かる別名にする
LocalDate nextDate = date.plusDays(1);メソッドチェーンもすべて新しい値を返します。途中の結果を読みやすくする必要があれば、1行へ詰め込まず変数を分けます。
ループで戻り値を捨てると処理が進まない
日付範囲を1日ずつ処理するループで代入を忘れると、currentが変わらず無限ループになります。テストデータが0件なら処理本体の副作用だけが繰り返される可能性もあります。
for (LocalDate current = from;
!current.isAfter(to);
current = current.plusDays(1)) {
dailyService.execute(current);
}for文の更新式へ書けば、日付を進める責務が明確です。whileを使う場合も、例外やcontinueで更新処理を飛ばさない構造にします。
月末とうるう年はAPIに任せる
LocalDateはカレンダーを理解しているため、月末やうるう年を手計算する必要はありません。
System.out.println(LocalDate.of(2026, 1, 31).plusDays(1));
// 2026-02-01
System.out.println(LocalDate.of(2024, 2, 28).plusDays(1));
// 2024-02-29ただしplusMonths(1)の結果は『同じ日番号が存在しなければ月末へ調整』されます。1か月後と翌月末は業務上同じとは限らないため、期待例を仕様へ書きます。
営業日計算はplusDaysだけでは足りない
土日、祝日、会社休日、銀行営業日を飛ばす締切計算は、単純なplusDaysでは表せません。休日カレンダーを参照する業務サービスとして分けます。
LocalDate nextBusinessDay(LocalDate date) {
LocalDate candidate = date.plusDays(1);
while (holidayCalendar.isHoliday(candidate)) {
candidate = candidate.plusDays(1);
}
return candidate;
}祝日マスタの更新時期、地域差、臨時休日などもあります。日付APIの正しさと休日データの正しさは別に監視します。
LocalDateと日時・タイムゾーンを混同しない
LocalDateには時刻とタイムゾーンがありません。利用者の現地日付、サーバー日付、UTC基準の締切を扱う場合、LocalDateだけでは情報が足りないことがあります。
LocalDate todayInTokyo = LocalDate.now(ZoneId.of("Asia/Tokyo"));
LocalDate todayInUtc = LocalDate.now(ZoneOffset.UTC);テストでLocalDate.now()へ直接依存すると日付境界で不安定になります。現在日を使うServiceではClockを注入するなど、プロジェクトの方針に合わせて固定可能にします。
JUnitで月末と境界日を確認する
通常日だけでなく、月末、年末、うるう日、範囲の開始日と終了日をテストします。営業日なら祝日前後も必要です。
@ParameterizedTest
@CsvSource({
"2026-01-31, 2026-02-01",
"2024-02-28, 2024-02-29",
"2026-12-31, 2027-01-01"
})
void 翌日を計算する(LocalDate source, LocalDate expected) {
assertEquals(expected, source.plusDays(1));
}APIそのものではなく、請求期限や処理対象日の自作ロジックをこの境界値で検証します。テスト名と期待値に業務用語を入れると、仕様変更時に直す場所が分かります。
現場レビューでよくある指摘
日付処理のレビューでは、戻り値の代入だけでなく、暦日、営業日、締日、タイムゾーンのどれを計算しているかを確認します。
// レビューコメント例
plusDaysは元のLocalDateを変更しません。
戻り値をdateへ代入するか、nextDateとして利用してください。
// レビューコメント例
このwhileではcurrentが更新されないため処理が終わりません。
ループ更新式でcurrent = current.plusDays(1)としてください。
// レビューコメント例
仕様は翌営業日ですが、plusDays(1)では休日を考慮できません。
休日カレンダーを使う業務メソッドへ分けてください。日付が1日ずれた障害は、データ修正や再請求につながります。手元の今日だけで確認せず、月末・年末・休日・タイムゾーンを含む具体例で説明します。
提出前のセルフチェック
レビュー前に確認すること
- plusDaysの戻り値を使っているか
- 元の日付を残す必要があるか
- ループで日付が必ず進むか
- 月末・年末・うるう年をテストしたか
- 暦日と営業日を区別したか
- 締日や月末調整の仕様を確認したか
- タイムゾーンが必要な日時ではないか
- 現在日に依存するテストを固定できるか
Javaの日付APIを基本から学ぶ参考書
LocalDate、LocalDateTime、ZoneIdの役割を体系的に整理しておくと、単純な日数加算と業務日付計算を混同しにくくなります。
スッキリわかるJava入門 実践編 第5版
基礎文法の次に必要な、現場寄りのJava知識を補う。
コレクション、ジェネリクス、ラムダ式、ストリームなど、業務コードで出会いやすい機能を入門編の次に学べます。
- Java基礎の次に何を学ぶか迷っている
- コレクションやStreamを整理したい
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
まとめ
LocalDateは不変オブジェクトなので、plusDaysは元の日付を書き換えず、新しいLocalDateを返します。日付を進めるなら戻り値を変数へ代入してください。
月末とうるう年はAPIへ任せられますが、営業日、締日、タイムゾーンは業務ルールです。ループの更新漏れと境界日をテストし、何の日付を計算しているか明確にします。
