#! /usr/local/bin/perl # Copyright (C) 2004-2005 Lars Eggert # All rights reserved. # # Redistribution and use in source and binary forms are permitted # provided that the above copyright notice and this paragraph are # duplicated in all such forms and that any documentation, # advertising materials, and other materials related to such # distribution and use acknowledge that the software was developed # by Lars Eggert. The name of the author may not be used to endorse # or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE. # $Id: ietf2ical.pl,v 1.4 2007/11/20 11:38:36 eggert Exp $ use warnings; use strict; use LWP::Simple; use Mac::Glue ":all"; use Date::Manip; use Time::Epoch; use File::Copy; die "must give IETF number on command line" unless defined $ARGV[0]; die "IETF number not numeric" unless $ARGV[0] =~ /^\d+$/; die "must give IETF time zone in iCal format on command line" unless defined $ARGV[1]; my $ietf_nr = $ARGV[0]; my $ietf_tz = $ARGV[1]; my $ietf = "IETF-$ietf_nr"; my $url = "http://ietf.org/meetings/agenda_${ietf_nr}.txt"; my $ical = new Mac::Glue "iCal"; sub error_handler { my ($glue, $event, $gluename, $eventname, $errs, $errn, @args) = @_; my $args = join ', ', @args; die "$gluename->$eventname($args) event failed:\n$errs\n"; } sub schedule ($$$$$;$$$$) { my ($cal, $date, $start, $stop, $session, $area, $abbrev, $name, $room) = @_; my $link; if ($abbrev) { $link = "http://ietf.org/html.charters/$abbrev-charter.html"; } else { $link = $url; } foreach ($start, $stop) { s/^(\d{2})(\d{2})/$1:$2/; $_ = UnixDate ParseDate("$date $_"), "%s"; # this was required for old the Mac::Glue # $_ = perl2epoch $_, "macos"; # $_ = Mac::AppleEvents::Simple::pack_psn $_; # $_ = param_type(typeLongDateTime(), $_); } if ($name) { my $desc = ($name =~ /\*\s*$/ ? "(Multicast Session)" : ""); $name =~ s/\*\s*$//; if ($area) { $desc = "$session\n$area\n$abbrev\n$name\n$desc"; } else { $desc = "$session\n$name\n$desc"; } $ical->make(new => "event", at => Mac::Glue::location("end", $cal), with_properties => { summary => ($abbrev ? "$abbrev\n$name" : $name), location => $room, url => $link, "start date" => $start, "end date" => $stop, description => $desc }, ERRORS => \&error_handler); } else { $ical->make(new => "event", at => Mac::Glue::location("end", $cal), with_properties => { summary => $session, "start date" => $start, "end date" => $stop, description => $session }, ERRORS => \&error_handler); } } # get the agenda my $text = get $url or die "Cannot retrieve agenda from $url"; my @agenda = split "\n", $text; # create a clean iCal calendar for the IETF # this removes all events from an existing calendar of that name! my $cal; my $found = 0; for my $c ($ical->prop("calendars")->get) { if ($c->prop("title")->get eq "$ietf-TMP") { $c->prop("events")->delete; $cal = $c; $found = 1; last; } } unless ($found) { $cal = $ical->make(new => "calendar", with_properties => { title => "$ietf-TMP", description => $ietf }, ERRORS => \&error_handler); } # parse the agenda my ($wkday, $date, $start, $stop, $session, $area, $abbrev, $name, $room); my $leftovers = 0; for (@agenda) { last if /^={3,}/; # skip empty lines, or lines that have whitespace in front, # or lines that start with a dash (plenary agenda) if (/^\s*$/ or /^\s/ or /^\s*-/) { print "$wkday | $date | $start | $stop | $session\n" if $leftovers; schedule $cal, $date, $start, $stop, $session if $leftovers; $leftovers = 0; next; } # parse a new day if (/^(\w+),\s+(.*)/i) { print "$wkday | $date | $start | $stop | $session\n" if $leftovers; schedule $cal, $date, $start, $stop, $session if $leftovers; ($wkday, $date) = (uc $1, $2); foreach ($wkday, $date) { s/^\s+//; s/\s+$//; } next; } # parse a new session if (/^(\d{4})-(\d{4})\s+(.*)/i) { print "$wkday | $date | $start | $stop | $session\n" if $leftovers; schedule $cal, $date, $start, $stop, $session if $leftovers; ($start, $stop, $session) = ($1, $2, $3); $session =~ s/\s+-\s*$//g; foreach ($start, $stop, $session) { s/^\s+//; s/\s+$//; } $leftovers = 1; next; } # parse a new session (open ended) if (/^(\d{4})\s+(.*)/i) { print "$wkday | $date | $start | $stop | $session\n" if $leftovers; schedule $cal, $date, $start, $stop, $session if $leftovers; ($start, $session) = ($1, $2); $session =~ s/\s+-\s*$//g; $stop = "2359"; foreach ($start, $stop, $session) { s/^\s+//; s/\s+$//; } $session .= " (OPEN END)"; $leftovers = 1; next; } # parse a new timeslot within a session if (/^(.*\s{2,})+/i) { my @col = split /\s{2,}/; if (scalar @col == 3) { # agenda does not yet have room column ($area, $abbrev, $name, $room) = ($col[0], $col[1], $col[2], ""); } elsif (scalar @col == 4) { # agenda does have room column ($area, $abbrev, $name, $room) = ($col[1], $col[2], $col[3], $col[0]); } elsif (scalar @col == 2) { # item has neither area nor abbbrev ($area, $abbrev, $name, $room) = ("", "", $col[1], $col[0]); } else { die "weird agenda format: $_"; } foreach ($area, $abbrev, $name, $room) { s/^\s+//; s/\s+$//; } print "$date | $start | $stop | $session | $area | $abbrev | $name | $room\n"; schedule $cal, $date, $start, $stop, $session, $area, $abbrev, $name, $room; $leftovers = 0; } } print "$wkday | $date | $start | $stop | $session\n" if $leftovers; schedule $cal, $date, $start, $stop, $session if $leftovers; # now remove the time zone information and create the final calendar # # XXX we must sleep to allow iCal to properly exit before mucking # with its database files $ical->quit; sleep 10; my $in = new IO::File; $in->open("<$ENV{HOME}/Library/Calendars/$ietf-TMP.ics"); die "open: $!" unless defined $in; my $out = new IO::File; $out->open(">$ENV{HOME}/Library/Calendars/$ietf.ics"); die "open: $!" unless defined $out; my $tz = new IO::File; $tz->open(">$ENV{HOME}/Library/Calendars/$ietf-TZ.ics"); die "open: $!" unless defined $tz; while (<$in>) { # remove all timezones my $line = $_; $line =~ s/;TZID=[^:]*//g; $line =~ s/$ietf-TMP/$ietf/g; print $out $line unless $line =~ /^X-WR-TIMEZONE:.*$/; # mangle all timezones $line = $_; $line =~ s/;TZID=[^:]*/;TZID=$ietf_tz/g; $line =~ s/^X-WR-TIMEZONE:.*/X-WR-TIMEZONE:$ietf_tz/g; $line =~ s/$ietf-TMP/$ietf-TZ/g; print $tz $line; } $in->close; $out->close; $tz->close;