RubyでiCalendarを利用する

RubyでiCalendar形式を使いたくて、ちょっと調べたらやっぱりありました。
Ruby用iCalendarライブラリ♪->http://icalendar.rubyforge.org/
インストールしたiCalendarライブラリのバージョンは0.98です。

gemで一発インストール

$ gem install icalendar

サンプル(READMEから)

require 'rubygems'
require 'icalendar'
# Create a calendar with an event (standard method)
cal = Icalendar::Calendar.new
cal.event do
  dtstart       Date.new(2005, 04, 29)
  dtend         Date.new(2005, 04, 28)
  summary       "Meeting with the man."
  description   "Have a long lunch meeting and decide nothing..."
  klass         "PRIVATE"
end

ん〜便利♪
ただ、ちょっといじってたらどうもtimezoneがバグってるみたいです。
timezoneを使ってる状態で、to_icalメソッドを利用すると、エラーを出します。
これも、ちょっと調べたら、パッチがありました。

timezone用パッチ

---[patch begin]---
diff -c /home/trac/ruby/gem/gems/icalendar-0.98/lib/icalendar/component/timezone.rb.org /home/trac/ruby/gem/gems/icalendar-0.98/lib/icalendar/component/timezone.rb
*** /home/trac/ruby/gem/gems/icalendar-0.98/lib/icalendar/component/timezone.rb.org     Wed May  2 13:45:15 2007
--- /home/trac/ruby/gem/gems/icalendar-0.98/lib/icalendar/component/timezone.rb Mon May  7 08:40:32 2007
***************
*** 49,58 ****
      # Also need a custom to_ical because typically it iterates over an array
      # of components.
      def to_ical
!       print_component do |s|
          @components.each_value do |comp|
            s << comp.to_ical
          end
        end
      end

--- 49,60 ----
      # Also need a custom to_ical because typically it iterates over an array
      # of components.
      def to_ical
!       print_component do
!         s = ""
          @components.each_value do |comp|
            s << comp.to_ical
          end
+         s
        end
      end
---[patch end]---

これで、一応to_icalメソッドが通るようにはなるんですけど、

cal = Icaledar::Calendar.new
cal.timezone do
  tzid 'Asia/Tokyo'
  standard do
    tzoffsetfrom '+0900'
    tzoffsetto   '+0900'
    dtstart      '19700101T000000'
    tzname       'JST'
  end
end

という感じで、standardを使っても、一切反応してくれません。なんで・・・
ソース見てもぶっちゃけよくわからないんで、
とりあえず、図書館で借りた本の返却日をGoogle Calendarに反映する その3 - Action!!を参考にtimezone部分を作成。

cal = Icalendar::Calendar.new

# STANDARD コンポーネントを生成
standard_component = Icalendar::Component.new('STANDARD')
standard_component.custom_property('dtstart', '19700101T000000')
standard_component.custom_property('tzoffsetfrom', '+0900')
standard_component.custom_property('tzoffsetto', '+0900')
standard_component.custom_property('tzname', 'JST')

# VTIMEZONE コンポーネントを生成
vtimezone_component = Icalendar::Component.new('VTIMEZONE')
vtimezone_component.custom_property('tzid', 'Asia/Tokyo')
vtimezone_component.add(standard_component)

cal.add(vtimezone_component)

ちなみに、custom_propertyの第一引数は、自動的に大文字になります。
んで、これを使って、iCalendarのサンプルは以下のようになります。

iCalendarのサンプル

require 'rubygems'
require 'icalendar'
require 'kconv'

cal = Icalendar::Calendar.new

# イベントを作成
cal.event do
  dtstart       DateTime.new(2007, 07, 02, 10)
  dtend         DateTime.new(2005, 07, 02, 20)
  summary       "Meeting with the man."
  description   "Have a long lunch meeting and decide nothing..."
  klass         "PRIVATE"
end

# STANDARD コンポーネントを生成
standard_component = Icalendar::Component.new('STANDARD')
standard_component.custom_property('dtstart', '19700101T000000')
standard_component.custom_property('tzoffsetfrom', '+0900')
standard_component.custom_property('tzoffsetto', '+0900')
standard_component.custom_property('tzname', 'JST')

# VTIMEZONE コンポーネントを生成
vtimezone_component = Icalendar::Component.new('VTIMEZONE')
vtimezone_component.custom_property('tzid', 'Asia/Tokyo')
vtimezone_component.add(standard_component)

cal.add(vtimezone_component)

File.open("sample.ics", "w+b") { |f|
    f.write(cal.to_ical.toutf8)
}

というかんじで、基本的なiCalendar形式のファイル(sample.ics)が生成されるんですが、
それをGoogle Calendarにインポートしてみたら、なぜか、時刻がうまくいきません。

そして、いろいろ試した結果、以下のようにしたら、できました。

iCalendarのサンプル改(Google Calendarでインポート可能)

require 'rubygems'
require 'icalendar'
require 'kconv'

cal = Icalendar::Calendar.new

# イベントを作成
cal.event do
  dtstart       DateTime.new(2007, 07, 02, 10), {'TZID' => 'Asia/Tokyo'}  # 変更箇所
  dtend         DateTime.new(2005, 07, 02, 20), {'TZID' => 'Asia/Tokyo'}  # 変更箇所
  summary       "Meeting with the man."
  description   "Have a long lunch meeting and decide nothing..."
  klass         "PRIVATE"
end

# STANDARD コンポーネントを生成
standard_component = Icalendar::Component.new('STANDARD')
standard_component.custom_property('dtstart', '19700101T000000')
standard_component.custom_property('tzoffsetfrom', '+0900')
standard_component.custom_property('tzoffsetto', '+0900')
standard_component.custom_property('tzname', 'JST')

# VTIMEZONE コンポーネントを生成
vtimezone_component = Icalendar::Component.new('VTIMEZONE')
vtimezone_component.custom_property('tzid', 'Asia/Tokyo')
vtimezone_component.add(standard_component)

cal.add(vtimezone_component)

File.open("sample.ics", "w+b") { |f|
    f.write(cal.to_ical.toutf8)
}

という感じです。
どうもDTSTARTとDTENDにも、TZIDを設定することが重要なようです。
つまり、

DTSTART:20070702T100000
DTEND:20070702T200000

ではなく、

DTSTART;TZID=Asia/Tokyo:20070702T100000
DTEND;TZID=Asia/Tokyo:20070702T200000

となってないと、Google Calendarでは認識してくれないみたいです。