iCalからGoogle Calendarへ同期をとるツール書いた

wtnabe/ical2gcal - GitHub

何ができるか

  • iCalendar 形式のデータを指定の Google Calendar へ同期させる1
  • 複数の iCalendar データを一つの Google Calendar にまとめることができる

なぜ必要か

  • Android の少なくとも 2.3 までのカレンダーは「照会」を使えないから
  • 個人的には Google Calendar は使っていないが「照会」したいカレンダーがいくつもあり、それらをいちいち手作業でインポートしたりせずに Android 上で確認したいから

どうやって動かすか

準備するもの

  • Ruby
  • gem install ical2gcal
  • googleアカウント
  • 同期をとるカレンダーのURL(またはパス)
  • 同期先のカレンダーとその名前

あとは

ical2gcal –help でなんとなく察して。

使用例

こんなことができます。

  • RTM のプライベートアドレスから Google Calendar への同期を明示的に行える
    • Google Calendar で直接参照すると反映がとても遅く、使いものにならない
  • Trac の milestone の締め日を iCalendar 形式で取得して Google Calendar 上で確認可能

その他、URL やパスのある iCalendar 形式のデータであればなんでも Google Calendar と同期できる。

自分の場合はこれを自宅サーバや社内のサーバで cron で定期実行して Android のカレンダー上で気にしなければいけない日時を俯瞰できるようにしている。

制限

  • 「1実行 : 1Google Calendar」の対応なので、同期したいカレンダーが複数ある場合は何回も実行しなければいけない

ただ、このおかげで同期頻度を調整もしやすいので、これでいいかなと思っている2

できあがった Google Calendar を Android 端末で利用する際の問題

このツールを使って Google Calendar を別なツールやプログラムから更新することができる。しかしこれを Android で同期しようとすると問題になる場合がある。

どうも Android のカレンダープログラムは外部のカレンダーデータについては Google Calendar との同期しかできないのにその同期が正しく動作しないというバグがあるらしい。

手元で確認したおかしな動作は以下の通り。

  1. Google Calendar に正しく反映されていても Android のカレンダープログラム上で再現されない
  2. そもそも Mobile の Google Calendar は終日のイベントを表示してくれない

2 については Google のサーバ側の挙動なのでどうにもならないけれど 1 については

  • 同期に失敗しているカレンダーを Android 上で削除
  • いったん更新(同期)して
  • 再度同期したいカレンダーを追加

すると正しく反映されるようだ。こちらのアプリだとダメであちらのアプリだと大丈夫、という話もフォーラムなどでは見かけるが、手元の環境では一切そういうことはなかった。

とにかく

削除 -> 追加

を行う。

Andorid 上の削除は Google Calendar からの削除にはならず、あくまで Android とのリンクを切る、という感じで動作するらしい。

しかもこの問題、必ず反映されなくなるかというとそうでもない、とてもいやらしい発現具合。結局、

終日のイベントが表示されないのを分かったうえでブラウザで Google Calendar を開くのがいちばん確実

という状態。

しかし、かなり有名なバグっぽいんだけど全然直ってないのね。

cf.

開発時に気づいたこと

  1. Ruby で書いたわけだが、意外なことに Google Calendar 上の event を削除できないライブラリが多い
  2. 必ずしも event の削除に成功するわけではない
  3. 全イベントを削除するのが面倒、あるいはできないからってカレンダーを削除して作り直しちゃダメ3

2 については実は今も解決はしていない。個人的には cron で定期実行しているうちになんとなく解決するので良しとしている4

1 については一度 event を削除できないライブラリを採用して作り始めてしまい、仕方なく googlecl という Python でできたコマンドラインで Calendar などを管理できるツールで削除を行ってみたがこれを cron で自動実行することができなかった。5

結局ライブラリを置き換えて Google Calendar へ投げる部分はすべて作り直した。また iCal のライブラリでパースして Google Calendar のライブラリでイベントを作成するわけだけど、

  • Google Calendar ライブラリが end time を必要とするが元の iCal が終日イベントの場合に end time がないことがある
  • Google Calendar ライブラリには「終日」を明示するメソッドがあるが iCal をパースするライブラリにはこれを取得する方法がない6
  • DateTime オブジェクトと Time オブジェクトの違い
  • timezone が合わない7

など、実際にいじってみて初めて知った細かい差異にいちいち引っ掛かった。

課題

API 呼び出しについてはテストがなんにもない。まさにレガシーコード。

当初は Google Calendar の情報を解釈するのに標準の rss/atom モジュールを使おうかと思っていたんだけど、これで Atom を解釈する例が見つからなかったので simple-rss を利用している。標準のライブラリが Atom に対応していなかった時代が続いてるうちに gem のライブラリで処理するのがフツーになってしまったようだ。ちょっと残念。まぁ依存も少なくていいんだけど、少しだけ標準添付のライブラリで処理できた方が嬉しいなと思った。

まとめ

Twitter でも結構長いこと不満を並べていたが、これでようやく RTM を起点にカレンダーで予定を俯瞰できるようになった。Android と Google Calendar との同期についての不満は解消されていないが、時が解決するかブラウザでの閲覧にだんだん慣れていくか、呼吸するようにカレンダーの削除 -> 追加が行えるようになるかのいずれかだろうと思っている。

ちなみに、今カレンダーアプリは

Calendar Pad - Android マーケット

を使っている。もっと有名なものもあるんだけど iOS のカレンダーのように一覧上である程度情報が見えて、Evo の画面をちゃんと使い切るアプリがあまりなくてこれになった。ただし、カレンダーの追加/削除にはどうしても標準の「カレンダー」を使わなくちゃダメで、かつ最新の状態はブラウザで見るのが確実なので、結局

  • ホーム画面に Google Calendar のブックマークを置く
  • ホーム画面にフォルダを作って今挙げた二つのカレンダーを登録

という、なんだかなーという状態になっている。

  1. 1回だけインポートできるツールはいくつかあるけど、このツールなら何回でも同期させられる 

  2. 例えば毎日同期をとるもの、毎時同期をとるもの、など。 

  3. IDが変わり、URIが変わるので「照会」や「同期」している側で認識できなくなる 

  4. 真面目に対応するならエラーがゼロになるまで retry した方がいいんだろうけど、ダメなときはしばらくダメだったりするので対応は難しいんじゃないかなとなんとなく思っている。 

  5. 削除時に y/N で確認を求められるが、これをうまく制御できなかった。cron でなく手で起動した場合には動作したが、それでは自分の目的を達成できなかった。 

  6. 開始時刻が 0:00 で終了時刻がない場合に終日と判断するようにした。 

  7. 無理矢理削って処理したらlocaltimeになった 

More

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Develop Document Jekyll