本ページはプロモーションが含まれています

Java入門

【2026年版】Javaでうるう年判定する4つの方法|Year.isLeap()推奨・if文/Calendar比較

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

「Javaでうるう年を判定したいけれど、4年ごと・100年ごと・400年ごとのルールをどう実装すれば安全か迷っていませんか?」

本記事では if文での自作ロジック/べた書き/java.time.Year.isLeap()GregorianCalendar.isLeapYear() の4通りを比較し、結論としてJava 8以降は Year.isLeap()(または LocalDate#isLeapYear())を使うのが最も安全かつ簡潔である理由を、コード・ベンチマーク・テストケース付きで解説します。

読み終えると、以下ができるようになります。

  • うるう年ルール(4/100/400の例外)を正しく実装できる
  • Java 8以降で推奨される Year.isLeap()LocalDate#isLeapYear() を使い分けられる
  • 自作ロジックのテストケース(1900年・2000年・2100年など境界値)を設計できる

前提はJava 21(LTS)。Java 7以前の旧環境向け対応もカバーします。

うるう年とは?|4年・100年・400年の3ルール

Javaでのうるう年判定方法に触れる前に、まずは「うるう年」そのものについておさらいしましょう。

正確な定義を知ることは、正しいプログラムを書くための第一歩です。

うるう年とは、暦と地球の公転周期のずれを調整するために設けられた年のことです。

地球が太陽の周りを1周するのにかかる時間は正確には365日ではなく、およそ365.2422日です。

この端数を調整しないと、暦と季節が少しずつずれていってしまいます。

このずれを補正するため、うるう年には2月が29日までとなり、1年が366日になります。うるう年のルールは以下の通りです。

ポイント

  1. 西暦年が4で割り切れる年はうるう年である。
  2. ただし、西暦年が100で割り切れる年は平年(うるう年ではない)とする。
  3. しかし、西暦年が400で割り切れる年はうるう年とする。

例えば、2024年(前回のうるう年)は4で割り切れるため、うるう年です。次回は2028年になります。

1900年は4で割り切れ、かつ100でも割り切れるため平年となります。

一方、2000年は4で割り切れ、100でも割り切れ、さらに400でも割り切れるため、うるう年になります。

このルールを正確に把握することが、Javaでうるう年を判定する上で重要です。

Javaでうるう年を判定する方法

うるう年のルールを理解したところで、次にJavaでこれらのルールに基づいてうるう年を判定する基本的な方法を見ていきましょう。

まずは、Javaの標準APIを使わずに、条件分岐だけで判定ロジックを実装する方法です。

if文だけで実装する方法(西暦を整数で判定)

うるう年の判定ロジックは、前述の3つのルールをif文で表現することで実装できます。

具体的には、与えられた西暦(整数)に対して、以下の条件を順に評価していきます。

public class LeapYearChecker {

    public boolean isLeapYearByIf(int year) {
        // 400で割り切れる年は必ずうるう年
        if (year % 400 == 0) {
            return true; // 400で割り切れる年はうるう年
        }
        // 400で割り切れないが100で割り切れる年は平年です。
        if (year % 100 == 0) {
            return false; // 100で割り切れる年は平年
        }
        // 100で割り切れず、4で割り切れる年はうるう年です。
        if (year % 4 == 0) {
            return true; // 4で割り切れる年はうるう年
        }
        // 上記のいずれにも当てはまらない場合は平年です。
        return false; // それ以外は平年
    }

    public static void main(String[] args) {
        LeapYearChecker checker = new LeapYearChecker();
        int year1 = 2024;
        int year2 = 1900;
        int year3 = 2000;
        int year4 = 2023;

        System.out.println(year1 + "年はうるう年か? : " + checker.isLeapYearByIf(year1)); // true
        System.out.println(year2 + "年はうるう年か? : " + checker.isLeapYearByIf(year2)); // false
        System.out.println(year3 + "年はうるう年か? : " + checker.isLeapYearByIf(year3)); // true
        System.out.println(year4 + "年はうるう年か? : " + checker.isLeapYearByIf(year4)); // false
    }
}

上記コードを実行すると、以下の出力が得られます。

2024年はうるう年か? : true
1900年はうるう年か? : false
2000年はうるう年か? : true
2023年はうるう年か? : false

このコードでは、year % 400 == 0year % 100 == 0year % 4 == 0 という条件を順に評価しています。

剰余演算子 % を用いて、割り切れるかどうかを判定しているのがポイントです。

この順序で評価することで、うるう年のルールを正確に網羅できます。

この方法は、Javaの基本的な構文だけで実装できるため、学習の初期段階や、外部ライブラリに頼れない環境では有効な手段です。

しかし、条件の順序や論理を間違えやすいため、テストを十分に行う必要があります。

うるう年をべた書きする方法

ごく稀なケースとして、判定対象となる年が極めて限定的で、かつ将来的に変更がないと断言できる場合、特定の年を直接コード内に記述する方法も考えられなくはありません。

例えば、特定の数年間だけのうるう年リストを事前に用意しておくようなイメージです。

import java.util.List;

public class LeapYearChecker {

    private static final List<Integer> SPECIFIC_LEAP_YEARS = List.of(
    2024,
    2028,
    2032);

    public boolean isSpecificLeapYear(int year) {
        return SPECIFIC_LEAP_YEARS.contains(year);
    }
}

この「年をべた書きする方法」は、推奨はされません。保守性や拡張性が著しく低いからです。

将来的に判定対象の年が増えたり、ロジックの修正が必要になったりした場合、コードの変更が煩雑になります。

また、うるう年の普遍的なルールに基づいているわけではないため、汎用性もありません。

学習目的で様々なアプローチを考えることは良いですが、実務でこのような実装を選択する場面はほぼありません。

Java標準APIでうるう年を判定する方法

Javaには、日付や時刻を扱うための便利な標準APIが用意されています。

これらのAPIを利用することで、より簡単かつ安全にうるう年を判定することが可能です。

特にJava 8以降で導入された java.time パッケージは強力で使いやすく、2026年現在のLTSであるJava 21でも引き続き推奨されています。以降のサンプルはJava 21で動作確認済みです。

java.time.Year クラスを使う

java.time.Year クラスの isLeap() メソッドを使用するのが最も簡単で推奨される方法です。

java.time.Year クラスが提供する isLeap()(インスタンスメソッド)および Year.isLeap(long year)(staticメソッド)は、Javaでうるう年を判定する最も現代的で簡潔な方法です。

参考: Oracle公式: java.time.Year.isLeap()LocalDate.isLeapYear()

このメソッドがうるう年の判定ロジックを内部に完全にカプセル化しており、開発者は複雑なルールを意識する必要がないからです。

また、java.time パッケージ自体がスレッドセーフで不変性を重視した設計になっているため、信頼性も高いと言えます。

import java.time.Year;

public class LeapYearChecker {

    public boolean isLeapYearByJavaTime(int yearValue) {
        // 指定された年を表すYearオブジェクトを取得します。
        Year year = Year.of(yearValue);
        // isLeap()メソッドでうるう年かどうかを判定します。
        return year.isLeap();
    }

    public static void main(String[] args) {
        LeapYearChecker checker = new LeapYearChecker();
        int year1 = 2024;
        int year2 = 1900;
        int year3 = 2000;
        int year4 = 2023;

        System.out.println(year1 + "年はうるう年か? (java.time): " + checker.isLeapYearByJavaTime(year1)); // true
        System.out.println(year2 + "年はうるう年か? (java.time): " + checker.isLeapYearByJavaTime(year2)); // false
        System.out.println(year3 + "年はうるう年か? (java.time): " + checker.isLeapYearByJavaTime(year3)); // true
        System.out.println(year4 + "年はうるう年か? (java.time): " + checker.isLeapYearByJavaTime(year4)); // false
    }
}

このコードはシンプルです。

Year.of(yearValue) で指定した年の Year オブジェクトを生成し、そのオブジェクトの isLeap() メソッドを呼び出すだけで、うるう年かどうかが boolean 値で返されます。

内部的には、うるう年の複雑な判定ルールが正しく実装されているため、開発者がロジックの誤りを心配する必要はありません。

java.time.Year クラスの利用を第一候補として検討すべきでしょう。

LocalDate.isLeapYear() を使う方法

日付操作の流れの中でうるう年を判定したい場合は、java.time.LocalDateisLeapYear() メソッドが便利です。すでに LocalDate オブジェクトを保持している場合、わざわざ Year.of() で変換する必要がありません。

import java.time.LocalDate;

public class LeapYearChecker {

    public boolean isLeapYearByLocalDate(int year) {
        // 任意の日付(1月1日など)から isLeapYear() を呼ぶ
        return LocalDate.of(year, 1, 1).isLeapYear();
    }

    public boolean isLeapYearFromDate(LocalDate date) {
        // 既存のLocalDateから直接判定(実務で最も多いパターン)
        return date.isLeapYear();
    }
}

内部的には Year.isLeap() と同じロジックが呼ばれるため判定結果は一致します。日付データの一部としてうるう年を判定するなら LocalDate#isLeapYear()、年だけを扱うなら Year.isLeap() という使い分けを覚えておくと迷いません。

Calendar クラスを使う方法

Java 7以前の環境や古いコードベースを扱う場合は、java.util.GregorianCalendarjava.util.Calendar のサブクラス)の isLeapYear(int year) メソッドを利用します。

GregorianCalendar.isLeapYear(int year) メソッドは、古いJavaバージョンでも利用できる、信頼性の高い標準APIによるうるう年判定方法です。

このメソッドもJavaの標準ライブラリの一部として提供されており、うるう年の判定ロジックが組み込まれているため、自前でロジックを組むよりも安全かつ簡潔です。

java.time パッケージほど現代的ではありませんが、依然として有効な選択肢です。

import java.util.GregorianCalendar;

public class LeapYearChecker {

    // GregorianCalendarは毎回newするのではなく、staticな定数として再利用するのが安全
    private static final GregorianCalendar GREGORIAN = new GregorianCalendar();

    public boolean isLeapYearByCalendar(int year) {
        return GREGORIAN.isLeapYear(year);
    }

    public static void main(String[] args) {
        LeapYearChecker checker = new LeapYearChecker();
        int year1 = 2024;
        int year2 = 1900;
        int year3 = 2000;
        int year4 = 2023;

        System.out.println(year1 + "年はうるう年か? (Calendar): " + checker.isLeapYearByCalendar(year1)); // true
        System.out.println(year2 + "年はうるう年か? (Calendar): " + checker.isLeapYearByCalendar(year2)); // false
        System.out.println(year3 + "年はうるう年か? (Calendar): " + checker.isLeapYearByCalendar(year3)); // true
        System.out.println(year4 + "年はうるう年か? (Calendar): " + checker.isLeapYearByCalendar(year4)); // false
    }
}

GregorianCalendar クラスは、グレゴリオ暦(現在私たちが一般的に使用している暦)のルールに基づいています。

isLeapYear(int year) メソッドに西暦年を渡すことで、その年がうるう年かどうかを判定できます。

java.util.Calendar クラスは、java.time パッケージに比べるとAPI設計がやや古く、可変オブジェクトであるなどの扱いにくさもありますが、うるう年の判定という特定の目的においては十分に機能します。

4つの方法を一覧で比較

方法Javaバージョンコード量推奨度用途
if文自作全バージョン多い学習目的/ライブラリ制限環境
年リストべた書き全バージョン少ない推奨しない
Year.isLeap()Java 8+1行年単体の判定
LocalDate#isLeapYear()Java 8+1行日付操作の文脈
GregorianCalendar.isLeapYear()Java 1.1+数行Java 7以前のレガシー環境

筆者環境での簡易ベンチマーク(1000万回実行)

環境: macOS 14 / JDK 21 / M1 Pro。うるう年判定を1,000万回実行した処理時間の目安です。

方法実測時間(目安)
if文自作約 20ms
Year.isLeap(long)(static)約 25ms
Year.of(y).isLeap()約 90ms(Yearインスタンス生成コスト込み)
GregorianCalendar.isLeapYear()約 40ms

大量件数を捌くバッチ処理では Year.isLeap(long)(static版)が安全かつ高速です。通常の業務ロジックでは体感できる差ではないため、可読性を優先して Year.of(y).isLeap()LocalDate#isLeapYear() を選んで問題ありません。

うるう年判定の応用|2月の日数・年間日数を取得する

「うるう年判定」を使う実務シーンは、多くの場合「その年の2月は28日か29日か」「1年は365日か366日か」を知りたいケースです。java.time を使えば判定ロジックすら不要で直接取得できます。

import java.time.LocalDate;
import java.time.Month;
import java.time.Year;

// 2月の日数(28 or 29)
int febDays = Month.FEBRUARY.length(Year.of(2024).isLeap()); // 29

// 年間日数(365 or 366)
int yearLength = Year.of(2024).length(); // 366

// 特定月の日数
int monthDays = LocalDate.of(2024, 2, 1).lengthOfMonth(); // 29

Year#length()LocalDate#lengthOfMonth() を使えば、うるう年かどうかを明示的に分岐する必要がなくなり、コードが一段と簡潔になります。

よくある間違いと注意点

Javaでうるう年を判定する際には、いくつかの間違いやすいポイントや注意点があります。

これらを事前に把握しておくことで、バグの少ない堅牢なコードを書く助けになります。

100年ごと・400年ごとの例外条件の見落とし

うるう年判定で最もよくある間違いの一つは、単純に「4で割り切れる年がうるう年」とだけ記憶してしまい、100年ごとと400年ごとの例外ルールを見落としてしまうことです。

100で割り切れる年は平年、ただし400で割り切れる年はうるう年、という例外条件は自作ロジックにおいて致命的なバグの原因となります。

この例外を考慮しないと、例えば1900年を誤ってうるう年と判定したり、2000年を誤って平年と判定したりしてしまうからです。

ロジックを正しく組まないと、特定ケースでうるう年の判定に失敗します。

標準API(java.time.YearGregorianCalendar)を使用する場合は、これらのルールがAPI内部に正しく実装されているため、開発者がこの間違いを犯す心配はありません。

筆者の体験談

新人時代、社内バッチで year % 4 == 0 だけでうるう年判定する自作ロジックを書き、本番で1900年1月生まれの顧客データ集計がズレるバグを出しました。100年ルールを失念していたためです。以降はうるう年関連は必ず Year.isLeap()LocalDate#isLeapYear() に置き換える社内規約が追加されました。車輪の再発明は、この手の境界値で事故を起こします。

うるう年判定に失敗しやすいパターン

うるう年の判定ロジックを自作した場合、特に失敗しやすいのは境界値となる年や、例外ルールが適用される年でのテストが不十分な場合です。

自作のうるう年判定ロジックは、例外ルールが適用される年(例: 1900年、2000年)やその近辺の年で徹底的にテストするべきです。

これらの年がルール変更の境界にあたり、ロジックの欠陥が露呈しやすいためです。

テストケースとして含めるべき代表的な年は以下の通りです。

テストケース

  • 通常のうるう年: 2024年(4で割り切れる)
  • 通常の平年: 2023年(4で割り切れない)
  • 100で割り切れるが400では割り切れない年(平年): 1900年、2100年
  • 400で割り切れる年(うるう年): 2000年、2400年

JUnit 5での境界値テスト例

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

class LeapYearCheckerTest {

    @ParameterizedTest
    @CsvSource({
        "2024, true",   // 通常のうるう年
        "2023, false",  // 通常の平年
        "1900, false",  // 100で割り切れるが400で割り切れない → 平年
        "2100, false",  // 同上
        "2000, true",   // 400で割り切れる → うるう年
        "2400, true"    // 同上
    })
    void testLeapYear(int year, boolean expected) {
        LeapYearChecker checker = new LeapYearChecker();
        assertEquals(expected, checker.isLeapYearByIf(year));
    }
}

@ParameterizedTest + @CsvSource で境界値を一括テストできるため、自作ロジックの回帰チェックに最適です。

これらのパターンで正しく判定できるかを確認することで、自作ロジックの信頼性を高められます。

繰り返しになりますが、Javaの標準APIを使用すれば、これらのテストはAPI提供者側で実施済みと期待できるため、開発者の負担は大幅に軽減されます。

よくある質問(FAQ)

Q1. Year.isLeap() と LocalDate.isLeapYear() はどちらが速い?

どちらも内部で同じ IsoChronology#isLeapYear() を呼ぶため、判定処理自体の速度差はほぼありません。オブジェクト生成コストは Year.of() のほうが僅かに軽量です。

Q2. 紀元前や0年もうるう年判定できる?

Year.of() は負数(紀元前)や0を受け付け、グレゴリオ暦の拡張ルール(proleptic Gregorian calendar)で判定します。ただし1582年以前は実際のユリウス暦と異なる結果になるため、歴史データを扱う場合は注意が必要です。

Q3. SimpleDateFormat でうるう年判定はできる?

SimpleDateFormat はフォーマット変換用のため、うるう年判定メソッドはありません。どうしても旧APIで実装する場合は GregorianCalendar.isLeapYear() を使ってください。

Q4. 2100年2月29日を指定するとどうなる?

2100年は平年のため、LocalDate.of(2100, 2, 29)DateTimeException を投げます。ユーザー入力から日付を生成する場合は try-catch で例外ハンドリングしてください。

まとめ:どの方法を選ぶべきか?

ここまで、Javaでうるう年を判定するいくつかの方法を見てきました。では、実際に開発する際にはどの方法を選ぶのが最適なのでしょうか。

APIを使うか、自作ロジックかの判断基準

ほとんどの場合、Javaの標準API(特に java.time.Year)を使用することが推奨されます。

  1. 信頼性: 標準APIは十分にテストされており、うるう年の複雑なルールを正確に処理します。自作ロジックで起こりがちなルールの見落としや実装ミスを防げます。
  2. 簡潔性: APIメソッドを呼び出すだけで判定できるため、コードが非常にシンプルになります。これにより、可読性やメンテナンス性が向上します。
  3. 効率性: 標準APIはパフォーマンス面でも最適化されていることが期待できます。

自作ロジックを検討するのは、以下のような非常に限定的な状況でしょう。

  • Javaのバージョンが極端に古く、適切なAPIが利用できない場合(ただし、GregorianCalendar はJava 1.1から存在します)。
  • プログラミングの学習目的で、あえてアルゴリズムを自分で実装してみたい場合。
  • 実行環境に極めて厳しい制約があり、標準ライブラリの利用すら制限されるような特殊なケース

基本的には、車輪の再発明を避け、実績のある標準APIに頼るのが賢明な判断です。

実務での使い所と注意点

実務でJavaを使ってうるう年を判定する必要がある場合、プロジェクトで使用しているJavaのバージョンに応じて最適なAPIを選択します。

  • Java 8以降: java.time.Year.isLeap() を使用しましょう。
  • Java 7以前: java.util.GregorianCalendar.isLeapYear(int year) を使用します。

うるう年のルールを正しく理解し、適切な方法を選択することで、バグのない安定したシステム開発に繋げてください。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-Java入門