home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/perl
-
- # Author: Florian Schaefer <florian@netego.de>, April 2002
- # Feel free to distribute this file under the terms of the GPL.
-
- use POSIX qw(strftime);
-
- @uidl="";
- $uidl[0]=0;
-
- #
- # Converts a date from dd/mm/yyyy format to yyyymmdd format
- #
-
- sub create_date
- {
- my ($raw) = @_;
-
- $raw =~ s/^([0-9]*)\/([0-9]*)\/([0-9]*)$/$3/;
- if (length($raw) > 4) {chop($raw);}
- $raw .= sprintf("%02i%02i", $2, $1);
-
- return ($raw);
- }
-
- #
- # Converts a time in minutes since midnight into hhmm format
- #
-
- sub create_time
- {
- my ($mins) = @_;
-
- my $hour = sprintf("%02i",$mins/60);
- my $min = sprintf("%02i",($mins-60*$hour));
-
- return ("$hour$min"."00");
- }
-
- #
- # The function manages to exclude dates on repeated events
- #
-
- sub exclude_dates
- {
- my ($exdate) = @_;
- my $output ="";
- if ($exdate =~ /,$/) {chop($exdate)};
- $output = "EXDATE\n :$exdate\n";
-
- return $output;
- }
-
- #
- # This is the main part: &convert gets an array with the lines
- # of one event and the number of lines. It does all the fancy
- # work.
- #
-
- sub convert
- {
- my ($lines, @raw) = @_;
- my $uid, $cont, $tart, $length;
- my $finishdate = $extrastart = 0;
- my $exdate = "";
- $start = "";
-
- #
- # This loop tries to get all relevant information
- #
- for (my $pos = 0; $pos < $lines; $pos++)
- {
- if ($raw[$pos] =~ /^Finish /)
- {
- #
- # find the finish date
- #
- $finishdate = $raw[$pos];
- $finishdate =~ s/ End//;
- $finishdate =~ s/^Finish (.*)/$1/;
- chop($finishdate);
- $finishdate = &create_date($finishdate);
- }
- if ($raw[$pos] =~ /^Start [0-9]/)
- {
- #
- # an extra start field containing a date
- #
- $extrastart = $raw[$pos];
- $extrastart =~ s/ End//;
- $extrastart =~ s/^Start (.*)/$1/;
- chop($extrastart);
- $extrastart = &create_date($extrastart);
- }
-
- if ($raw[$pos] =~ /^Deleted /)
- {
- #
- # find dates to be excluded from a repeating event
- #
- my $rawdate = $raw[$pos];
- $rawdate =~ s/ End//;
- $rawdate =~ s/Deleted //;
- $exdate .= &create_date($rawdate).",";
- }
-
- #
- # and finally split the info into two pieces and
- # fill the variables, I know there are hashes, but
- # I don't know how to use them...
- #
- $raw[$pos] =~ s/^(.*) \[(.*)$/$1~$2/;
- ($item, $value) = split(/~/, $raw[$pos]);
- $value =~ s/\]$//;
- chop($value);
- if ($item eq "PilotRecordId") {$uid = $value;}
- if ($item eq "Contents") {$cont = $value;}
- if (($item eq "Start") && ($value !=~ /\//)) {$start = $value;}
- if ($item eq "Length") {$length = $value;}
- if ($item eq "Dates") {$date = $value;}
- }
-
- #
- # the header of each event
- #
- my $out ="";
- $out = "BEGIN:VCALENDAR\nVERSION\n :2.0\n";
- $out .= "PRODID\n :PRODID:-//netego.de/NONSGML ical2ics.pl//EN\n";
- $out .= "BEGIN:VEVENT\n";
- $out .= "UID\n :$uid\n";
- $out .= "SUMMARY\n :$cont\n";
- $out .= "CLASS\n :PRIVATE\n";
- $out .= "DESCRIPTION\n :$description\n" if ($description);
-
- #
- # It is a repeated event - something with days
- #
- if (($date =~ /Days/) && (!($date =~ /WeekDays/)))
- {
- my $interval = $date;
- $interval =~ s/^.* ([0-9]*)$/$1/;
-
- if ($date =~ /7$/)
- {
- #
- # 7 days, let's make 1 week out of it
- #
- $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :weeks\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
- $out .= "RRULE\n :";
- $out .= "FREQ=WEEKLY;INTERVAL=1;";
- if ($finishdate) { $out .= "UNTIL=$finishdate\n"; }
- else { chop($out); $out .= "\n"; }
- }
- else
- {
- #
- # seems like a real event for a dayly recurrence
- #
- $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :days\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :$interval\n";
- $out .= "RRULE\n :";
- $out .= "FREQ=DAILY;";
- if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
- $out .= "INTERVAL=$interval\n";
- }
- #
- # don't forget to exclude certain dates
- #
- if ($exdate) {$out .= &exclude_dates($exdate);}
- }
-
- #
- # It is a repeated event - the same with months
- #
- if (($date =~ /Months/) && (!($date =~ /WeekDays/)))
- {
- my $interval = $date;
- $interval =~ s/^.* ([0-9]*)$/$1/;
-
- if ($date =~ /12$/)
- {
- #
- # 12 month make one year
- #
- $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :years\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
- $out .= "RRULE\n :";
- $out .= "FREQ=YEARLY;INTERVAL=1;";
- if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
- my $mon = $date;
- $mon =~ s/^.*\/([0-9]+)\/.*$/$1/;
- $out .= "BYMONTH=$mon\n";
- }
- else
- {
- $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :years\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :$interval\n";
- $out .= "RRULE\n :";
- $out .= "FREQ=MONTHLY;";
- if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
- $out .= "INTERVAL=$interval\n";
- }
- #
- # don't forget to exclude certain dates
- #
- if ($exdate) {$out .= &exclude_dates($exdate);}
- }
-
- #
- # It is a weekly event, but only some days are selected
- #
- if ($date =~ /WeekDays/)
- {
- $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :weeks\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
- $out .= "RRULE\n :";
- $out .= "FREQ=WEEKLY;INTERVAL=1;";
- if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
-
- (my $daynames = $date) =~ s/.*WeekDays (.*) Months.*$/$1/;
- $daynames =~ s/2/MO/;
- $daynames =~ s/3/TU/;
- $daynames =~ s/4/WE/;
- $daynames =~ s/5/TH/;
- $daynames =~ s/6/FR/;
- $daynames =~ s/7/SA/;
- $daynames =~ s/1/SU/;
- $daynames =~ s/ /,/g;
- $out .= "BYDAY=$daynames\n";
-
- #
- # don't forget to exclude certain dates
- #
- if ($exdate) {$out .= &exclude_dates($exdate);}
-
- #
- # The date of the first event is in another field
- #
- $date = $extrastart;
- }
-
- if ($date =~ /\//)
- {
- #
- # Just in case that date isn't converted yet...
- # The format is slightly different from the one required for
- # &create_date, this is why I don't call it here.
- #
- $date =~ s/^.* ([0-9]*)\/([0-9]*)\/([0-9]*).*$/$3/;
- $date .= sprintf("%02i%02i", $2, $1);
- }
-
- if ($start)
- {
- #
- # Now just print the start and end time.
- #
- $out = $out."DTSTART\n :".$date."T".&create_time($start)."\n";
- $out = $out."DTEND\n :".$date."T".&create_time($start+$length)."\n";
- }
- else
- {
- #
- # There is no time for the beginning of the event defined, I assume
- # that the event should last all day.
- #
- $out = $out."DTSTART\n ;VALUE=DATE\n :".$date."\n";
- }
-
- #
- # The RFC requires me to include the time of the object creation
- #
- $date = strftime "%Y%m%dT%H%M%S", localtime;
- $out = $out."DTSTAMP\n :".$date."\n";
-
- $out = $out."END:VEVENT\nEND:VCALENDAR\n";
-
-
- #
- # Remember the UID for later merging
- #
- $uidl[0]++;
- $uidl[$uidl[0]]=$uid;
-
- return ($out);
- }
-
- #
- # This sub takes an older ical file and merges it to the new one.
- # It does this by comparing the stored UID's and adding all entries
- # whose ID hasn't been found. As you can see, the new events have
- # got a higher priority and can delete newer entries from the old file
- #
-
- sub merge_entries
- {
- my ($mergefile) =@_;
- my $out = ""; # this string will be added later
- my $event = ""; # the current event being processed
- my $record = 0; # is true if loop is in an event object
- my $nextoneuid = 0; # marks the line for the UID
- my $uidfound = 0; # is true if UID is already in new file
-
- open MERGEFILE, $mergefile or die "Can't open '$mergefile' for input $!";
- foreach my $line (<MERGEFILE>)
- {
- if ($line =~ /BEGIN:VCALENDAR$/)
- {
- $event = "";
- $record = 1;
- }
- if ($record == 1) {$event.=$line};
- if ($nextoneuid == 1)
- {
- $nextoneuid = 0;
- $uidfound = $line;
- $uidfound =~ s/^ ://;
- chop($uidfound);
- }
- # This way of finding the UID depends on having two lines
- # for field descriptor and value and is therefore Mozilla
- # Calendar specific.
- if ($line =~ /^UID$/) {$nextoneuid=1};
- if ($line =~ /^END:VCALENDAR$/)
- {
- $record = 0;
- my $found = 0;
- for (my $c=1; $c < $uidl[0]+1; $c++)
- {
- $found=1 if ($uidl[$c] eq $uidfound);
- }
- if ($found == 0)
- {
- $out .= $event;
- }
- }
- }
- close (MERGEFILE);
-
- return($out);
- }
-
- #
- # Here the input file is read and each event is then given to
- # &convert for further processing.
- #
-
- sub read_palmfile
- {
- my ($instream, $outstream) = @_;
- if (($instream ne "") && ($instream ne "-"))
- { open STDIN, $instream or die "Can't open '$instream' for input: $!";}
- if ($outstream ne "")
- { open STDOUT, ">$outstream" or die "Can't open '$outstream' for output $!";;}
- my $line = "";
- my $pos = 0;
- my @note;
- foreach $line (<STDIN>)
- {
- if ($line =~ /(Note|Appt)/)
- {
- if ($pos != 0)
- {
- print &convert($pos,@note);
- }
- $pos = 0;
- $note[$pos] = $line;
- $pos++;
- }
- elsif ($pos != 0)
- {
- $note[$pos] = $line;
- $pos++;
- }
- }
- close (INFILE);
- print &convert($pos, @note);
- print &merge_entries($mergefile) if ($mergefile);
- }
-
- #
- # Well, the first sub has to be called somewhere. ;-)
- # Most of the stuff below is just option parsing.
- #
-
- if (($ARGV[0] eq "--help") || ($ARGV[0] eq "-h"))
- {
- print << "EOF"
-
- Usage: ical2ics.pl [-d STRING] [-m mergefile] [infile] [outfile]
-
- This little script tries to convert ical calender files produced by read-ical
- to a rfc2445 compliant format which can be read by the Mozilla Calendar.
-
- If no filename (or '-' for infile) is given STDIN respectively STDOUT are used.
-
- -d STRING Change default description to STRING
- -m mergefile Merge the outfile with mergefile (the syntax has to be
- similar to the one created by this script), may be the
- outfile itself
-
- Please report bugs or send comments to <florian\@netego.de>.
- EOF
- }
- else
- {
- $description = "Imported from Palm Pilot.";
- my $infile = $outfile = ""; # names of the streams
- my $infilenum = 0; # position of infile in ARGV
- my $outfilenum = 1; # the same for outfile
- my $argpos = 0; # pointer to ARGV position
-
- # I hope that this option-vodoo works reliably
- while ($ARGV[$argpos] ne "")
- {
- if ($ARGV[$argpos] eq "-d")
- {
- $argpos++;
- $description = $ARGV[$argpos];
- $infilenum += 2;
- $outfilenum += 2;
- }
- elsif ($ARGV[$argpos] eq "-m")
- {
- $argpos++;
- $mergefile = $ARGV[$argpos];
- $infilenum += 2;
- $outfilenum += 2;
- }
- else {$argpos++};
- }
- $infile = $ARGV[$infilenum];
- $outfile = $ARGV[$outfilenum];
-
- if ($mergefile)
- {
- # mergefile and outfile mustn't be the same, work
- # with a backup copy instead
- my $rnd = int(rand(100000));
- $status = `cp $mergefile /tmp/ical2ics-$rnd.tmp`;
- $mergefile="/tmp/ical2ics-$rnd.tmp";
- }
-
- &read_palmfile($infile,$outfile,$mergefile);
-
- unlink $mergefile;
- }
-
- # ChangeLog:
- #
- # 25.07.2002:
- # - Switched from the old "X ;MEMBER" fields to the new "X-MOZILLA" properties
- # - Events that last all day don't use "MEMBER=AllDay" any more, I just give a
- # DTSTART date and no DTEND
- #
- # 24.05.2002:
- # - New option for merging ('-d') with existing data
- #
- # 13.05.2002:
- # - Added DTSTAMP property
- #
- # 25.04.2002:
- # - The bug in libical and Calendar concerning the EXDATE property have been
- # fixed, therefore I removed the RECURRENCE-ID alternative.
- # - Added the -d option
- #
- # 24.04.2002:
- # - First official release
-