Perl Advent Calendar 2007-12-01

On the ... which day again?!?

by David Westbrook

gcal

Is your true love forgetful? Well, here's a little reminder to him or her for your twelve days of gifts. [mod1a.pl]

Many things have a schedule of events or calendar of some kind. iCal (RFC 2445) is the standard for people to share calendars with one another, and Data::ICal provides perl with ability to both parse and generate calendar files.

Have event information or dates stored in a database or just listed in a file? Export them out into an iCalendar .ics file to be read by your favorite calendaring application: Sunbird, Google Calendar, Outlook, etc.

Perl Advent Calendars

Now, let's take all of the Perl Advent entries to date, going back to 2000. First, we'll scrape the archive page with the script get_links.pl to create an AoH links.dat data file. Now we can create our calendar file with a Data::ICal script. [mod1b.pl]

The resulting file is perladvent.ics which can be loaded as a remote calendar in your favorite application, as in these screenshots of Thunderbird and Google Calendar.

Now you can "open the window" (on the calendar) each day by regenerating this year's (or really any year 2000-2007) events with a combination of the scraper and Data::ICal scripts mentioned above [mod1c.pl], or perhaps more sanely add some daily reminders generated by reminders.pl.

Parsing example

Using a calendaring application to maintain a personal or organization calendar? Want those events custom embedded in your web pages or application? Data::ICal can parse the calendar file and provide easy access to the event data, such as taking our perladvent.ics and creating perladvent.html. [mod1d.pl]

This allows the full leveraging of established calendaring applications for maintaining calendars, reducing much wheel-reinvention, leaving more time for milk & cookies.

mod1a.pl


This script generates twelve.ics, shown in the Google Calendar screenshot above.

   1 use Data::ICal;
   2 use Data::ICal::Entry::Event;
   3 use Date::Calc qw/Add_Delta_Days/;
   4 
   5 my $calendar = Data::ICal->new();
   6 $calendar->add_properties( 'X-WR-CALNAME' => 'The Twelve Days of Christmas' );
   7 
   8 my $song = '';
   9 while(my $line = <DATA>){
  10   $song = $line . $song;
  11   chomp($line);
  12   my $vevent = Data::ICal::Entry::Event->new();
  13   $vevent->add_properties(
  14     summary => $line,
  15     description => "On the 12th day of Christmas, my true love sent to me\n" . $song,
  16     dtstart => sprintf( '%04d%02d%02d', Add_Delta_Days(2007,12,24,$.) ),
  17   );
  18   $calendar->add_entry($vevent);
  19 }
  20 
  21 print $calendar->as_string;
  22 
  23 __DATA__
  24 A partridge in a pear tree
  25 Two turtle doves
  26 Three French hens
  27 Four calling birds
  28 Five golden rings
  29 Six geese a-laying
  30 Seven swans a-swimming
  31 Eight maids a-milking
  32 Nine ladies dancing
  33 Ten lords a-leaping
  34 Eleven pipers piping
  35 Twelve drummers drumming

mod1b.pl


This script generates perladvent.ics from an AoH data file.

   1 use Data::ICal;
   2 use Data::ICal::Entry::Event;
   3 
   4 my $links_file = $ARGV[0] or die;
   5 
   6 my $calendar = Data::ICal->new();
   7 $calendar->add_properties(
   8   'X-WR-CALNAME' => 'Perl Advent Calendar',
   9   'X-WR-CALDESC' => 'Perl Advent Calendar history - http://perladvent.pm.org/',
  10 );
  11 
  12 my $links = do {local $/=undef; open FILE, '<', $links_file or die; my $VAR1; eval <FILE>};
  13 
  14 foreach my $row ( @$links ){
  15     my $vevent = Data::ICal::Entry::Event->new();
  16     $vevent->add_properties(
  17           summary => $row->{modules},
  18 	  description => $row->{title},
  19           categories => 'Perl Advent Calendar',
  20 	  dtstart => sprintf('%04d%02d%02d',$row->{year},12,$row->{day}),
  21 	  url => sprintf('http://perladvent.pm.org/%d/%d/',$row->{year},$row->{day}),
  22     );
  23     $calendar->add_entry($vevent);
  24 }
  25 
  26 print $calendar->as_string;

mod1c.pl


This script generates a calendar for a specific year from the Perl Advent Calendar archives.

   1 use LWP::Simple;
   2 use Data::ICal;
   3 use Data::ICal::Entry::Event;
   4 
   5 my $YEAR = shift or die "need year";
   6 
   7 sub get_links {
   8   my $s = get 'http://perladvent.pm.org/archives-Yd.html';
   9   my @list;
  10   foreach my $line (split /[\r\n]+/, $s){
  11     next unless $line =~ m#(\d+)-(\d+)-(\d+)</a>\s*&mdash;#;
  12     my ($year,$month,$day,$module) = ($1, $2, $3);
  13     next unless $year == $YEAR;
  14     my @modules = $line =~ m#module=(\S+)"#g or next;
  15     push @list, { year => $year, day => $day, modules => join(', ',@modules), title => get_title($year,$day) };
  16   }
  17   return \@list;
  18 }
  19 sub get_title {
  20   my ($year,$day) = @_;
  21   use WWW::Mechanize;
  22   my $mech = WWW::Mechanize->new;
  23   $mech->get(sprintf('http://perladvent.pm.org/%d/%d',$year,$day));
  24   if( $mech->content =~ m#<meta http-equiv="refresh" content="0;url=(.*?)"># ){
  25     # Handle any "Ordinal Redirects" done via a meta-refresh
  26     $mech->get($1);
  27   }
  28   return $mech->title;
  29 }
  30 
  31 my $links = get_links();
  32 die "nothing found" unless @$links;
  33 
  34 my $calendar = Data::ICal->new();
  35 $calendar->add_properties(
  36   'X-WR-CALNAME' => "Perl Advent Calendar $YEAR",
  37   'X-WR-CALDESC' => "Perl Advent Calendar $YEAR - http://perladvent.pm.org/$YEAR/",
  38 );
  39 
  40 foreach my $row ( @$links ){
  41     my $vevent = Data::ICal::Entry::Event->new();
  42     $vevent->add_properties(
  43           summary => $row->{modules},
  44 	  description => $row->{title},
  45           categories => 'Perl Advent Calendar',
  46 	  dtstart => sprintf('%04d%02d%02d',$row->{year},12,$row->{day}),
  47 	  url => sprintf('http://perladvent.pm.org/%d/%d/',$row->{year},$row->{day}),
  48     );
  49     $calendar->add_entry($vevent);
  50 }
  51 
  52 print $calendar->as_string;

mod1d.pl


This script generates perladvent.html from an ICal file.

   1 use Data::ICal;
   2 my $calendar = Data::ICal->new( filename => $ARGV[0] );
   3 
   4 print '<h1>', $calendar->property('x-wr-calname')->[0]->value, '</h1>';
   5 print '<h2>', $calendar->property('x-wr-caldesc')->[0]->value, '</h2>';
   6 
   7 foreach my $entry ( @{$calendar->entries} ){
   8   printf '<b>%s</b> - <a href="%s">%s</a> <i>%s</i><br>'."\n",
   9     map { $entry->property($_)->[0]->value }
  10     qw/ dtstart url summary description /
  11   ;
  12 }