トップ «前の日記(2008-11-30) 最新 次の日記(2008-12-04)» 編集

2008-12-02 [長年日記]

_ RotationMaker を作った

最近は iCal づいています。

/lang/ruby/misc/rotation-maker – CodeRepos::Share – Trac

これは何か?

README より抜粋

  • 例えば毎週月曜に何かやります
  • メンバーが 5人いて担当は 2人ずつとします
  • いつ誰が担当するのか先々の予定を算出したい
  • ハッピーマンデーがあるので、その場合は火曜にスライド

みたいなときに便利かもしれないツールです。

使い方

オプションを与えずに起動するとなんとなく分かります。たぶん。

$ ruby rotation-maker.rb
Usage: rotation-maker [options]
    -x, --exclude DATE
        --exclude-from-ical ICS
        --exclude-from-file FILE
        --exmethod [METHOD]
    -s, --dtstart DATE
    -e, --dtend DATE
    -i, --interval INTERVAL
    -d, --date EVENT DATE
        --date-from-ical ICS
        --date-from-file FILE
    -m, --member MEMBER
        --member-from-file FILE
    -a, --atonce NUMBER
    -y, --yaml FILE

最低限必要なものは

  • date
  • member

です。-d や -m を複数書いてもいいですが、それぞれ1行1アイテム形式のファイルから読み込ませることもできます。

また、date ではなく -s START, -e END, -i INTERVAL でくり返しの予定を記述することもできます。INTERVAL は基本的に整数ですが、activesupport をインストールしてあれば week, month, year も指定できます。

exclude で除外する日付を指定します。デフォルトでは除外日に予定が当たったらその日はスキップします。exmethod オプションに backload を与えていたら次の日にずらします。次の日も除外日だったら除外日でなくなるまでスライドします。*1

あ、今気づいたけど slide オプションは YAML からしか与えられないじゃないか。後で直します。

YAML で設定を書く場合はこんな感じで書きます。

dtstart: '2008-12-01'
dtend: '2009-03-31'
interval: week
atonce: 2
exmethod: backload
members:
  - abc
  - def
  - ghi
  - jkl
  - mno
  - pqr
  - stu
excludes:
  # PATH or URI
  ical: http://www.google.com/calendar/ical/japanese%40holiday.calendar.google.com/public/basic.ics

出力部分は自分で書いてください^^;

例えばこんな感じのものを用意すると iCal 形式で予定を吐き出せます。

#! /usr/bin/env ruby
# -*- coding: utf-8 -*-

require File.dirname( __FILE__ ) + '/rotation-maker'
require 'erb'
require 'nkf'

def main
  rot = RotationMaker.new.run
  puts NKF::nkf( '-w -Lw', ERB.new( template, nil, '-' ).result( binding ) )
end

def serialized_time( time )
  return time.gsub( /-/, '' )
end

def template
  return <<EOD
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Asia/Tokyo
X-LIC-LOCATION:Asia/Tokyo
BEGIN:STANDARD
TZOFFSETFROM:+0900
TZOFFSETTO:+0900
TZNAME:JST
DTSTART:19700101T000000
END:STANDARD
END:VTIMEZONE
<%- rot.each do |r| -%>
BEGIN:VEVENT
SUMMARY:社内勉強会
DESCRIPTION:発表 <%= r['member'].join( ', ' ) %>
DTSTART;TZID=Asia/Tokyo:<%= serialized_time( r['date'] ) %>T180000
DTEND;TZID=Asia/Tokyo:<%= serialized_time( r['date'] ) %>T190000
END:VEVENT
<%- end -%>
END:VCALENDAR
EOD
end

main

結果はこんな感じ。

ハッピーマンデーを除けてスケジューリングできた様子

あ。あれ? Google Calendar にある Japanese Holidays の成人の日の情報が

DTSTART;VALUE=DATE:20090112
DTEND;VALUE=DATE:20090113
DTSTAMP:20081203T115909Z

になってますね。見た目は 12日だけに見えるんだけど。えーまぁ、これはデータの問題ということで。

問題というか

iCal の Rrule *2って、くり返しの終了が指定されていない場合、無限に続いてしまいますよね? これをどう扱ったらいいのかが全然分かっていません。今回、除外日を設定できるようにしたため、除外日を Array にして Array#include? でチェックしているのですが、Array で無限なんて扱えないですよね。

実際には Vpim::Icalendar がイベントの日時情報を DateTime ではなく Time で扱っているため、2037年問題に引っかかって有限の配列を取得することができているのですが、本当に無限のものを扱いたい場合は全部を計算で扱う Array 以外の何かが必要になるんじゃないかと思っています。とにかく動くものを作りたかったのでその辺突っ込んで調べてませんが。

またこのため Time の ArgumentError を rescue してそのまま知らん顔するという暴挙に出ています。こんなんじゃいかんよなぁ。limit を設けるようにした方がいいのか、Icalendar に戻した方がいいのか。Icalendar は DateTime で扱っているので少なくとも 2037年問題には引っかからなそうなのですが。Icalendar にこの include?( date ) みたいな便利メソッドがあれば Array に展開することなく処理を続けられるのかな。ただ、実際にはどこかで区切らないと処理が終わらないのでどっちみち limit は要るのか。

Icalendar は Icalendar で to_ical がマルチバイト文字列をぶった切る問題があるしなぁ。ふーむ悩ましい。

何かご存知の方はツッコミください。

TODO とか

  • slide オプションをコマンドラインからも与えられるようにする
  • slide オプションで予定の前倒しもできるようにする
  • 土日を除外するのはもっと簡単にできてもいいような気がする

げ。

*atonce をコマンドラインから与えられるようにする><

参考

2008-12-04 追記

前のエントリのツールも一緒に checkout できるようにしました。手軽に cat できるなら月水金カレンダーとかでも割とすぐ作れるので、こっちで頑張りすぎる必要ないかなぁという気がしてきました。

Tags: Ruby iCal

*1 年末年始分、丸ごとスライドさせることもできます。

*2 例えば毎週月曜といった形で設定されたイベント