#!/usr/bin/perl -w use strict; use Time::Local; local $SIG{__WARN__}=sub{}; #Temporarily suppress warnings my %ITEM = (); #Create hash for each appointment's data my @t = localtime(time); my $gmt_offset_in_seconds = timegm(@t) - timelocal(@t); # this calculates the offset for server time from the iCal's "zulu" time (GMT). my @all_files = <*>; open (OUTPUT, '>output_memo.txt'); # empty the file, and get ready to write (change > to >> to append) foreach my $file (@all_files) { if($file =~ /\.ics/){ # only process files with the right extension open FILE, $file or die $!; my @lines = ; close FILE; my $this_ical_file = join ("",@lines); $this_ical_file =~ s/\r//g; # icky window line endings removed $this_ical_file =~ s/\n //g; # a new line starting with a space is actually meant to be wrap indicatio, so unwrap. my @events = split(/BEGIN:VEVENT/, $this_ical_file); # we make an array, each element being an event LOOP: # a way to escape the foreach when we don't want to include an appointment foreach my $event (@events){ for (keys %ITEM){ delete $ITEM{$_};} # is this the best way to empty a hash? I was declaring the hash here everytime, but I think that's wrong. $event =~ s/[^a-zA-Z0-9: \n-;!\.,=\?\@\#\$\%\^\&\*\(\)]//g; # I only want certain expected characters. Buhbye to everything else. my @event_details = split(/\n/,$event); # we'll make a hash here, called %ITEM, which has all the various types of features an iCal file has foreach my $line(@event_details){ if($line =~ /:/){ # I think they should all have colons, but just in case. my ($name,$value) = split(/:/,$line,2); # the 2 means split first occurence of delimiter (I think) if($name =~ /DTSTART/){ # sometimes DTSTART has extensions. But I don't care. $name = "DTSTART"; if(length($value) < 15){ $value = $value . "T000000";} # make sure we can pass the substr below by adding 0 time to all day events } if($name =~ /DTEND/){ $name = "DTEND"; if(length($value) < 15){ $value = $value . "T000000";} } $ITEM{$name} = $value; # fill hash with details of this appointment, like start time, recurring rule, location, etc. } } ## hash is filled, let's analyze this appointment: # I want the localtime that corresponds to that gmtime: if(length($ITEM{'DTSTART'}) >= 15 && length($ITEM{'DTEND'}) >= 15){ # so I don't get any problems with substr. Is this even needed? my $start_unixtime = (timegm( (substr ($ITEM{'DTSTART'},13,2)),(substr($ITEM{'DTSTART'},11,2)),(substr($ITEM{'DTSTART'},9,2)),(substr($ITEM{'DTSTART'},6,2)),(substr($ITEM{'DTSTART'},4,2)-1),(substr($ITEM{'DTSTART'},0,4)))) - $gmt_offset_in_seconds; my $end_unixtime = (timegm( (substr($ITEM{'DTEND'},13,2)),(substr($ITEM{'DTEND'},11,2)),(substr($ITEM{'DTEND'},9,2)),(substr($ITEM{'DTEND'},6,2)),(substr($ITEM{'DTEND'},4,2)-1),(substr($ITEM{'DTEND'},0,4)) )) - $gmt_offset_in_seconds; # now the length of the appointment my $appointment_length = ($end_unixtime - $start_unixtime) / 60; # subtraction gives us seconds, dividing by 60 gives us minutes, so appointment length is in minutes. # now checkout the recurring factor: (one off appt is "63", yearly is "62", monthly is "61" Weekly is "60") # NOTE that this is my best guess of the weird .memo format. Who made this anyway? Couldn't find any docs on it. my $appointment_type = 0; if($ITEM{'RRULE'} =~ /WEEKLY/){ $appointment_type = "60"; } elsif($ITEM{'RRULE'} =~ /MONTHLY/){ $appointment_type = "61"; } elsif($ITEM{'RRULE'} =~ /YEARLY/){ $appointment_type = "62"; } else{ $appointment_type = "63"; # if it's in the past, we don't even want it on the export if ($end_unixtime < time){ # could possibly modify this to get two weeks in the past by substracting additional seconds from time next LOOP; } } # Recurring Rules might be marked to end at a certain time. If that time is in the past, we don't want it exported # RRULE:FREQ=WEEKLY;BYDAY=MO;WKST=MO;UNTIL=20090817T223000Z my @recurring_detail = split(/;/,$ITEM{'RRULE'}); foreach my $breakdown (@recurring_detail){ my ($name,$value) = split(/=/,$breakdown); if($name eq "UNTIL" && length($value) >= 15){ if (timegm( (substr ($value,13,2)),(substr($value,11,2)),(substr($value,9,2)),(substr($value,6,2)),(substr($value,4,2)-1),(substr($value,0,4))) - $gmt_offset_in_seconds < time){ print "Skipping " . $ITEM{'SUMMARY'} . "\n"; next LOOP; } } } # Make this time-zone corrected date/time look the way RockBox wants it to: my ($sec,$min,$hour,$day,$month,$year) = (localtime($start_unixtime))[0,1,2,3,4,5,6]; $year = $year + 1900; $month++; # date print OUTPUT sprintf("%02d", $day) . sprintf("%02d", $month) . $year . $appointment_type; # time print OUTPUT "[" . sprintf("%02d",$hour) . ":" . sprintf("%02d",$min) . "] "; # title print OUTPUT $ITEM{'SUMMARY'}; # location? if($ITEM{'LOCATION'}) { print OUTPUT " \@ " . $ITEM{'LOCATION'}; } # length print OUTPUT " (" . $appointment_length . " min) "; # description and status print OUTPUT substr($ITEM{'DESCRIPTION'},0,20) . "(" . $ITEM{'STATUS'} . ")"; print OUTPUT "\n"; } } } } close (OUTPUT); # open the file again to sort it (will this help time related appointments sort correctly?) open FILE, "output_memo.txt"; my @contents = ; close FILE; @contents = sort(@contents); # and save... open (SAVE, '>output_memo.txt'); print SAVE @contents; close SAVE; print "Complete\n\n"; exit;