コマンドライン引数から「生年月日」「現在の日付」「年齢計算方法」の情報を受け取り,該当日の年齢を求めるプログラムを作りなさい.年齢計算方法としては,数え年,慣例による満年齢(誕生日に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と出まして,各英語表現で用例を見ても,「これだ!」というのが出ませんでした.もしかしたら「基準日」という表現がよくなかったのかもしれません.
次の問題.
慣習と法律とで,満年齢に違いが現れる日(生年月日と基準日のペア)を求めるプログラムを作りなさい.生年月日と基準日の範囲は,ハードコード(プログラムの中で決め打ち)してよい.
もちろん答えは後日です.今日は書きすぎました.