Simple Text Agenda

Yesterday, I got tired of not having a simple way of seeing an agenda for the upcoming days of all my calendars. Between work using hosted Exchange, using a joint iCloud calendar, and still using Google Calendar quite often, getting all of the information in one place is a pain.

I tried starting out with Google Calendar. Unfortunately, I can't add iCloud shared calendars because the iCloud servers have a robots.txt that disallow everything to everyone, and Google Calendar for some reason respects that.

iCloud doesn't let you subscribe to third party calendars. So it's out as a combined solution.

Hosted Exchange makes subscribing to third party calendars so difficult as to be impossible.

So like any good programmer, I decided I needed to write my own.

 
#!/usr/bin/perl
 
use strict;
use warnings;
 
use Data::Dumper;
use HTML::Entities;
use iCal::Parser;
use LWP::UserAgent::Cached;
use Text::Table;
 
my $cache_dir = '/tmp/lwp-cache';
my @calendars = qw|
http://www.google.com:80/finance/events?q=NYSE:KO&output=ical
|;
 
my $now = DateTime->now( time_zone => 'America/New_York' );
my $tb = Text::Table->new({ }, { align => "right" });
 
my $parser = iCal::Parser->new();
foreach my $calendar_url (@calendars) {
	$parser->parse_strings(get($calendar_url));
}
 
my $combined = $parser->calendar;
 
my @events_list;
my $events = $combined->{events};
 
my %summaries;
my %summaries_seen;
 
foreach my $year (keys %{$events}) {
	foreach my $month (keys %{$events->{$year}}) {
		foreach my $day (keys %{$events->{$year}{$month}}) {
			foreach my $uuid (keys %{$events->{$year}{$month}{$day}}) {
 
				my $event = $events->{$year}{$month}{$day}{$uuid};
 
				my $summary = $event->{SUMMARY};
				my $start = $event->{DTSTART};
				my $end = $event->{DTEND};
 
				my $delta = $start->delta_days(DateTime->today());
				next if (DateTime->compare($start, DateTime->today()) == -1);
				next if ($delta->in_units("days") > 14);
 
				$summaries{$summary}++;
				push(@events_list, [ $summary, $start, $end ]);
			}
		}
	}
}
 
foreach my $event (sort { DateTime->compare($a->[1], $b->[1]) } @events_list) {
	next if ($summaries_seen{$event->[0]});
	my $summary = $event->[0];
	if ($summaries{$summary} > 1) {
		$summary .= ' (multiple)';
	}
 
	my $delta = $event->[1]->subtract_datetime($now);
	my ($days, $hours, $minutes) = $delta->in_units('days', 'hours', 'minutes');
 
	my $delta_text = 'd';
	if ($days == 0) {
		$delta = $delta->in_units('hours');
		$delta_text = 'h';
	} else {
		$delta = $delta->in_units('days');
	}
 
	$tb->load([ decode_entities($summary), $delta . $delta_text ]);
	$summaries_seen{$event->[0]}++;
}
print $tb;
 
sub get {
	my ($url) = @_;
 
	mkdir($cache_dir) unless -d $cache_dir;
	my $ua = new LWP::UserAgent::Cached(cache_dir => $cache_dir);
	$ua->show_progress(1);
	my $response = $ua->get($url);
 
	if ($response->is_success()) {
		return $response->content();
	} else {
		warn $response->status_line;
	}
}
 

Above is the source for the script. Simply, it fetches the calendar URLs provided in @calendars, parses them, and ignores anything from the past, and anything more than 14 days out. Then it generates a nice text table showing the events happening.

If you find this useful, please let me know.