#!/usr/bin/perl # # Script to translate `calendar` output into a couple of possibly useful # formats. CGI parameters can be used to construct a URL that works for # subscribing iCal, etc: # # user - whose calendar to process, on the host server. This is the only # required parameter. # fmt - either 'html' or 'vcal', defaults to html # ahead - number of days in the future to process. See code for defaults # back - number of days in the past to process. See code for defaults # # html format attempts to figure out how much of the beginning of each line # is the date and time information, such as 'Dec 19'. If the event text # happens to start with a time such as '9:30am', this may be recognized and # treated as part of the date. The date will be enclosed in a # tag, which could of course be changed to any # convenient HTML tag. The emph span happens to be defined on the style # sheets that my pages use. The rest of the event text is not marked up. # A
tag is inserted after each line. # # The html output can be embedded in a web page using ssi or something # similar. For example, in a server-parsed shtml page: # # # vCalendar format tries much harder to understand the `calendar` output. # Exactly one vEvent object will be created for each line. The date is # parsed from the beginning of the line, e.g. 'Dec 9*'. If the event text # happens to contain a string such as "15:56 UTC", this may be recognized # and a more specific vEvent object will be created with the exact time # in UTC. Or, if the event text happens to contain a string of the form # "9:30am", this may be recognized and the exact time (assumed to be local) # will be added to the vEvent object. No attempt is made to define time # zone information for local-time events, although this would probably be # a good idea. Because `calendar` output contains no year information, # crude heuristics are used to guess; see the code for details. # # The vCalendar output is suitable for subscribing to iCal, forwarding to # a mobile device, etc. For example, to subscribe iCal, enter a URL such as: # http://www.server.com/path/cal-translate.pl?user=mikey&fmt=vcal # # Copyright (C) 2003 Michael A. Dickerson. Copying, modification, or # redistribution are permitted under the terms of the Artistic License # as published by the Open Source Initiative. This is OSI Certified # Open Source Software; see http://www.opensource.org. Other scripts, # and possibly a newer revision of this one, are availabe at # http://www.singingtree.com/software. # # Created 21 Nov 2003 by M. Dickerson # 28 Nov 2003 MAD: added vCalendar output format use strict; use CGI ":standard"; use Digest::MD5 "md5_base64"; # this is a cheap trick to quickly convert 'May' to 5 later my $MONTHS = "xxxx+Jan+Feb+Mar+Apr+May+Jun+Jul+Aug+Sep+Oct+Nov+Dec"; my $fmt = param('fmt'); my $user = param('user'); my $lookahead = param('ahead'); my $lookback = param('back'); my $ethaddr; my $thisyear; my $thismonth; my $host = `hostname -s`; my $now = &get_nowstring(); $lookahead = ($fmt eq 'vcal' ? 60 : 10) unless $lookahead; $lookback = ($fmt eq 'vcal' ? 30 : 0) unless $lookback; # note that sort -M is a trick that sorts Jan < Feb < Mar ... open CAL, "calendar -f ~$user/.calendar/calendar -A $lookahead -B $lookback | sort -M |" or print("can't run calendar: $!"); if ($fmt eq 'vcal') { # Note that text/x-vCalendar should be a better content-type, but this # seems to mostly confuse my browser print "Content-type: text/plain\n\n"; print "BEGIN:VCALENDAR\n"; print "VERSION:2.0\n"; print "PRODID:-//Michael Dickerson//cal-translate.pl 1.00//EN\n"; print "X-WR-CALNAME:$user\@$host"; # no \n, it came with $host print "CALSCALE:GREGORIAN\n"; # find the current year, since calendar won't tell us my $x; # trash variable ($x, $x, $x, $x, $thismonth, $thisyear) = localtime(time()); $thismonth++; $thisyear += 1900; # more intuitive values } elsif ($fmt = 'html') { print "Content-type: text/html\n\n"; } else { print "Content-type: text/plain\n\n"; print "bogus format parameter: $fmt\n"; exit 0; } unless ($user) { print "need user parameter!\n"; exit 0; } while () { chomp; s/\t/ /g; # don't want to be confused by the tabs if ($fmt eq 'html') { # html is easy, since we don't have to understand anything really, # just regurgitate it. Note that happens to # have meaning in the style sheet that my pages use. It could just # as easily be changed to or whatever kind of emphasis you like. m/^([A-Z][a-z]{2,2} [0-9 ][0-9][* ] [0-9:]*(am|pm)?)(.*)$/; print "$1$3
\n"; } elsif ($fmt eq 'vcal') { # vCalendar format is harder, since we have to do some date-intelligent # conversions. m/^([A-Z][a-z][a-z]) +([0-9]+)[* ]+(.*)$/; my ($month, $day, $text) = ($1, $2, $3); $month = index($MONTHS, "+$month") / 4; # ugly heuristic: if this is July or later, assume that events reported # in Jan-Jun are for NEXT year my $year = $thisyear; $year++ if ($thismonth > 6 && $month <= 6); print "BEGIN:VEVENT\n"; # more ugly heuristics to try to handle time information written in # the event text if ($text =~ /([0-9]{1,2}):([0-9][0-9]) UTC/i) { # if the event text contains a time such as "00:00 UTC", parse it my $date = sprintf("%04d%02d%02dT%02d%02d00Z", $year, $month, $day, $1, $2); print "DTSTART:$date\n"; } elsif ($text =~ /([0-9]{1,2}):([0-9][0-9])([ap]m)/i) { # if the event text contains a time such as "9:30am:, parse that my $hour = $1; my $min = $2; $hour += 12 if $3 =~ /pm/i; my $date = sprintf("%04d%02d%02dT%02d%02d00", $year, $month, $day, $hour, $min); print "DTSTART:$date\n"; } else { # just make it a date event my $date = sprintf("%04d%02d%02d", $year, $month, $day); print "DTSTART;VALUE=DATE:$date\n"; } print "SUMMARY:$text\n"; print "UID:", &create_guid($_), "\n"; print "DTSTAMP:$now\n"; print "END:VEVENT\n"; } } if ($fmt eq 'vcal') { print "END:VCALENDAR\n"; } close CAL or warn("can't close calendar: $!"); exit 0; # We make a valiant attempt to create persistent globally-unique-IDs for # each translated event, so that if someone wanted to create a 'related' # event using a calendar application or some such, the link should work # across re-invocations of this script. sub create_guid() { (my $line) = @_; unless ($ethaddr) { # try to find [one of] the machine's Ethernet address[es] open IFCFG, "/sbin/ifconfig -a |"; while () { if (m/([0-9a-f\:]{17,17})/) { $ethaddr = $1; $ethaddr =~ s/\://g; last; } } close IFCFG; $ethaddr = 'macaddr-unknown' unless $ethaddr; } # append the base64-encoded md5 hash to the Ethernet address return $ethaddr . "-" . md5_base64($line); } sub get_nowstring() { my ($sec, $min, $hour, $day, $mon, $year) = gmtime(time()); $year += 1900; $mon++; my $now = sprintf("%04d%02d%02dT%02d%02d%02dZ", $year, $mon, $day, $hour, $min, $sec); return $now; }