[開発] Google CalendarのAtomフィードをiCalに変換
Google Calendarの出力するiCalをつかってスケジュールをiPodでみられるようにしたいのですが,
メイビィの戯言さんの記事
やBLOGKIDさんの記事
などで指摘されているように,現時点ではGoogle CalendarのiCal出力は日本語が抜け落ちてしまいます.
そのうち改善されると思うのですが..
Atomフィードのほうにはきちんと日本語が出力されているので,AtomからiCal形式に変換してやることにしました.
書いたのは下のrubyスクリプト.飽くまでGoogleさんが日本語対応してくれるまでの応急処置なので,軽い気持ちで書いたけど..レポート一本分+αくらいの時間が費やされた気がする.
#!/usr/bin/ruby # $Id: gcalatom2ical.rb 119 2006-06-18 05:06:32Z mazmura $ # Google Calendar のAtomフィードをiCalに # # * 能書き # - Google Calendarの出力するAtomフィードをiCalに変換 # - 最後の実行日時をホームディレクトリに記録し,次回実行時は変更点のみicsファイルに落とす # # * 開発時環境 # - ruby 1.8.4 (2005-12-24) [i386-cygwin] # # * インストール&実行 # (1) rubygems をインストール # (2a) $ gem install atom # (2b) $ gem install vpim # (3) このファイルの[1] 設定部分を修正 # (4) $ ruby gcalatom2ical.rb # (5) $target_dirにicsファイルが作成される # # * 既知の不具合/不都合 # - 「繰り返し」形式の予定は無視される # - 25件以上新しいエントリがある場合に,それを超える分のエントリは無視される # -- 大量に取得したければAtomフィードURLの後ろに ?max-results=150 を加えるなどして対処 $KCODE = 'utf-8' require 'rubygems' require 'atom' require 'tmpdir' require 'vpim/icalendar' require 'logger' require 'yaml' # [1] 設定部分(グローバル変数) begin # Google Calendar の XML(Atomフィード) アドレス $urls = [ 'http://www.google.com/calendar/feeds/matzun@gmail.com/private-xxxxxxxxxxx/basic', # Main 'http://www.google.com/calendar/feeds/xxxxxxxxxxx@group.calendar.google.com/private-xxxxxxxxxxx/basic', # Events 'http://www.google.com/calendar/feeds/xxxxxxxxxxx@group.calendar.google.com/private-xxxxxxxxxxx/basic', # Misc 'http://www.google.com/calendar/feeds/xxxxxxxxxxx@group.calendar.google.com/private-xxxxxxxxxxx/basic', # Nayuta 'http://www.google.com/calendar/feeds/xxxxxxxxxxx@group.calendar.google.com/private-xxxxxxxxxxx/basic' # 就活 ] # iCal形式ファイルの出力先ディレクトリ #$target_dir = "." $target_dir = "/cygdrive/e/Calendars" # ログ $log = Logger.new(STDOUT) $log.level = Logger::INFO end # [2] クラス定義 begin # 最後の実行日時をURLごとに記録するためのクラス class LastRunLog @@log_file = ENV['HOME'] + "/.gcalatom2ical.lrlog" def self.instance if File.exists?(@@log_file) then ret = nil File.open(@@log_file, "r") {|file| ret = YAML.load(file)} return ret else return LastRunLog.new end end def initialize @last_times = Hash.new end attr_accessor(:last_times) def store File.open(@@log_file, "w") {|file| YAML.dump(self, file)} end end # ひとつのカレンダー単位 class CalendarUnit @@re_both = /^(\d\d\d\d)\-(\d?\d)\-(\d?\d)( (\d?\d):(\d?\d):(\d?\d))?$/ @@re_time = /^(\d?\d):(\d?\d):(\d?\d)$/ @@re_content = /When: ([0-9\-: ]+)to([0-9\-: ]+).+<br>(Duration:.+<br>)?(Where: (.+)<br>)?(Event Status: [A-Z]+<br>)?(Event Description:(.+))?/ def initialize(url, id) @url = url @id = id @path_atom = Dir.tmpdir + "/atom_#{id}.xml" end # Atomフィードをローカルにダウンロード def download `wget -O #{@path_atom} #{@url}` return self end def parse_time(base_date, str) if @@re_both =~ str then return Time.gm($1, $2, $3, $5, $6, $7) elsif @@re_time =~ str then raise "Base date must be specified: #{str}" if base_date == nil return Time.gm(base_date.year, base_date.month, base_date.day, $1, $2, $3) end $log.warn("Parse error: '#{str}'") return nil end # AtomフィードをiCal形式に変換 def convert(last_time = nil) @feed = Atom::Feed.new(IO.read(@path_atom)) @cal = Vpim::Icalendar.create @feed.entries.each { |entry| # 更新日時(entry.updated)が最後のチェック日時(last_time)以前ならスキップ puts("entry.updated = #{entry.updated}, last_time = #{last_time}") if last_time != nil then if entry.updated <= last_time then $log.info("Skip..") next end end # スケジュールの内容をパース if (@@re_content =~ entry.content.value) != 0 then $log.warn("Ignored entry(illegal content): content = '#{entry.content.value}'") next end @cal.add_event {|event| dtstart = parse_time(nil, $1.strip) if dtstart == nil then $log.warn("Ignored entry(illegal date): content = '#{entry.content.value}'") else event.dtstart(dtstart) event.dtend(parse_time(dtstart, $2.strip)) event.summary(entry.title) event.description($8) if $8 != nil end } } return self end # iCalを指定ディレクトリに出力 def output(dir) t = Time.now.strftime("%Y%m%d_%H%M%S") File.open(dir + "/out_#{t}_#{@id}.ics", "w") {|file| file.puts(@cal)} return self end def clean `rm -f #{@path_atom}` end end end # [3] メインの処理 begin lrlog = LastRunLog.instance cnt = 0 $urls.map {|url| $log.info("ID:#{cnt} started...") unit = CalendarUnit.new(url, cnt) unit.download unit.convert(lrlog.last_times[url]) unit.output($target_dir) #unit.clean lrlog.last_times[url] = Time.now cnt += 1 } lrlog.store end
もう少し再利用を考えるならPlaggerなどでやると吉?
Cygwinのrubyを起動する次のようなバッチファイルをつくって,クリック一つでiPodと同期.
@echo off REM $Id: gcalatom2ical.bat 118 2006-06-17 17:44:10Z mazmura $ set CYGWIN=ntsec set HOME=/home/mazmura set MAKE_MODE=UNIX set SHELL=/bin/bash C: chdir C:\Cygwin\bin bash --login -i -c 'ruby /home/mazmura/workspaces/scripts/misc/gcalatom2ical.rb'
Google Calendarの正式な日本語対応が待ち遠しい..