Edited at

まだ年齢計算で消耗してるの?

More than 3 years have passed since last update.


2月29日に女性からプロポーズされた男性は、断ってはいけない


サービスの申し込みで年齢制限を設けるなど、誕生日から年齢を算出したいことがあります。さてどういうロジックを書けばよいのでしょうか。


年齢とは何か(仕様の定義)

年齢計算は日本の場合は、「年齢計算ニ関スル法律」に基づいた計算になります。

ここで、大きく別れるのが、時刻基準で計算するか日基準で計算するかです。

一般的には時刻基準で計算するので、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. 誕生日と基準日の年の差をとり、年齢とする。

  2. 誕生日の月が基準日の月を比較


    1. 誕生日の月の方が大きい場合:


      1. 年齢から1を引く



    2. 誕生日の月と基準日の月が同じ場合:


      1. 誕生日の日と基準日の日を比較


        1. 誕生日の日の方が大きい場合:


          1. 年齢から1を引く









  3. 年齢を返す

コードにすると、こんな感じです。

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日生まれの人の計算も、時刻基準の計算と同等になります。

ということで、時刻基準の年齢計算であれば、このLocalDatebetweenは使えそうです。


まとめ


  • 年齢計算をする場合は、日基準か時刻基準か、仕様をまず明らかにしよう。

  • 法令のしばりがなければ時刻基準を使おう。