日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

Railsで祝日の判定と祝日の名前を得る

 Ruby on Railsで以下のことをやりたいのです。目的はカレンダーに祝日の時は色を変えるとかして、祝日の名前を出すとかをするため。

  1. 祝日かどうかの判定
  2. 祝日の場合は祝日の名前を得る

 で、RailsというよりRubyで上記のことが出来るようにするにはどうすればいいのでしょうか?
 1.の祝日かどうかの判定については「date2」の中のholiday.rbに記載されているようです。このdate2はRubyに標準添付されているライブラリなのですが、どうも祝日判定のholiday.rbについては標準添付されてないようです。date2のパッケージをダウンロードし、holiday.rbをRubyのdateライブラリがインストールされている場所(私の場合はMacでrvmを使っているので ~/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/date)に設置すれば使えました。2011年元日のDateオブジェクトを作って、インスタンスメソッドであるnational_holiday?を使えばいいです。

$ irb
ruby-1.8.7-p352 :001 > require 'date'
ruby-1.8.7-p352 :002 > require 'date/holiday'
ruby-1.8.7-p352 :003 > d = Date.new(2011,1,1)
 => # 
ruby-1.8.7-p352 :004 > d.national_holiday?
 => true 

この場合は以下の問題点があったため、今回の目的には合わないようです。

  • 祝日かどうかをbooleanで返事するのみ(祝日の名前が得られない)
  • Date型のインスタンスのみ(Time型などで使えない)

 RailsActiveRecordを使ってデータベースを扱う場合、日付のカラムをdatetime型で作るとActiveSupport::TimeWithZoneというクラスのインスタンスになるようです。Time型が拡張されているのかな?
 他に祝日判定の方法を探していると、Ruby用祝日判定コードを書いてくれている方がいらっしゃいました。これなら祝日の判定と祝日の名前取得が同時に出来そうです。で、これをRailsプロジェクトから使うため、shukujitu.rbという名前で保存してinitializersの下に置くことにしました。
 さらにActiveSupport::TimeWithZoneにshukujitu?メソッドを追加する必要があるのかな?と思い、以下のように修正を加えました。

class ActiveSupport::TimeWithZone # Timeクラスから修正
  def inc_day(n=1)
    return self + n*24*60*60
  end
end

module Shukujitu
  省略
end # of module

class ActiveSupport::TimeWithZone # Timeクラスから修正
  def shukujitu?
    Shukujitu.shukujitu?(self)
  end
end


ScheduleモデルのMigrationはこんな感じです。

class CreateSchedules < ActiveRecord::Migration
  def self.up
    create_table :schedules do |t|
      t.string   "title",           :null => false
      t.text     "content"
      t.datetime "start_date",      :null => false
      t.datetime "end_date"
      t.boolean  "full_time"
      t.datetime "created_at"
      t.datetime "updated_at"
    end
  end
  
  def self.down
    drop_table :schedules
  end
end


consoleで動きを確かめてみると、祝日の名前の敬老の日が返されました。

$ ruby script/console 
Loading development environment (Rails 2.3.11)
ruby-1.8.7-p352 :001 > schedule = Schedule.first
 => # 
ruby-1.8.7-p352 :002 > schedule.end_date
 => Mon, 19 Sep 2011 01:00:00 UTC +00:00 
ruby-1.8.7-p352 :003 > schedule.end_date.shukujitu?
 => "敬老の日" 
ruby-1.8.7-p352 :004 > 


ここで、ActiveSupport::TimeWithZoneクラスはTimeクラスを拡張したもの?だとしたら、Timeクラスにshukujitu?メソッドを追加でもいいのじゃないか?と、initializerに設置したshujijitu.rbを以下のように書き換えました(元に戻した)consoleで動きを確かめたところ、これでも目的通りの動きを果たしました。

class Time # ActiveSupport::TimeWithZone から元に戻した
  def inc_day(n=1)
    return self + n*24*60*60
  end
end

module Shukujitu
  ##省略
end # of module

class Time # ActiveSupport::TimeWithZone から元に戻した
  def shukujitu?
    Shukujitu.shukujitu?(self)
  end
end


shukujitu?メソッドActiveSupport::TimeWithZoneクラスに追加した場合と、Timeクラスに追加した場合での動きの違いを見るために、Shukujituモジュール内で実際に祝日を返しているkihon_shukujitu?メソッドにcallerを入れてbacktraceを見てみることにした。

module Shukujitu
  def self.kihon_shukujitu? date
    p caller
    ### 省略 ###
  end
end # of module


ActiveSupport::TimeWithZoneクラスにshukujitu?メソッドを追加した場合のbacktraceはこうなりました。initializersに置いてあるshukujitu.rbが直接よびだされているようです。(irb_bindingの部分は除いて考えてます)

$ ruby script/console 
Loading development environment (Rails 2.3.11)
ruby-1.8.7-p352 :001 > schedule = Schedule.first
 => # 
ruby-1.8.7-p352 :002 > schedule.end_date.shukujitu?
["/Users/tashiro/work/sample/config/initializers/shukujitu.rb:57:in `shukujitu?'", "/Users/tashiro/work/sample/config/initializers/shukujitu.rb:194:in `shukujitu?'", "(irb):2:in `irb_binding'", "/Users/tashiro/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", "/Users/tashiro/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/irb/workspace.rb:52"]
 => "敬老の日" 


次にTimeクラスにshukujitu?メソッドを追加した場合のbacktraceはこうなりました。ActiveSupportのtime_with_zone.rbがmethod_missingが呼ばれ、そこからinitializersのshukujitu.rbが呼ばれています。

$ ruby script/console 
Loading development environment (Rails 2.3.11)
ruby-1.8.7-p352 :001 > schedule = Schedule.first
 => # 
ruby-1.8.7-p352 :002 > schedule.end_date
 => Mon, 19 Sep 2011 01:00:00 UTC +00:00 
ruby-1.8.7-p352 :003 > schedule.end_date.shukujitu?
["/Users/tashiro/work/sample/config/initializers/shukujitu.rb:57:in `shukujitu?'", "/Users/tashiro/work/sample/config/initializers/shukujitu.rb:194:in `shukujitu?'", "/Users/tashiro/.rvm/gems/ruby-1.8.7-p352/gems/activesupport-2.3.11/lib/active_support/time_with_zone.rb:309:in `__send__'", "/Users/tashiro/.rvm/gems/ruby-1.8.7-p352/gems/activesupport-2.3.11/lib/active_support/time_with_zone.rb:309:in `method_missing'", "(irb):3:in `irb_binding'", "/Users/tashiro/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'", "/Users/tashiro/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/irb/workspace.rb:52"]
 => "敬老の日" 


TimeクラスとActiveSuport::TimeWithZoneの関係がまだきちんと分かっていないのですが、Timeクラスに祝日判定のメソッドを追加することで目的の事は出来るようになりました。がこれで良いのかはまだまだ疑問が残ります。