aboutsummaryrefslogtreecommitdiff
path: root/ical2rem
diff options
context:
space:
mode:
Diffstat (limited to 'ical2rem')
-rwxr-xr-xical2rem279
1 files changed, 279 insertions, 0 deletions
diff --git a/ical2rem b/ical2rem
new file mode 100755
index 0000000..aab07d5
--- /dev/null
+++ b/ical2rem
@@ -0,0 +1,279 @@
+#!/usr/bin/perl -w
+#
+# ical2rem.pl -
+# Reads iCal files and outputs remind-compatible files. Tested ONLY with
+# calendar files created by Mozilla Calendar/Sunbird. Use at your own risk.
+# Copyright (c) 2005, 2007, Justin B. Alcorn
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+# version 0.5.2 2007-03-23
+# - BUG: leadtime for recurring events had a max of 4 instead of DEFAULT_LEAD_TIME
+# - remove project-lead-time, since Category was a non-standard attribute
+# - NOTE: There is a bug in iCal::Parser v1.14 that causes multiple calendars to
+# fail if a calendar with recurring events is followed by a calendar with no
+# recurring events. This has been reported to the iCal::Parser author.
+# version 0.5.1 2007-03-21
+# - BUG: Handle multiple calendars on STDIN
+# - add --heading option for priority on section headers
+# version 0.5 2007-03-21
+# - Add more help options
+# - --project-lead-time option
+# - Supress printing of heading if there are no todos to print
+# version 0.4
+# - Version 0.4 changes all written or inspired by, and thanks to Mark Stosberg
+# - Change to GetOptions
+# - Change to pipe
+# - Add --label, --help options
+# - Add Help Text
+# - Change to subroutines
+# - Efficiency and Cleanup
+# version 0.3
+# - Convert to GPL (Thanks to Mark Stosberg)
+# - Add usage
+# version 0.2
+# - add command line switches
+# - add debug code
+# - add SCHED _sfun keyword
+# - fix typos
+# version 0.1 - ALPHA CODE.
+
+=head1 SYNOPSIS
+
+ cat /path/to/file*.ics | ical2rem.pl > ~/.ical2rem
+
+ All options have reasonable defaults:
+ --label Calendar name (Default: Calendar)
+ --lead-time Advance days to start reminders (Default: 3)
+ --todos, --no-todos Process Todos? (Default: Yes)
+ --heading Define a priority for static entries
+ --help Usage
+ --man Complete man page
+
+Expects an ICAL stream on STDIN. Converts it to the format
+used by the C<remind> script and prints it to STDOUT.
+
+=head2 --label
+
+ ical2rem.pl --label "Bob's Calendar"
+
+The syntax generated includes a label for the calendar parsed.
+By default this is "Calendar". You can customize this with
+the "--label" option.
+
+=head2 --lead-time
+
+ ical2rem.pl --lead-time 3
+
+How may days in advance to start getting reminders about the events. Defaults to 3.
+
+=head2 --no-todos
+
+ ical2rem.pl --no-todos
+
+If you don't care about the ToDos the calendar, this will surpress
+printing of the ToDo heading, as well as skipping ToDo processing.
+
+=head2 --heading
+
+ ical2rem.pl --heading "PRIORITY 9999"
+
+Set an option on static messages output. Using priorities can made the static messages look different from
+the calendar entries. See the file defs.rem from the remind distribution for more information.
+
+=cut
+
+use strict;
+use iCal::Parser;
+use DateTime;
+use Getopt::Long 2.24 qw':config auto_help';
+use Pod::Usage;
+use Data::Dumper;
+use vars '$VERSION';
+$VERSION = "0.5.2";
+
+# Declare how many days in advance to remind
+my $DEFAULT_LEAD_TIME = 3;
+my $PROCESS_TODOS = 1;
+my $HEADING = "";
+my $help;
+my $man;
+
+my $label = 'Calendar';
+GetOptions (
+ "label=s" => \$label,
+ "lead-time=i" => \$DEFAULT_LEAD_TIME,
+ "todos!" => \$PROCESS_TODOS,
+ "heading=s" => \$HEADING,
+ "help|?" => \$help,
+ "man" => \$man
+);
+pod2usage(1) if $help;
+pod2usage(-verbose => 2) if $man;
+
+my $month = ['None','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+
+my @calendars;
+my $in;
+
+while (<>) {
+ $in .= $_;
+ if (/END:VCALENDAR/) {
+ push(@calendars,$in);
+ $in = "";
+ }
+}
+my $parser = iCal::Parser->new();
+my $hash = $parser->parse_strings(@calendars);
+
+##############################################################
+#
+# Subroutines
+#
+#############################################################
+#
+# _process_todos()
+# expects 'todos' hashref from iCal::Parser is input
+# returns String to output
+sub _process_todos {
+ my $todos = shift;
+
+ my ($todo, @newtodos, $leadtime);
+ my $output = "";
+
+ $output .= 'REM '.$HEADING.' MSG '.$label.' ToDos:%"%"%'."\n";
+
+# For sorting, make sure everything's got something
+# To sort on.
+ my $now = DateTime->now;
+ for $todo (@{$todos}) {
+ # remove completed items
+ if ($todo->{'STATUS'} && $todo->{'STATUS'} eq 'COMPLETED') {
+ next;
+ } elsif ($todo->{'DUE'}) {
+ # All we need is a due date, everything else is sugar
+ $todo->{'SORT'} = $todo->{'DUE'}->clone;
+ } elsif ($todo->{'DTSTART'}) {
+ # for sorting, sort on start date if there's no due date
+ $todo->{'SORT'} = $todo->{'DTSTART'}->clone;
+ } else {
+ # if there's no due or start date, just make it now.
+ $todo->{'SORT'} = $now;
+ }
+ push(@newtodos,$todo);
+ }
+ if (! (scalar @newtodos)) {
+ return "";
+ }
+# Now sort on the new Due dates and print them out.
+ for $todo (sort { DateTime->compare($a->{'SORT'}, $b->{'SORT'}) } @newtodos) {
+ my $due = $todo->{'SORT'}->clone();
+ my $priority = "";
+ if (defined($todo->{'PRIORITY'})) {
+ if ($todo->{'PRIORITY'} == 1) {
+ $priority = "PRIORITY 1000";
+ } elsif ($todo->{'PRIORITY'} == 3) {
+ $priority = "PRIORITY 7500";
+ }
+ }
+ if (defined($todo->{'DTSTART'}) && defined($todo->{'DUE'})) {
+ # Lead time is duration of task + lead time
+ my $diff = ($todo->{'DUE'}->delta_days($todo->{'DTSTART'})->days())+$DEFAULT_LEAD_TIME;
+ $leadtime = "+".$diff;
+ } else {
+ $leadtime = "+".$DEFAULT_LEAD_TIME;
+ }
+ $output .= "REM ".$due->month_abbr." ".$due->day." ".$due->year." $leadtime $priority MSG \%a $todo->{'SUMMARY'}\%\"\%\"\%\n";
+ }
+ $output .= 'REM '.$HEADING.' MSG %"%"%'."\n";
+ return $output;
+}
+
+
+#######################################################################
+#
+# Main Program
+#
+######################################################################
+
+print _process_todos($hash->{'todos'}) if $PROCESS_TODOS;
+
+my ($leadtime, $yearkey, $monkey, $daykey,$uid,%eventsbyuid);
+print 'REM '.$HEADING.' MSG '.$label.' Events:%"%"%'."\n";
+my $events = $hash->{'events'};
+foreach $yearkey (sort keys %{$events} ) {
+ my $yearevents = $events->{$yearkey};
+ foreach $monkey (sort {$a <=> $b} keys %{$yearevents}){
+ my $monevents = $yearevents->{$monkey};
+ foreach $daykey (sort {$a <=> $b} keys %{$monevents} ) {
+ my $dayevents = $monevents->{$daykey};
+ foreach $uid (sort {
+ DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'})
+ } keys %{$dayevents}) {
+ my $event = $dayevents->{$uid};
+ if ($eventsbyuid{$uid}) {
+ my $curreventday = $event->{'DTSTART'}->clone;
+ $curreventday->truncate( to => 'day' );
+ $eventsbyuid{$uid}{$curreventday->epoch()} =1;
+ for (my $i = 0;$i < $DEFAULT_LEAD_TIME && !defined($event->{'LEADTIME'});$i++) {
+ if ($eventsbyuid{$uid}{$curreventday->subtract( days => $i+1 )->epoch() }) {
+ $event->{'LEADTIME'} = $i;
+ }
+ }
+ } else {
+ $eventsbyuid{$uid} = $event;
+ my $curreventday = $event->{'DTSTART'}->clone;
+ $curreventday->truncate( to => 'day' );
+ $eventsbyuid{$uid}{$curreventday->epoch()} =1;
+ }
+
+ }
+ }
+ }
+}
+foreach $yearkey (sort keys %{$events} ) {
+ my $yearevents = $events->{$yearkey};
+ foreach $monkey (sort {$a <=> $b} keys %{$yearevents}){
+ my $monevents = $yearevents->{$monkey};
+ foreach $daykey (sort {$a <=> $b} keys %{$monevents} ) {
+ my $dayevents = $monevents->{$daykey};
+ foreach $uid (sort {
+ DateTime->compare($dayevents->{$a}->{'DTSTART'}, $dayevents->{$b}->{'DTSTART'})
+ } keys %{$dayevents}) {
+ my $event = $dayevents->{$uid};
+ if (exists($event->{'LEADTIME'})) {
+ $leadtime = "+".$event->{'LEADTIME'};
+ } else {
+ $leadtime = "+".$DEFAULT_LEAD_TIME;
+ }
+ my $start = $event->{'DTSTART'};
+ print "REM ".$start->month_abbr." ".$start->day." ".$start->year." $leadtime ";
+ if ($start->hour > 0) {
+ print " AT ";
+ print $start->strftime("%H:%M");
+ print " SCHED _sfun MSG %a %2 ";
+ } else {
+ print " MSG %a ";
+ }
+ print "%\"$event->{'SUMMARY'}";
+ print " at $event->{'LOCATION'}" if $event->{'LOCATION'};
+ print "\%\"%\n";
+ }
+ }
+ }
+}
+exit 0;
+#:vim set ft=perl ts=4 sts=4 expandtab :