home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / other-os / ical2ics.pl < prev    next >
Perl Script  |  2004-01-06  |  11KB  |  465 lines

  1. #!/usr/bin/perl
  2.  
  3. # Author: Florian Schaefer <florian@netego.de>, April 2002
  4. # Feel free to distribute this file under the terms of the GPL.
  5.  
  6. use POSIX qw(strftime);
  7.  
  8. @uidl="";
  9. $uidl[0]=0;
  10.  
  11. #
  12. # Converts a date from dd/mm/yyyy format to yyyymmdd format
  13.  
  14. sub create_date
  15. {
  16.     my ($raw) = @_;
  17.  
  18.     $raw =~ s/^([0-9]*)\/([0-9]*)\/([0-9]*)$/$3/;
  19.     if (length($raw) > 4) {chop($raw);}
  20.     $raw .= sprintf("%02i%02i", $2, $1);
  21.  
  22.     return ($raw);
  23. }
  24.  
  25. #
  26. # Converts a time in minutes since midnight into hhmm format
  27.  
  28. sub create_time
  29. {
  30.     my ($mins) = @_;
  31.  
  32.     my $hour = sprintf("%02i",$mins/60);
  33.     my $min = sprintf("%02i",($mins-60*$hour));
  34.  
  35.     return ("$hour$min"."00");
  36. }
  37.  
  38. #
  39. # The function manages to exclude dates on repeated events
  40. #
  41.  
  42. sub exclude_dates
  43. {
  44.     my ($exdate) = @_;
  45.     my $output ="";
  46.     if ($exdate =~ /,$/) {chop($exdate)};
  47.     $output = "EXDATE\n :$exdate\n";
  48.  
  49.     return $output;
  50. }
  51.  
  52. #
  53. # This is the main part: &convert gets an array with the lines
  54. # of one event and the number of lines. It does all the fancy
  55. # work.
  56. #
  57.  
  58. sub convert
  59. {
  60.     my ($lines, @raw) = @_;
  61.     my $uid, $cont, $tart, $length;
  62.     my $finishdate = $extrastart = 0;
  63.     my $exdate = "";
  64.     $start = "";
  65.  
  66.     # 
  67.     # This loop tries to get all relevant information
  68.     #
  69.     for (my $pos = 0; $pos < $lines; $pos++)
  70.     {
  71.         if ($raw[$pos] =~ /^Finish /)
  72.         {
  73.             # 
  74.             # find the finish date
  75.             #
  76.             $finishdate = $raw[$pos];
  77.             $finishdate =~ s/ End//;
  78.             $finishdate =~ s/^Finish (.*)/$1/;
  79.             chop($finishdate);
  80.             $finishdate = &create_date($finishdate);
  81.         }
  82.         if ($raw[$pos] =~ /^Start [0-9]/)
  83.         {
  84.             # 
  85.             # an extra start field containing a date
  86.             #
  87.             $extrastart = $raw[$pos];
  88.             $extrastart =~ s/ End//;
  89.             $extrastart =~ s/^Start (.*)/$1/;
  90.             chop($extrastart);
  91.             $extrastart = &create_date($extrastart);
  92.         }
  93.  
  94.         if ($raw[$pos] =~ /^Deleted /)
  95.         {
  96.             #
  97.             # find dates to be excluded from a repeating event
  98.             #
  99.             my $rawdate = $raw[$pos];
  100.             $rawdate =~ s/ End//;
  101.             $rawdate =~ s/Deleted //;
  102.             $exdate .= &create_date($rawdate).",";
  103.         }
  104.         
  105.         #
  106.         # and finally split the info into two pieces and
  107.         # fill the variables, I know there are hashes, but
  108.         # I don't know how to use them...
  109.         #
  110.         $raw[$pos] =~ s/^(.*) \[(.*)$/$1~$2/;
  111.         ($item, $value) = split(/~/, $raw[$pos]);
  112.         $value =~ s/\]$//;
  113.         chop($value);
  114.         if ($item eq "PilotRecordId") {$uid = $value;}
  115.         if ($item eq "Contents") {$cont = $value;}
  116.         if (($item eq "Start") && ($value !=~ /\//)) {$start = $value;}
  117.         if ($item eq "Length") {$length = $value;}
  118.         if ($item eq "Dates") {$date = $value;}
  119.     }
  120.  
  121.     #
  122.     # the header of each event
  123.     # 
  124.     my $out ="";
  125.     $out  = "BEGIN:VCALENDAR\nVERSION\n :2.0\n";
  126.     $out .= "PRODID\n :PRODID:-//netego.de/NONSGML ical2ics.pl//EN\n";
  127.     $out .= "BEGIN:VEVENT\n";
  128.     $out .= "UID\n :$uid\n";
  129.     $out .= "SUMMARY\n :$cont\n";
  130.     $out .= "CLASS\n :PRIVATE\n";
  131.     $out .= "DESCRIPTION\n :$description\n" if ($description);
  132.  
  133.     #
  134.     # It is a repeated event - something with days
  135.     #
  136.     if (($date =~ /Days/) && (!($date =~ /WeekDays/)))
  137.     {
  138.         my $interval = $date;
  139.         $interval =~ s/^.* ([0-9]*)$/$1/;
  140.             
  141.         if ($date =~ /7$/)
  142.         {
  143.             #
  144.             # 7 days, let's make 1 week out of it
  145.             #
  146.             $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :weeks\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
  147.             $out .= "RRULE\n :";
  148.             $out .= "FREQ=WEEKLY;INTERVAL=1;";
  149.             if ($finishdate) { $out .= "UNTIL=$finishdate\n"; }
  150.             else { chop($out); $out .= "\n"; }
  151.         }
  152.         else
  153.         {
  154.             #
  155.             # seems like a real event for a dayly recurrence
  156.             #
  157.             $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :days\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :$interval\n";
  158.             $out .= "RRULE\n :";
  159.             $out .= "FREQ=DAILY;";
  160.             if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
  161.             $out .= "INTERVAL=$interval\n";
  162.         }
  163.         #
  164.         # don't forget to exclude certain dates
  165.         #
  166.         if ($exdate) {$out .= &exclude_dates($exdate);}
  167.     }
  168.  
  169.     #
  170.     # It is a repeated event - the same with months
  171.     #
  172.     if (($date =~ /Months/) && (!($date =~ /WeekDays/)))
  173.     {
  174.         my $interval = $date;
  175.         $interval =~ s/^.* ([0-9]*)$/$1/;
  176.             
  177.         if ($date =~ /12$/)
  178.         {
  179.             #
  180.             # 12 month make one year
  181.             #
  182.             $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :years\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
  183.             $out .= "RRULE\n :";
  184.             $out .= "FREQ=YEARLY;INTERVAL=1;";
  185.             if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
  186.             my $mon = $date;
  187.             $mon =~ s/^.*\/([0-9]+)\/.*$/$1/;
  188.             $out .= "BYMONTH=$mon\n";
  189.         }
  190.         else
  191.         {
  192.             $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :years\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :$interval\n";
  193.             $out .= "RRULE\n :";
  194.             $out .= "FREQ=MONTHLY;";
  195.             if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
  196.             $out .= "INTERVAL=$interval\n";
  197.         }
  198.         #
  199.         # don't forget to exclude certain dates
  200.         #
  201.         if ($exdate) {$out .= &exclude_dates($exdate);}
  202.     }
  203.     
  204.     #
  205.     # It is a weekly event, but only some days are selected
  206.     #
  207.     if ($date =~ /WeekDays/)
  208.     {
  209.         $out .= "X-MOZILLA-RECUR-DEFAULT-UNITS\n :weeks\nX-MOZILLA-RECUR-DEFAULT-INTERVAL\n :1\n";
  210.         $out .= "RRULE\n :";
  211.         $out .= "FREQ=WEEKLY;INTERVAL=1;";
  212.         if ($finishdate) { $out .= "UNTIL=$finishdate;"; }
  213.         
  214.         (my $daynames = $date) =~ s/.*WeekDays  (.*) Months.*$/$1/;
  215.         $daynames =~ s/2/MO/;
  216.         $daynames =~ s/3/TU/;
  217.         $daynames =~ s/4/WE/;
  218.         $daynames =~ s/5/TH/;
  219.         $daynames =~ s/6/FR/;
  220.         $daynames =~ s/7/SA/;
  221.         $daynames =~ s/1/SU/;
  222.         $daynames =~ s/ /,/g;
  223.         $out .= "BYDAY=$daynames\n";
  224.             
  225.         #
  226.         # don't forget to exclude certain dates
  227.         #
  228.         if ($exdate) {$out .= &exclude_dates($exdate);}
  229.  
  230.         #
  231.         # The date of the first event is in another field
  232.         #
  233.         $date = $extrastart;
  234.     }
  235.     
  236.     if ($date =~ /\//)
  237.     {
  238.         #
  239.         # Just in case that date isn't converted yet...
  240.         # The format is slightly different from the one required for
  241.         # &create_date, this is why I don't call it here.
  242.         #
  243.         $date =~ s/^.* ([0-9]*)\/([0-9]*)\/([0-9]*).*$/$3/;
  244.         $date .= sprintf("%02i%02i", $2, $1);
  245.     }
  246.  
  247.     if ($start)
  248.     {
  249.         #
  250.         # Now just print the start and end time.
  251.         #
  252.         $out = $out."DTSTART\n :".$date."T".&create_time($start)."\n";
  253.         $out = $out."DTEND\n :".$date."T".&create_time($start+$length)."\n";
  254.     }
  255.     else
  256.     {
  257.         #
  258.         # There is no time for the beginning of the event defined, I assume
  259.         # that the event should last all day.
  260.         #
  261.         $out = $out."DTSTART\n ;VALUE=DATE\n :".$date."\n";
  262.     }
  263.  
  264.     #
  265.     # The RFC requires me to include the time of the object creation
  266.     #
  267.     $date = strftime "%Y%m%dT%H%M%S", localtime;
  268.     $out = $out."DTSTAMP\n :".$date."\n";
  269.  
  270.     $out = $out."END:VEVENT\nEND:VCALENDAR\n";
  271.  
  272.  
  273.     #
  274.     # Remember the UID for later merging
  275.     #
  276.     $uidl[0]++;
  277.     $uidl[$uidl[0]]=$uid;
  278.     
  279.     return ($out);
  280. }
  281.  
  282. #
  283. # This sub takes an older ical file and merges it to the new one.
  284. # It does this by comparing the stored UID's and adding all entries
  285. # whose ID hasn't been found. As you can see, the new events have
  286. # got a higher priority and can delete newer entries from the old file
  287. #
  288.  
  289. sub merge_entries
  290. {
  291.     my ($mergefile) =@_;
  292.     my $out = "";        # this string will be added later
  293.     my $event = "";      # the current event being processed
  294.     my $record = 0;      # is true if loop is in an event object
  295.     my $nextoneuid = 0;  # marks the line for the UID
  296.     my $uidfound = 0;    # is true if UID is already in new file
  297.  
  298.     open MERGEFILE, $mergefile or die "Can't open '$mergefile' for input $!";
  299.     foreach my $line (<MERGEFILE>)
  300.     {
  301.         if ($line =~ /BEGIN:VCALENDAR$/)
  302.         {
  303.             $event = "";
  304.             $record = 1;
  305.         }
  306.         if ($record == 1) {$event.=$line};
  307.         if ($nextoneuid == 1)
  308.         {
  309.             $nextoneuid = 0;
  310.             $uidfound = $line;
  311.             $uidfound =~ s/^ ://;
  312.             chop($uidfound);
  313.         }
  314.         # This way of finding the UID depends on having two lines
  315.         # for field descriptor and value and is therefore Mozilla
  316.         # Calendar specific.
  317.         if ($line =~ /^UID$/) {$nextoneuid=1};
  318.         if ($line =~ /^END:VCALENDAR$/)
  319.         {
  320.             $record = 0;
  321.             my $found = 0;
  322.             for (my $c=1; $c < $uidl[0]+1; $c++)
  323.             {
  324.                 $found=1 if ($uidl[$c] eq $uidfound);
  325.             }
  326.             if ($found == 0)
  327.             {
  328.                 $out .= $event;
  329.             }
  330.         }
  331.     }
  332.     close (MERGEFILE);
  333.     
  334.     return($out);
  335. }
  336.  
  337. #
  338. # Here the input file is read and each event is then given to
  339. # &convert for further processing.
  340. #
  341.  
  342. sub read_palmfile
  343. {
  344.     my ($instream, $outstream) = @_;
  345.     if (($instream  ne "") && ($instream ne "-"))
  346.         { open STDIN, $instream or die "Can't open '$instream' for input: $!";}
  347.     if ($outstream ne "") 
  348.         { open STDOUT, ">$outstream" or die "Can't open '$outstream' for output $!";;}
  349.     my $line = "";
  350.     my $pos = 0;
  351.     my @note;
  352.     foreach $line (<STDIN>)
  353.     {
  354.         if ($line =~ /(Note|Appt)/)
  355.         {
  356.             if ($pos != 0)
  357.             {
  358.                 print &convert($pos,@note);
  359.             }
  360.             $pos = 0;
  361.             $note[$pos] = $line;
  362.             $pos++;
  363.         }
  364.         elsif ($pos != 0)
  365.         {
  366.             $note[$pos] = $line;
  367.             $pos++;
  368.         }
  369.     }
  370.     close (INFILE);
  371.     print &convert($pos, @note);
  372.     print &merge_entries($mergefile) if ($mergefile);
  373. }
  374.  
  375. #
  376. # Well, the first sub has to be called somewhere. ;-)
  377. # Most of the stuff below is just option parsing.
  378. #
  379.  
  380. if (($ARGV[0] eq "--help") || ($ARGV[0] eq "-h"))
  381. {
  382. print << "EOF"
  383.  
  384. Usage: ical2ics.pl [-d STRING] [-m mergefile] [infile] [outfile]
  385.  
  386. This little script tries to convert ical calender files produced by read-ical
  387. to a rfc2445 compliant format which can be read by the Mozilla Calendar.
  388.  
  389. If no filename (or '-' for infile) is given STDIN respectively STDOUT are used.
  390.  
  391.   -d STRING     Change default description to STRING
  392.   -m mergefile  Merge the outfile with mergefile (the syntax has to be
  393.                 similar to the one created by this script), may be the
  394.                                 outfile itself
  395.  
  396. Please report bugs or send comments to <florian\@netego.de>.
  397. EOF
  398. }
  399. else
  400. {
  401.     $description = "Imported from Palm Pilot.";
  402.     my $infile = $outfile = ""; # names of the streams
  403.     my $infilenum = 0;          # position of infile in ARGV
  404.     my $outfilenum = 1;         # the same for outfile
  405.     my $argpos = 0;             # pointer to ARGV position
  406.     
  407.     # I hope that this option-vodoo works reliably
  408.     while ($ARGV[$argpos] ne "")
  409.     {
  410.         if ($ARGV[$argpos] eq "-d") 
  411.         {
  412.             $argpos++;
  413.             $description = $ARGV[$argpos];
  414.             $infilenum += 2;
  415.             $outfilenum += 2;
  416.         }
  417.         elsif ($ARGV[$argpos] eq "-m") 
  418.         {
  419.             $argpos++;
  420.             $mergefile = $ARGV[$argpos];
  421.             $infilenum += 2;
  422.             $outfilenum += 2;
  423.         }
  424.         else {$argpos++};
  425.     }
  426.     $infile  = $ARGV[$infilenum];
  427.     $outfile = $ARGV[$outfilenum];
  428.     
  429.     if ($mergefile)
  430.     {
  431.         # mergefile and outfile mustn't be the same, work
  432.         # with a backup copy instead
  433.         my $rnd = int(rand(100000));
  434.         $status = `cp $mergefile /tmp/ical2ics-$rnd.tmp`;
  435.         $mergefile="/tmp/ical2ics-$rnd.tmp";
  436.     }
  437.  
  438.     &read_palmfile($infile,$outfile,$mergefile);
  439.  
  440.     unlink $mergefile;
  441. }
  442.  
  443. # ChangeLog:
  444. #
  445. # 25.07.2002:
  446. # - Switched from the old "X ;MEMBER" fields to the new "X-MOZILLA" properties
  447. # - Events that last all day don't use "MEMBER=AllDay" any more, I just give a
  448. #   DTSTART date and no DTEND
  449. #
  450. # 24.05.2002:
  451. # - New option for merging ('-d') with existing data
  452. #
  453. # 13.05.2002:
  454. # - Added DTSTAMP property
  455. #
  456. # 25.04.2002:
  457. # - The bug in libical and Calendar concerning the EXDATE property have been
  458. #   fixed, therefore I removed the RECURRENCE-ID alternative.
  459. # - Added the -d option
  460. #
  461. # 24.04.2002:
  462. # - First official release
  463.