わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

続・満年齢の計算

コマンドライン引数から「生年月日」「現在の日付」「年齢計算方法」の情報を受け取り,該当日の年齢を求めるプログラムを作りなさい.年齢計算方法としては,数え年,慣例による満年齢(誕生日に1加える),法に基づく満年齢(誕生日の前日に1加える)に対応すること.

満年齢の計算 - わさっき

コードです.

#!/usr/bin/env ruby

require 'date'

class Age
  METHOD_ORDINAL = 1    # 誕生した日を1歳とし,1月1日に年齢を1加える
  METHOD_CONVENTION = 2 # 誕生日に年齢を1加える
  METHOD_LAW = 3  # 年齢計算ニ関スル法律に基づき,誕生日の前日に年齢を1加える

  def initialize
    @birth = nil # 生年月日.Dateインスタンスまたはnil
    @today = Date.today # 基準日
    @method = METHOD_CONVENTION # 算出方法.定数METHOD_*のいずれか
    @readiness = 0 # 入力データの状況
  end
  attr_accessor :birth, :today, :method
  attr_reader :readiness

  def parse(argv)
    @readiness = 0

    # コマンドライン引数解析ができなければすぐ終了
    return if !(Array === argv) or argv.empty?

    @readiness = 1

    while !argv.empty?
      # -l, -o, -c オプションの判定
      if argv[0][0, 1] == '-'
        param = argv.shift
        case param
        when '-o'
          @method = METHOD_ORDINAL
        when '-c'
          @method = METHOD_CONVENTION
        when '-l'
          @method = METHOD_LAW
        end
      else
        d, argv = create_date(argv)
        return unless d
        case @readiness
        when 1
          # 生年月日
          @birth = d
          @readiness = 2
        when 2
          # 基準日
          @today = d
          @readiness = 3
        end
      end
    end
  end

  def create_date(argv)
    d = nil

    begin
      if argv.size >= 3 and /^\d+ \d+ \d+$/ =~ argv[0, 3].join(' ')
        # 3つの数値が引数にあるなら,それをDateオブジェクトに変換する
        d = Date.parse(argv.slice!(0, 3).join('/'))
      else
        # そうでなければ,配列の最初の要素をDateオブジェクトに変換する
        d = Date.parse(argv.shift)
      end
    rescue
      # 失敗したら,nilと,日付取得のための要素を取り除いた配列を返す
      # (無限ループ回避のため)
    end

    return d, argv
  end
  
  def method_to_s
    case @method
    when METHOD_ORDINAL
      'ordinal'
    when METHOD_LAW
      'by law'
    else
      'by convention'
    end
  end

  def calc_age
    # 未来の人の年齢を求めることはできない
    return -1 if @today < @birth

    # 年差を仮の年齢とおく
    age = @today.year - @birth.year

    if @method == METHOD_ORDINAL
      # 数え年: 年差+1
      age += 1
    else
      # [基準年, 生まれ月, 生まれ日]をもとにDateインスタンスを作る.
      # ただし,2月29日生まれで,基準年が閏年でない場合は,
      # 基準年の3月1日を用いる.
      if !@today.leap? and @birth.mon == 2 and @birth.mday == 29
        d = Date.new(@today.year, 3, 1)
      else
        d = Date.new(@today.year, @birth.mon, @birth.mday)
      end
      # 法律の満年齢: 年差 (ただし,誕生日の前日より前なら -1)
      # 慣習の満年齢: 年差 (ただし,誕生日より前なら -1)
      d -= 1 if @method == METHOD_LAW
      age -= 1 if @today < d
    end

    # 戻り値
    age
  end
end

if __FILE__ == $0
  # 引数チェック
  ac = Age.new
  ac.parse(ARGV)
  case ac.readiness
  when 0
    puts "Usage: ruby #{$0} [-l][-c][-o] birthdate [today]"
    exit
  when 1
    puts "Wrong parameter"
    exit
  end

  # 年齢計算
  age = ac.calc_age

  # 出力
  puts "Birth date: #{ac.birth}"
  puts "Today:      #{ac.today}"
  if age < 0
    puts "Preborn?"
    exit
  end
  puts "Age: #{age}"
  puts "Method: #{ac.method_to_s}"
end

実行例は:

$ date
Wed Feb  6 05:59:59 JST 2008
$ ruby age2.rb 2000 2 6
Birth date: 2000-02-06
Today:      2008-02-06
Age: 8
Method: by convention
$ ruby age2.rb 2000 2 7
Birth date: 2000-02-07
Today:      2008-02-06
Age: 7
Method: by convention
$ ruby age2.rb -l 2000 2 7
Birth date: 2000-02-07
Today:      2008-02-06
Age: 8
Method: by law
$ ruby age2.rb -o 2000 2 7
Birth date: 2000-02-07
Today:      2008-02-06
Age: 9
Method: ordinal
$ ruby age2.rb 2000/2/29 2003/2/28
Birth date: 2000-02-29
Today:      2003-02-28
Age: 2
Method: by convention
$ ruby age2.rb -l 2000/2/29 2003/2/28
Birth date: 2000-02-29
Today:      2003-02-28
Age: 3
Method: by law
$ ruby age2.rb 2000/2/29 2003/3/1
Birth date: 2000-02-29
Today:      2003-03-01
Age: 3
Method: by convention

工夫したこと.

  • コマンドライン引数で日付を指定する方法は,「年 月 日」と3つの数字に分けてもいいし,「年/月/日」や「年-月-日」,あるいは「日-月-年」といった,Date.parseが解釈できる文字列でもいいことにしてみました.
  • オプションについて,日付の引数の前でなくても,後ろで書いてもいいようにしました.「2000 2 -l 28」のように,間に入れるのはさすがにダメですが.
  • 2月29日生まれ対策が不可欠です.「2月29日生まれの人が,うるう年でない年は,慣習なら3月1日に1歳増え,法律なら2月28日(3月1日の前日)に1歳増える」ように処理しました.
  • どの方法でも,基準日が生年月日よりも前なら,年齢を求めるわけにいきません.calc_ageではこの場合の戻り値を-1とし,出力時に,"Preborn?" というメッセージにしてみました.
  • 英語は,いい表現が浮かびませんでした.とりわけ「基準日」は,辞書(英辞郎 Ver.111)を引けば,base date●basis date●critical date●reference date●relevant dateと出まして,各英語表現で用例を見ても,「これだ!」というのが出ませんでした.もしかしたら「基準日」という表現がよくなかったのかもしれません.

次の問題.

慣習と法律とで,満年齢に違いが現れる日(生年月日と基準日のペア)を求めるプログラムを作りなさい.生年月日と基準日の範囲は,ハードコード(プログラムの中で決め打ち)してよい.

もちろん答えは後日です.今日は書きすぎました.