Javaによるうるう年チェック

土曜日に参加したペアプログラミング勉強会で作って自信満々にお披露目したうるう年チェックのコードが誤ってたのでここでさらしておきますw


バグってるコード

package q2;

import java.util.Calendar;

public class うるう年チェッカー {
    public boolean isうるう年(int i) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, i);
        cal.set(Calendar.MONTH, Calendar.FEBRUARY);
        return cal.getActualMaximum(Calendar.DAY_OF_MONTH) == 29;
    }
}

このコードだと、29日(うるう年なら30日)以降の日に「isうるう年」を実行するバグります。例えば今日(2012年7月30日)に引数2012を渡して実行すると、

  1. Calendarオブジェクト取得時に2012/7/30と言う状態のオブジェクトが返却される
  2. 年を2012->2012の置き換えを行い結果、2012/7/30と言う状態になる
  3. 次に月を2月に置き換えを行い結果、2012/2/30となる
  4. 2月が取りうる最大日を取得しようとしているのだが、2012/2/30は許容外であり、2012/2/30は、2012/3/1に変換され3月が取りうる最大値である31が返却される

って感じでバグる。


正しいコードは

package q2;

import java.util.Calendar;

public class うるう年チェッカー {
    public boolean isうるう年(int i) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, i);
        cal.set(Calendar.MONTH, Calendar.FEBRUARY);
	cal.set(Calendar.DAY_OF_MONTH, 1);
        return cal.getActualMaximum(Calendar.DAY_OF_MONTH) == 29;
    }
}

と、日付に1もしくは許容される値をセットしなければいけないです。先に月をセットしても良いってことは、get系の処理が走るまで補正ロジックは走らないみたいですね。不安なら月のセットの前に日のセットをした方が良いかもしれません。


そもそもやりたかったのは「もっと簡単にうるう年チェックできるんじゃね?」って実験しようとしたらテストでこけた訳で、多分Javaでの最小コードはこーじゃないかな?

package q2;

import java.util.Calendar;
import java.util.GregorianCalendar;

public class うるう年チェッカー {
    public boolean isうるう年(int i) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, i);
        return cal.getActualMaximum(Calendar.DAY_OF_YEAR) == 366;
    }
}

※2012/8/2追記
[twitter:@haradakiro]に以下のようなツッコミをもらったので、これも晒してみます。

package q2;

import java.util.Calendar;
import java.util.GregorianCalendar;

public class うるう年チェッカー {
    public boolean isうるう年(int i) {
        GregorianCalendar cal = (GregorianCalendar)Calendar.getInstance();
        return cal.isLeapYear(i);
    }
}

エレガント!!
(でも、isLeapのメソッドは設計誤りだ・よ・ね!)