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

Java入門

Javaでうるう年判定する4つの方法

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
7枚
2/29(うるう年判定4つの方法)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

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

結論から言うと、Java 8以降なら java.time.Year.isLeap()(または LocalDate#isLeapYear())を使うのが最も安全かつ簡潔です。

4年ごと・100年ごとの例外ルールをif文で自作すると、100の倍数の判定漏れなど境界値のミスが起きやすいためです。

自作する場合の正しい条件式は year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) の1行です。「4で割り切れるか」だけで判定すると、1900年や2100年をうるう年と誤判定してしまいます。

if文による自作・Year.isLeap()・LocalDate#isLeapYear()・GregorianCalendar.isLeapYear()の4つを実行結果付きで比較し、キーボード入力した西暦を判定するプログラムや境界値テストまで紹介します。

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

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

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

うるう年とは?

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でうるう年を判定する方法(if文で自作)

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

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

if文だけで実装する方法

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

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

判定の流れを図にすると、次のようになります。400→100→4の順に「割り切れるか」を確かめるのがポイントです。

うるう年の判定フロー(Q1から順に答える)
QUESTION / 西暦 year
はい
いいえ
Q1. 400で割り切れる?year % 400 == 0
うるう年例:2000年
Q2へ
Q2. 100で割り切れる?year % 100 == 0
平年例:1900年・2100年
Q3へ
Q3. 4で割り切れる?year % 4 == 0
うるう年例:2024年
平年例:2023年
NOTE400→100→4の順に「割り切れるか」を確かめるのがポイント。この順で評価すると、うるう年のルールを漏れなく判定できます。
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 == 0、year % 100 == 0、year % 4 == 0 という条件を順に評価しています。

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

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

isLeapYearByIf() で4つの年を判定する(400 → 100 → 4 の順)
YEAR / 年
CHECK / 確かめた順(余り)
RESULT / 結果
2024年
400:余り24 → いいえ
100:余り24 → いいえ
4:余り0 → はい
うるう年true
1900年
400:余り300 → いいえ
100:余り0 → はい
平年false
2000年
400:余り0 → はい
うるう年true
2023年
400:余り23 → いいえ
100:余り23 → いいえ
4:余り3 → いいえ
平年false
NOTE「はい」が出た時点で return するので、2000年は1回、1900年は2回で判定が終わります。

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

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

1行で書く場合(&&と||の組み合わせ)

3つのif文は、論理演算子 && と || を使うと1行の条件式にまとめられます。

public class LeapYearOneLine {

    // 4で割り切れ、かつ「100で割り切れない または 400で割り切れる」年がうるう年
    static boolean isLeap(int year) {
        return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
    }

    public static void main(String[] args) {
        for (int year : new int[] {2024, 1900, 2000, 2023}) {
            System.out.println(year + "年はうるう年か? : " + isLeap(year));
        }
    }
}

実行結果は、if文版と同じになります。

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

&& の左側で4の倍数に絞り込み、右側の括弧で「100の倍数ではない、または400の倍数である」かを確かめています。if文を並べる書き方と判定結果は同じなので、読みやすいほうを選んでください。

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

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

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

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 25や、企業導入が根強い前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 クラスの利用を第一候補として検討すべきでしょう。

キーボード入力した西暦を判定する(Scanner)

学習課題でよく出る「入力した西暦がうるう年かどうかを表示する」プログラムも、Year.isLeap(long)(staticメソッド)を使えば数行で書けます。

import java.time.Year;
import java.util.Scanner;

public class LeapYearInput {

    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.print("西暦を入力してください: ");
            int year = sc.nextInt();

            // staticメソッド版なら Year オブジェクトを作らずに判定できる
            if (Year.isLeap(year)) {
                System.out.println(year + "年はうるう年です(2月は29日まで)");
            } else {
                System.out.println(year + "年は平年です(2月は28日まで)");
            }
        }
    }
}

2024と1900を入力した場合の実行例です。

西暦を入力してください: 2024
2024年はうるう年です(2月は29日まで)

西暦を入力してください: 1900
1900年は平年です(2月は28日まで)

数値以外が入力されたときの InputMismatchException など、Scannerの入力処理の注意点はJavaのScanner使い方で解説しています。

LocalDate.isLeapYear() を使う方法

日付操作の流れの中でうるう年を判定したい場合は、java.time.LocalDate の isLeapYear() メソッドが便利です。

すでに 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.GregorianCalendar(java.util.Calendar のサブクラス)の isLeapYear(int year) メソッドを利用します。

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

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

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

import java.util.GregorianCalendar;

public class LeapYearChecker {

    // GregorianCalendarは毎回newせず、staticな定数として再利用する(isLeapYear(int)は状態を変更しない)
    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 クラスは、グレゴリオ暦(現在私たちが一般的に使用している暦)のルールに基づいています。ただしデフォルトでは1582年10月15日を境にそれ以前はユリウス暦として扱うため、1582年以前の年は Year.isLeap() と結果が異なります(例: 1500年は GregorianCalendar では true、Year.isLeap() では false)。1583年以降は Year.isLeap() と同じ結果です。

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

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

4つの方法とべた書きを一覧で比較

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

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

環境: macOS 26 / JDK 21(Amazon Corretto 21.0.5)/ Apple M4。1,024個の年を巡回しながらうるう年判定を1,000万回実行し、ウォームアップ後に5回計測した処理時間の目安です(JMHを使わない簡易計測のため、JITの最適化で結果は変わります)。

方法実測時間(目安)
if文自作約 9ms
Year.isLeap(long)(static)約 5ms
Year.of(y).isLeap()約 7ms(Yearインスタンス生成を含む)
GregorianCalendar.isLeapYear()約 5ms

どの方法も1,000万回で10ms未満で、大きな差はありません。大量件数を捌くバッチ処理でも、インスタンスを作らない 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でうるう年を判定する際には、いくつかの間違いやすいポイントや注意点があります。

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

例外条件の見落とし

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

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

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

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

標準API(java.time.Year や GregorianCalendar)を使用する場合は、これらのルールが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 で境界値を一括テストできるため、自作ロジックの回帰チェックに最適です。JUnit 5の基本的な書き方はJUnit5の使い方入門を参照してください。

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

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

よくある質問(FAQ)

Q1. Year.isLeap() と isLeapYear() の速度に差はある?

A1. Year.isLeap() は Year.isLeap(long)(staticメソッド)を、LocalDate#isLeapYear() は IsoChronology#isLeapYear() を呼びますが、どちらも中身は同じ式(4で割り切れ、かつ100で割り切れない、または400で割り切れる)なので、判定処理自体の速度差はほぼありません。

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

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

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

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

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

A4. 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) を使用します。

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

REVISIONS / 改訂履歴
REV.DATE内容
52026-10-16図解を描き直し(図面のデザインにそろえた)
42026-10-14雰囲気だけの挿絵を外した
32026-10-04記載内容の事実確認と修正
22026-09-042026年9月時点の情報で全体を見直し
12025-05-10初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

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

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門