#!/usr/bin/perl # iCalendar(.ics) 形式を vCalendar(.vcs) 形式に変換する use Jcode ; use Time::Local ; use MIME::QuotedPrint ; require "getopts.pl" ; Getopts( 'n:d' ) ; my $max = ($opt_n ne "" ? $opt_n : 50 ) ; my $count = 0 ; # VEVENT 内外の判断用 my %event = () ; # VEVENT 1項目保存用 my %vcal = () ; my @events = () ; # ローカル時間の iCal 形式を GMT 時間に変換する sub local_ical( $ ) { local( $str ) = @_ ; my $time ; if ( $str =~ /^(\d\d\d\d)(\d\d)(\d\d)$/ ) { # 終日イベント $time = timelocal( 0 , 0 , 0 , $3 , $2 - 1 , $1 - 1900 ) ; } elsif ( $str =~ /^(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)(Z|)$/ ) { # 時間指定イベント if ( $7 eq "Z" ) { # GMT イベント return timegm( $6 , $5 , $4 , $3 , $2 - 1 , $1 - 1900 ) ; } else { # ローカル時間イベント $time = timelocal( $6 , $5 , $4 , $3 , $2 - 1 , $1 - 1900 ) ; } } return $time ; } # 時間を iCal の GMT 形式に変換する sub gm_ical( $ ) { local( $time ) = @_ ; my @time = gmtime( $time ) ; return sprintf( "%04d%02d%02dT%02d%02d%02dZ" , $time[ 5 ] + 1900 , $time[ 4 ] + 1 , $time[ 3 ] , $time[ 2 ] , $time[ 1 ] , $time[ 0 ] ) ; } # SUMMARY から CATEGORIES を生成する sub categories( $ ) { local( $str ) = @_ ; if ( $str =~ /(会議|委員会)/ ) { return "MEETING" ; } elsif ( $str =~ /(研修|仕事)/ ) { return "BUSINESS" ; } elsif ( $str =~ /(誕生日|記念日)/ ) { return "PERSONAL" ; } elsif ( $str =~ /(代休|休暇|休み|休日)/ ) { return "HOLIDAY" ; } elsif ( $str =~ /(出張|旅行)/ ) { return "TRAVEL" ; } else { return "MISCELLANEOUS" ; } } # 項目を Quoted printable の出力形式の変換 sub enc_qp( $$ ) { local( $ev , $key ) = @_ ; my $str = $ev->{$key} ; if ( $str ne "" ) { $str = encode_qp( Jcode->new( $str )->sjis ) ; $str =~ s/[\r\n]+$// ; $str =~ s/=$// ; return "$key;ENCODING=QUOTED-PRINTABLE:$str\r\n" ; } else { return "" ; } } # vCalendar の1項目を出力 sub vevent( $ ) { local( $ev ) = @_ ; return "BEGIN:VEVENT\r\n" # 時間 ."DTSTART:".gm_ical( $ev->{'DTSTART'} )."\r\n" ."DTEND:".gm_ical( $ev->{'DTEND'} )."\r\n" # 項目説明 .enc_qp( $ev , 'SUMMARY' ) .enc_qp( $ev , 'DESCRIPTION' ) .enc_qp( $ev , 'LOCATION' ) # その他 ."CATEGORIES:".categories($ev->{'SUMMARY'})."\r\n" ."CLASS:PUBLIC\r\n" .($ev->{'LAST-MODIFIED'} ne "" ? "LAST-MODIFIED:".$ev->{'LAST-MODIFIED'}."\r\n" : "") # 項目区切り ."END:VEVENT\r\n" ; } # iCalendar ファイルを読み込み、指定時間以降のイベントを抽出 sub vcalendar( $$ ) { local( $file , $now ) = @_ ; if ( -f $file ) { open( VC , "$file" ) || die( "Can't open vcalendar file" ) ; while( ) { $line = $_ ; # VEVENT フィールドの開始 if ( $line =~ /^BEGIN:VEVENT\r*$/ ) { %event = () ; $count++ ; } if ( $count > 0 ) { # VEVENT フィールド内部の処理 if ( $line =~ /^(DTSTART|DTEND);(VALUE=DATE;|)TZID=([^:]*):([0-9TZ]*)\r*$/ ) { $event{$1} = local_ical( $4 ) ; } elsif ( $line =~ /^(SUMMARY|DESCRIPTION|LOCATION):(.*)\r*$/ ) { my $str = $2 , $key = $1 ; $str =~ s/[\r\n]+$// ; $event{$key} = Jcode->new( $str )->euc ; } elsif ( $line =~ /^LAST-MODIFIED:(.*)\r*$/ ) { $event{'LAST-MODIFIED'} = $1 ; $event{'LAST-MODIFIED'} =~ s/[\r\n]+$// ; } } else { # VEVENT 外部の項目保存 if ( $line =~ /^(PRODID|VERSION):(.*)\r*$/ ) { $vcal{$1} = $2 ; } } # VEVENT フィールドの終わり if ( $line =~ /^END:VEVENT\r*$/ ) { $count-- ; # 指定時間以降のイベントだけを変換出力 if ( $event{'DTSTART'} > $now - 24*3600 ) { push( @events , {%event} ) ; } } } } } # 指定時間以降のイベントの読み込み my $now = time() ; while( @ARGV > 0 ) { vcalendar( shift( @ARGV ) , $now ) ; } # VEVENT 情報の出力(イベント順で指定件数以内) print "BEGIN:VCALENDAR\r\n" ."VERSION:1.0\r\n" ."PRODID:".$vcal{'PRODID'}."\r\n" ; my $c = 0 ; foreach $ev ( sort { $a->{'DTSTART'} <=> $b->{'DTSTART'} } @events ) { print vevent( $ev ) ; last if ( ++$c >= $max ) ; } print "END:VCALENDAR\r\n" ; ### Local Variables: ### ### mode: perl ### ### tab-width: 4 ### ### End: ###