2015年8月31日月曜日

年齢を計算する関数

過去に実装した年齢計算プログラムのロジックが残念なことになっていましたので、考えを整理しました。
記事の最後に載せたプログラムのコメントにも書きましたが、ポイントは2つあります。
  1. 基準日の年と生年の差
  2. 基準日がその年の誕生日よりも前のときは1を減算する
2つ目の条件につきましては、生年月日の年を基準日の年と置き換え、年齢を求める日付と比較すれば判定できます。
「年齢を求める日付」はいい言葉が思いつきませんでした。いい言葉をご存知の方はコメントして頂けると助かります。
2015/8/31
NARITA Shoさんから「Excel 界隈では「基準日」という語が使われているみたいですね。」とのコメントを頂きました。ありがとうございました。

以下、xyzzy lispとC#での実装を記載します。
2015/9/2
C#での実装例を追加しました。

(defun compute-age (birth-date &optional date)
  "生年月日(universal-time)から年齢を計算する関数
  dateが指定されない時は現時点の年齢を計算する"
  ;; 年月日のみ計算に使用する
  ;; 基本的には対象年と生年月日の年を返す。
  ;; ただし、対象年の誕生日を迎えていない時は1を減算して返す。
  (setq date (if (null date) (get-universal-time) date))
  (multiple-value-bind
      (_ _ _ birth-day birth-month birth-year)
      (decode-universal-time birth-date)
    (multiple-value-bind
        (_ _ _ day month year)
        (decode-universal-time date)
      (if (> (encode-universal-time 0 0 0 birth-day birth-month year)
             (encode-universal-time 0 0 0 day month year))
          (1- (- year birth-year))
        (- year birth-year)))))
using System;

namespace Seekones.Util
{
    /// <summary>
    /// 誕生日クラス
    /// </summary>
    public class Birthday
    {
        /// <summary>生年月日</summary>
        private readonly DateTime birthday;
        
        /// <summary>
        /// 生年月日を指定してインスタンス化する
        /// </summary>
        public Birthday(DateTime birthday)
        {
            this.birthday = birthday;
        }
        
        /// <summary>
        /// 当日を基準日として年齢を計算する
        /// </summary>
        public int ComputeAge()
        {
            return ComputeAge(DateTime.Today);
        }
        
        /// <summary>
        /// referenceDate時点での年齢を計算する
        /// </summary>
        public int ComputeAge(DateTime referenceDate)
        {
            // 基準年と生年を取得する
            var referenceYear = referenceDate.Year;
            var birthYear     = birthday.Year;
            
            // 基準年と生年の差を算出する
            var subYear = referenceYear - birthYear;
            
            // 基準年の誕生日を取得する
            var birthdayOfReferenceYear = birthday.AddYears(subYear);
            
            // 年齢を算出する
            var age = 0;
            if (birthdayOfReferenceYear > referenceDate)
            {
                // 基準年の誕生日を迎えていない時は、基準年と生年の差から
                // 1を減算して年齢とする
                age = subYear - 1;
            }
            else
            {
                // 基準年の誕生日を迎えているときは、基準年と生年の差を
                // 年齢とする
                age = subYear;
            }
            
            return age;
        }

#if UT
        /// <summary>
        /// 単体テスト用メインメソッド
        /// 
        public static void Main(string[] args)
        {
            testComputeAge("1970/01/01", "1971/01/01", 1);
            testComputeAge("1970/01/01", "1971/12/31", 1);
        }

        /// <summary>
        /// 生年月日テストメソッド
        /// </summary>
        /// <param name="strBirthday" />生年月日(yyyy/MM/dd)
        /// <param name="strReferenceDate" />基準日(yyyy/MM/dd)
        /// <param name="expectedAge" />期待する年齢
        private static void testComputeAge(string strBirthday,
            string strReferenceDate,
            int expectedAge)
        {
            var birthday      = DateTime.Parse(strBirthday);
            var referenceDate = DateTime.Parse(strReferenceDate);
            
            var birthdayObj = new Birthday(birthday);
            
            var age = birthdayObj.ComputeAge(referenceDate);
            
            if (age == expectedAge) 
            {
                Console.WriteLine("OK");
                return;
            }
            Console.WriteLine("NG: expected:" + expectedAge + " actual:" + age);
        }
#endif
    }
}

0 件のコメント:

コメントを投稿