Javaで日付操作を行う際、最も頻繁に発生する処理の一つが「月末日の取得」です。

特に給与計算や在庫管理、請求処理といったビジネスシステムにおいて、特定の月が28日、30日、あるいは31日のいずれで終わるのかを正確に判定することは、システムの信頼性を担保するために非常に重要です。

Javaには歴史的な経緯から複数の日付操作ライブラリが存在しますが、現在はJava 8で導入された Date-Time API(java.timeパッケージ)を使用するのが標準 となっています。

本記事では、モダンな LocalDate を使った方法から、古いシステムで使われている Calendar クラスの扱いまで、実務で役立つ具体的なコード例を交えて詳しく解説します。

Java 8以降の推奨:LocalDateを使用する方法

現代のJava開発において、日付のみを扱う場合に最も適しているのが java.time.LocalDate クラスです。

このクラスは不変(イミュータブル)であり、スレッドセーフであるため、従来の DateCalendar に比べて格段に安全に扱うことができます。

月末日を取得するための最もスマートな方法は、 TemporalAdjustersクラスのlastDayOfMonthメソッド を使用することです。

このメソッドを利用することで、複雑な判定ロジックを自分で書く必要がなくなります。

LocalDateとTemporalAdjustersの組み合わせ

以下のプログラムは、現在の日付からその月の最終日を取得する例です。

Java
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class LastDayExample {
    public static void main(String[] args) {
        // 現在の日付を取得
        LocalDate now = LocalDate.now();
        
        // その月の最終日を取得
        LocalDate lastDay = now.with(TemporalAdjusters.lastDayOfMonth());
        
        System.out.println("今月の今日: " + now);
        System.out.println("今月の最終日: " + lastDay);
        
        // 最終日の「日」の数値だけを取得したい場合
        int dayOfMonth = lastDay.getDayOfMonth();
        System.out.println("最終日の数値: " + dayOfMonth);
    }
}
実行結果
今月の今日: 2026-03-05
今月の最終日: 2026-03-31
最終日の数値: 31

このコードのポイントは、 now.with(TemporalAdjusters.lastDayOfMonth()) という記述です。

with メソッドは、指定された調整器(Adjuster)を適用して新しい LocalDate インスタンスを生成します。

これにより、対象の月が何日であるかを気にすることなく、確実にその月の最後の日を指すオブジェクトが得られます。

特定の年月の最終日を取得する

現在の月だけでなく、過去や未来の特定の年月の最終日を知りたい場合もあります。

その場合は、 LocalDate.of() で任意の日付を作成してから同様の処理を行うか、 YearMonthクラス を使用するのが非常に効率的です。

Java
import java.time.YearMonth;
import java.time.LocalDate;

public class SpecificMonthExample {
    public static void main(String[] args) {
        // 2024年2月(うるう年)を指定
        YearMonth ym = YearMonth.of(2024, 2);
        
        // その月の最終日をLocalDateとして取得
        LocalDate lastDay = ym.atEndOfMonth();
        
        // その月の末日の「数値」を取得
        int lengthOfMonth = ym.lengthOfMonth();
        
        System.out.println("指定年月: " + ym);
        System.out.println("最終日: " + lastDay);
        System.out.println("月の日数: " + lengthOfMonth);
    }
}
実行結果
指定年月: 2024-02
最終日: 2024-02-29
月の日数: 29

YearMonth クラスは「年」と「月」の情報に特化したクラスであり、 atEndOfMonth() メソッドを呼ぶだけで、 うるう年の判定も自動的に行い 適切な最終日を返してくれます。

ロジックがシンプルになるため、月単位の集計処理などには最適な選択肢です。

レガシー環境:Calendarクラスを使用する方法

Java 7以前の環境や、既存の古いコードベースを保守する場合、 java.util.Calendar を使用しなければならない場面があります。

Calendar クラスで月の最終日を取得するには、 getActualMaximumメソッド を使用します。

Calendarによる実装例

Java
import java.util.Calendar;

public class CalendarExample {
    public static void main(String[] args) {
        // カレンダーインスタンスを取得
        Calendar cal = Calendar.getInstance();
        
        // 現在の月の最大日数を取得
        int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        
        System.out.println("現在の月: " + (cal.get(Calendar.MONTH) + 1) + "月");
        System.out.println("今月の最終日(数値): " + lastDay);
        
        // 特定の月(例: 2023年6月)の最終日を取得したい場合
        cal.set(2023, Calendar.JUNE, 1);
        int specificLastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        System.out.println("2023年6月の最終日: " + specificLastDay);
    }
}
実行結果
現在の月: 3月
今月の最終日(数値): 31
2023年6月の最終日: 30

Calendar クラスを使用する際の注意点は、 月の指定が0始まり(1月が0、12月が11)であること です。

この仕様は非常にミスを誘発しやすいため、新しいプロジェクトで Calendar を採用する理由はまずありません。

また、 Calendar は可変(ミュータブル)なオブジェクトであるため、複数の場所で同じインスタンスを使い回すと予期せぬ挙動を引き起こすリスクがあります。

LocalDateとCalendarの比較

月末日取得における両者の違いを表にまとめました。

特徴LocalDate (Date-Time API)Calendar (Legacy API)
導入バージョンJava 8以降JDK 1.1から存在
スレッド安全性安全(不変オブジェクト)非推奨(可変オブジェクト)
月の指定方法1月 = 1, 12月 = 12 (直感的)1月 = 0, 12月 = 11 (不直感的)
うるう年対応自動判定自動判定
主なメソッドTemporalAdjusters.lastDayOfMonth()getActualMaximum(Calendar.DAY_OF_MONTH)

これから新しくコードを書くのであれば、 間違いなくLocalDateを使用すべき です。

既存の CalendarDate を利用しているコードから LocalDate に変換する場合は、以下のようにブリッジ処理を行います。

Java
// DateからLocalDateへの変換
Date date = new Date();
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// 月末日取得
LocalDate lastDay = localDate.with(TemporalAdjusters.lastDayOfMonth());

月末日取得の応用:翌月の月初日を取得する

実務では「月末日の翌日(つまり翌月の1日)」を取得したいケースも多いでしょう。

Date-Time APIを使えば、計算も非常に容易です。

Java
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class NextMonthFirstDay {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        
        // 方法1:月末日を取得してから1日足す
        LocalDate nextMonth1 = today.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1);
        
        // 方法2:最初から「翌月の1日」を指定する
        LocalDate nextMonth2 = today.with(TemporalAdjusters.firstDayOfNextMonth());
        
        System.out.println("翌月の月初: " + nextMonth2);
    }
}

TemporalAdjusters.firstDayOfNextMonth() を使用すると、直接翌月の1日を取得できます。

このように、Date-Time APIは「やりたいこと」をメソッド名そのままの形で記述できるため、可読性の高いコードを実現できます。

月末日処理で注意すべきポイント

日付処理を実装する際には、いくつか考慮すべき落とし穴があります。

タイムゾーンの考慮

LocalDate.now() は、実行環境のデフォルトタイムゾーンを使用します。

グローバルに展開されるサーバーで実行する場合、サーバーの設定時間によっては日付が1日ずれる可能性があります。

特定のタイムゾーンの月末を知りたい場合は、 LocalDate.now(ZoneId.of("Asia/Tokyo")) のように明示的に指定することを検討してください。

営業日の考慮

単なる「カレンダー上の月末日」ではなく、「月末の最終営業日」を取得したい場合は、標準APIだけでは不十分です。

その場合は、月末日からループを回して getDayOfWeek() メソッドで土日かどうかを判定し、必要に応じて1日ずつ遡るロジックを実装する必要があります。

うるう年の存在

自前で if 文を書いて「2月なら28日、ただし4で割り切れる年は……」といったロジックを組むのは絶対に避けてください。

今回紹介した lastDayOfMonth()atEndOfMonth() は、 100年単位や400年単位の複雑なうるう年規則 も全て考慮済みです。

まとめ

Javaで月の最終日を取得する方法について解説しました。

内容を整理すると、以下の3点が重要なポイントとなります。

Java 8以降での月末取得

LocalDateTemporalAdjusters.lastDayOfMonth() を使用するのがベストプラクティスです。

年と月のみの場合

YearMonth.atEndOfMonth() を使用することで、より簡潔にコードを記述できます。

Java 7以前のレガシーシステム

Calendar.getActualMaximum() を使用しますが、月のインデックス(0~11)の扱いに十分注意する必要があります。

モダンな Date-Time APIを活用することで、日付計算に伴うバグを劇的に減らすことができます。

ぜひ本記事で紹介した手法を、日々の開発業務に活用してみてください。