snbhsmt_log

ネットワークとコンピュータ、その他いろいろ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Palm Desktop の予定データを iCalendar 形式に変換

Palm Desktop の予定のデータを Google カレンダに持ってく為の変換プログラムを Ruby で書いた。 以前ブログに書いた dbareader.rb が必要。

入力は Palm Desktop の予定をエクスポートした *.dba ファイル。 以下の様に実行して出来た iCalendar 形式のファイルを Google カレンダにインポートする。
$ ./dba2ics foo.dba > foo.ics

自分のデータを変換するためだけに作ったので、 他人のデータも正しく変換できる保証はまったく無い。
そういえば、繰り返し予定の例外に対応していない。 その場合、例外の元と先の 2 件の予定が作成されると思うので、 元の方を削除しなければならない。

dba2ics
#!/usr/bin/ruby
# vi: ts=2

# Palm の予定データ (databook.dat/*.dba) を
# iCalendar (*.ics) フォーマットに変換する。

# 注意: 繰り返しイベントで例外的に変更のある日に未対応。
# この場合、該当する日のイベントが二重に作成される。

$KCODE = 'u'
require 'rubygems'
require 'icalendar'
require 'date'
require 'kconv'

require 'dbareader'

module Icalendar
  class Component < Icalendar::Base
    def escape_chars(value)
      # rrule の value のカンマ、セミコロンのエスケープを抑止する。
      # 注意: rrule の value かどうかを :FREQ で始まるかどうかで判定している。
      if value =~ /^:FREQ/
        value.gsub("\\", "\\\\").gsub("\n", "\\n")
      else
        value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\
\;")
      end
    end
  end
end

class EntryWrapper
  def initialize(event)
    @event = event
  end

  def dtstart
    _dt(@event[:start_time], @event[:untimed])
  end

  def dtend
    _dt(@event[:end_time], @event[:untimed])
  end

  def _dt(time, untimed)
    t = Time.at(time)
    if untimed == 0
      DateTime.new(t.year, t.month, t.day, t.hour, t.min)
    else
      Date.new(t.year, t.month, t.day)
    end
  end

  def summary
    @event[:description].toutf8 unless @event[:description].empty?
  end

  def description
    @event[:note].gsub(/\r\n?/, '\n').toutf8 unless @event[:note].empty?
  end

  def klass
    #@event[:private] == 0 ? "PUBLIC" : "PRIVATE"
    # プライベートじゃない場合は Google カレンダの初期値にするには nil を返す
    @event[:private] == 0 ? nil : "PRIVATE"
  end

  def alarm?
    @event[:alarm_set] == 0 ? false : true
  end

  def alarm_trigger
    return unless alarm?

    units = @event[:alarm_adv_units]
    case @event[:alarm_adv_type]
    when 0 then "-PT#{units}M"
    when 1 then "-PT#{units}H"
    when 2 then "-P#{units}D"
    end
  end

  def repeat?
    @event[:repeat_event_flag] == 0 ? false : true
  end

  WEEK = [ 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ]

  def rrule
    return if @event[:repeat_event_flag] == 0

    s = "FREQ="

    # 繰り返しの種類
    brand = @event[:brand]
    case brand
    when 1 then s << 'DAILY'        # 日次の繰り返し
    when 2 then s << 'WEEKLY'       # 週次の繰り返し
    when 3 then s << 'MONTHLY'      # 月次の曜日指定による繰り返し
    when 4 then s << 'MONTHLY'      # 月次の日にち指定による繰り返し
    when 5 then s << 'YEARLY'       # 年次の繰り返し
    end

    # 繰り返しの周期がデフォルトの 1 以上なら INTERVAL を指定
    s << ";INTERVAL=#{@event[:interval]}" if @event[:interval] > 1

    # 繰り返しの終了日が指定されていたら UNTIL を指定
    if @event[:end_date] != 1956495599
      s << Time.at(@event[:end_date]).strftime(";UNTIL=%Y%m%d")
    end

    # 週の開始がデフォルトの月曜日以外なら WKST を指定
    if @event[:first_day_of_week] != 1
      week = WEEK[@event[:first_day_of_week]]
      s << ";WKST=#{week}"
    end

    # 週次の繰り返しの場合の曜日を BYDAY に指定
    if 2 == brand
      a = []
      (0..6).each do |n|
        a << WEEK[n] if (@event[:days_mask] & (0x01 << n)) != 0
      end
      s << ";BYDAY=" + a.join(',')
    end

    # 月次の曜日指定による繰り返しの場合の第何番目の何曜日かを BYDAY に指定
    if 3 == brand
      week = WEEK[@event[:day_index]]
      s << ";BYDAY=#{@event[:week_index]+1}#{week}"
    end

    # 月次の日にち指定か年次の繰り返しの場合の日付を BYMONTHDAY に指定
    s << ";BYMONTHDAY=#{@event[:day_number]}" if (4..5) === brand

    # 年次の繰り返しの場合の月を BYMONTH に指定
    s << ";BYMONTH=#{@event[:month_index]+1}" if 5 == brand

    s
  end
end


filename = ARGV.shift || exit(63)
dba = Palm::Datebook.read_dba(filename)

cal = Icalendar::Calendar.new

# タイムゾーンを作成
cal.timezone do
  tzid 'Asia/Tokyo'
  standard do
    tzoffsetfrom '+0900'
    tzoffsetto   '+0900'
    dtstart      '19700101T000000'
    tzname       'JST'
  end
end

dba[:datebook_entry].each do |entry|
  entry = EntryWrapper.new(entry)

  # イベント作成
  cal.event do
    dtstart     entry.dtstart
    dtend       entry.dtend
    summary     entry.summary
    description entry.description
    klass       entry.klass
    add_rrule   entry.rrule if entry.repeat?
    if entry.alarm?
      #alarm do
      #  action    "DISPLAY"
      #  trigger   entry.alarm_trigger
      #end
      alarm do
        action    "EMAIL"
        trigger   entry.alarm_trigger
      end
    end
  end # cal.event
end

puts cal.to_ical
スポンサーサイト

« Remember The Milk の iCalendar 出力と Mozilla Sunbird|Top|iCalendar の繰り返し予定の例外 »

コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://snbhsmt.blog110.fc2.com/tb.php/120-36a32ebd

Top

HOME

snbhsmt

Author:snbhsmt
Google Profiles

http://www.ksky.ne.jp/~snbhsmt/

全ての記事を表示する

この人とブロともになる

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。