#!/usr/local/bin/perl -w use strict; use lib qw(lib); use Data::ICal::DateTime; use Data::ICal::Entry::Event; use DateTime; use DateTime::Span; use GraphViz; use Memoize; use DB_File; my $ics = shift || "London.pm.ics"; $|++; # Create a new GraphViz document - GraphViz doesn't et us provide a graph name :( my $g = GraphViz->new( rankdir => 'LR', size => '10,7', compound => 'true', ratio => 'fill' ); # this makes things much faster if we generate this repeatedly tie my %cache => 'DB_File', "cache/memoize.cache", O_RDWR|O_CREAT, 0666; memoize('events', NORMALIZER => 'span_to_name', SCALAR_CACHE => [HASH => \%cache]); # define our time range (because recurring events can be infinite) my $date1 = DateTime->new( year => 2005, month => 6, day => 27 ); my $date2 = DateTime->new( year => 2005, month => 10, day => 27 ); my $span = DateTime::Span->from_datetimes( start => $date1, end => $date2 ); # get the (possibly memoized for speed) events my @events = events($ics, $span); my %days; my @timeline; # sort all the events into order for (sort { $a->start->epoch <=> $b->start->epoch } @events ) { # make a start and end label my $start = $_->start->ymd." ".$_->start->hms; my $end = $_->end->ymd." ".$_->end->hms; my $summ = $_->summary || ''; my $desc = $_->description || ''; # .ics text has embedded escaped characters eval "\$desc = \"$desc\";"; # GraphViz doesn't let us label ports by name $g->add_node("$summ-$start-$end", shape => 'record', label => [ $summ, $start, $end, $desc ]); # create nodes for the days if necessary if (!$days{$start}++) { $g->add_node($start, label => $start); push @timeline, { epoch => $_->start->epoch, label => $start }; } if (!$days{$end}++) { $g->add_node($end, label => $end); push @timeline, { epoch => $_->end->epoch, label => $end }; } # add an edge from the start and end ports to the relevant dates $g->add_edge("$summ-$start-$end" => $start, from_port => 1); $g->add_edge("$summ-$start-$end" => $end, from_port => 2); } # sort the days into order (since the end date of one # event might be after the start date of another) @timeline = sort { $a->{epoch} <=> $b->{epoch} } @timeline; # add links between the days for (my $f=0; $f<$#timeline; $f++) { $g->add_edge($timeline[$f]->{label} => $timeline[$f+1]->{label}); } # print out the png file $g->as_png("cal.png"); # for debugging # $g->as_text("cal.dot"); sub events { my ($ics, $span) = @_; my $cal = Data::ICal->new( filename => $ics ); my @events = $cal->events($span); return @events; } sub span_to_name { my $ics = shift; my $span = shift; my $start = ($span->start_is_open)? "infinite" : $span->start->epoch; my $end = ($span->end_is_open)? "infinite" : $span->end->epoch; return "$ics-$start-$end"; }