サービスの申し込みで年齢制限を設けるなど、誕生日から年齢を算出したいことがあります。さてどういうロジックを書けばよいのでしょうか。
年齢とは何か(仕様の定義)
年齢計算は日本の場合は、「年齢計算ニ関スル法律」に基づいた計算になります。
ここで、大きく別れるのが、時刻基準で計算するか日基準で計算するかです。
一般的には時刻基準で計算するので、4月1日生まれの人は、4月1日の0時ちょうどに歳をとることにすればよいですが…、日基準の場合には誕生日前日に歳をとることになります。日基準で計算される有名なのは学齢がありますが、選挙権なんかも同じく日基準のようです。
ここの仕様をどちらにするか、しっかり定義する必要があります。法によっても解釈の仕方が異なるのが現状のようです。
一番問題となる2月29日生まれの人が歳をとるのは、基準の違いによって以下のようになります。
うるう年でない | うるう年である | |
---|---|---|
日基準 | 2月28日 | 2月28日 |
時刻基準 | 3月1日 | 2月29日 |
3月1日の人は、
うるう年でない | うるう年である | |
---|---|---|
日基準 | 2月28日 | 2月29日 |
時刻基準 | 3月1日 | 3月1日 |
境界値としては、この2つ(時刻基準は2月29日生まれのみ特別)なので、ここをテストケースにあげればよいわけです。
一般的な実装例
業務SEが業務仕様決めるノリで作りがちなロジック(時刻基準)
- 誕生日と基準日の年の差をとり、年齢とする。
- 誕生日の月が基準日の月を比較
- 誕生日の月の方が大きい場合:
- 年齢から1を引く
- 誕生日の月と基準日の月が同じ場合:
- 誕生日の日と基準日の日を比較
- 誕生日の日の方が大きい場合:
- 年齢から1を引く
- 誕生日の日の方が大きい場合:
- 誕生日の日と基準日の日を比較
- 誕生日の月の方が大きい場合:
- 年齢を返す
コードにすると、こんな感じです。
public static int calcAgeAruAru(Date birthday, Date now) {
int age = now.getYear() - birthday.getYear();
if (now.getMonth() < birthday.getMonth()) {
age--;
} else if (now.getMonth() == birthday.getMonth()) {
if (now.getDay() < birthday.getDay()) {
age--;
}
}
return age;
}
エレガントな計算(時刻基準)
年齢計算で広く知られている簡単な方法は、以下のようなものです。
http://itpro.nikkeibp.co.jp/article/Watcher/20070822/280097/
public static int calcAge(Date birthday, Date now) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return (Integer.parseInt(sdf.format(now)) - Integer.parseInt(sdf.format(birthday))) / 10000;
}
パッと見、これでうまくいくのが不思議な気がしますが、実際に値入れて自分で計算してみると、納得できると思います。
日基準の計算
日基準の場合は、時刻基準の計算を一日ずらして処理をします。
public static int calcAgeDateStd(Date birthday, Date now) {
Calendar nowCal = Calendar.getInstance();
nowCal.setTime(now);
nowCal.add(Calendar.DAY_OF_MONTH, 1);
return calcAge(birthday, nowCal.getTime());
}
Javaのライブラリでの実装
Javaのライブラリには、年齢計算に使えそうな、2つの日付の間の年数を数えるものがあります。使えるのか仕様を確認してみましょう。
Joda Time
日時系ライブラリ定番のJoda Timeには、Years.yearsBetween
というのがあります。
import org.joda.time.LocalDate;
import org.joda.time.Years;
public class JodaAge {
public static int calcAge(LocalDate birthday, LocalDate now) {
Years age = Years.yearsBetween(birthday, now);
return age.getYears();
}
}
非常にすっきり書けますが、下記Issueのような問題があります。
Years between failing for leap years #132
2月29日生まれの人が、うるう年には2月28日に歳をとってしまうようです。作者からするとこれは仕様どおり、between
系操作は年齢計算用ではなく、あくまでも日付の差を出すためのものだ、という説明。一理あります。
ということで、Joda Timeを使っている場合は、年齢計算にYears.yearsBetween
を使わないようにしましょう。
java.time.LocalDate
Java8から使えるLocalDateではどうでしょうか。やはりbetween
というメソッドがChronoUnit
にあります。
long calcDate(LocalDate birthday, LocalDate today) {
return ChronoUnit.YEARS.between(birthday, today);
}
これは、2月29日生まれの人の計算も、時刻基準の計算と同等になります。
ということで、時刻基準の年齢計算であれば、このLocalDate
のbetween
は使えそうです。
まとめ
- 年齢計算をする場合は、日基準か時刻基準か、仕様をまず明らかにしよう。
- 法令のしばりがなければ時刻基準を使おう。