#!/usr/bin/perl # # commandline calendar client, using quick and dirty vCalendar format parser # this needs a proper vCalendar/iCal parser, but this will do for now use Date::Calc qw(:all); ($BCMD = $0) =~ s/.*\///; ($REVISION) = ('$Revision: 1.6 $' =~ /[^\d\.]*([\d\.]*)/); $HELPSTRING = "For help, type: $BCMD -h"; ($IDENT = '@(#)ccal: command-line calendar') =~ s/^[^:]*: *//; $USAGE = "Usage: $BCMD [-dhLv] [-f file] [w|m|t|startdate [enddate]]"; $DEFAULT_CALENDAR = "$ENV{'HOME'}/.gnome/user-cal.vcf"; $DEFAULT_CALENDAR = "$ENV{'HOME'}/user-cal.vcf" unless -f $DEFAULT_CALENDAR; ######################################################################## # process loosely specified date sub mydate { my ( $date ) = @_; if ( $date =~ /^(\d\d\d\d)(\d\d)(\d\d)$/ ) { $y = $1; $m = $2; $d = $3; print STDERR "yyyymmdd\n" if $DEBUG; } elsif ( $date =~ /^(\d\d\d\d)(\d\d)$/ ) { $y = $1; $m = $2; $d = 0; print STDERR "yyyymm\n" if $DEBUG; } elsif ( $date =~ /^(\d\d\d\d)$/ ) { $y = $1; $m = 0; $d = 0; print STDERR "yyyy\n" if $DEBUG; } elsif ( $date =~ /^(\d\d?)$/ ) { $y = 0; $m = $1; $d = 0; print STDERR "mm\n" if $DEBUG; } elsif ( $date =~ /^(...)$/ ) { $y = 0; $m = Decode_Month( $1 ); $d = 0; print STDERR "mmm\n" if $DEBUG; } else { $y = $m = $d = 0; print STDERR ":$date: non-date\n" if $DEBUG; } return ( $y, $m, $d ); } ######################################################################## # calculate day-1, day+1 sub prevday { my ( $date ) = @_; ( $y, $m, $d ) = ( $date =~ /(\d\d\d\d)(\d\d)(\d\d)/ ); ( $y, $m, $d ) = Add_Delta_Days( $y, $m, $d, -1); return sprintf "%04d%02d%02d", $y, $m, $d; } sub nextday { my ( $date ) = @_; ( $y, $m, $d ) = ( $date =~ /(\d\d\d\d)(\d\d)(\d\d)/ ); ( $y, $m, $d ) = Add_Delta_Days( $y, $m, $d, 1); return sprintf "%04d%02d%02d", $y, $m, $d; } ######################################################################## # return dow string sub dowstring { my ( $date ) = @_; ( $y, $m, $d ) = ( $date =~ /(\d\d\d\d)(\d\d)(\d\d)/ ); return Day_of_Week_Abbreviation( Day_of_Week( $y, $m, $d ) ); } ######################################################################## # convert this event into string sub event_string { return dowstring( $dstart ) . " $dstart $tstart-" . ( ( $dstart ne $dend ) ? ( dowstring( $dend ) . " " . ( ( $dend ne nextday( $dstart ) ) ? "$dend " : '' ) ) : '' ) . "$tend: $desc\n"; } ######################################################################## # print info if it matches input spec sub process_event { print STDERR "$dstart $tstart/$dend $tend\n" if $DEBUG > 1; if ( $tend eq '0000' ) { $dend = prevday( $dend ); $tend = '2400'; } if ( $nextonly ) { # just look for event with minimum start >= now if ( ( $dstart > $startdate or ( $dstart == $startdate and $tstart >= $starttime ) ) and $dstart <= $ndstart and ( ! $DEBUG or print "$dstart$tstart,$ndstart$ntstart\n" ) and ( $dstart < $ndstart or $tstart <= $ntstart ) ) { if ( $dstart < $ndstart or $tstart < $ntstart ) { $ndstart = $dstart; $ntstart = $tstart; $nextout = event_string(); print STDERR '1:' . $nextout if $DEBUG; } else { # same time $nextout .= event_string(); print STDERR '2:' . $nextout if $DEBUG; } } } elsif ( $dend.$tend >= $startdate.$starttime and $dstart.$tstart <= $enddate.$endtime ) { push @output, event_string(); } # EXDATE, RRULE still to do } ######################################################################## # show bad usage message and quit sub badusage { print STDERR "$USAGE\n$HELPSTRING\n"; exit 2; } ######################################################################## # process arguments require( 'getopts.pl' ); badusage() unless Getopts('df:hLv'); if ($opt_h) { print <<"EOT"; $BCMD $REVISION: $IDENT $USAGE -d print extra debugging information [false] -f file location of calendar file [${DEFAULT_CALENDAR}] -h display this help text -L display software license -n display next event -v turn on verbose mode [false] startdate enddate date spec in form YYYY[MM[DD]]|MM w|m|t|n next 7 days|rest of month|rest of today|next event EOT exit 0; } elsif ($opt_L) { print < This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. If you do not already have a copy of the GNU General Public License, you can obtain a copy by anonymous ftp from ftp.gnu.org (file COPYING in directory /pub/gnu) or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. EOT exit 0; } $VERBOSE = $opt_v; $DEBUG = $opt_d + $opt_d * $opt_v; $nextonly = 0; badusage() if (@ARGV > 2); $CALFILE = ( $opt_f ? $opt_f : $DEFAULT_CALENDAR ); ( $ty, $tm, $td, $th, $tmin ) = ( localtime() )[5,4,3,2,1]; $ty += 1900; $tm ++; $today = sprintf "%04d%02d%02d", $ty, $tm, $td; $time = sprintf "%02d%02d", $th, $tmin; printf "$BCMD $REVISION at $today $time\n" if $VERBOSE; $starttime = ''; if ( @ARGV ) { $startdate = $ARGV[0]; } else { $startdate = $enddate = $today; $starttime = $time; } $enddate = ( ( @ARGV > 1 ) ? $ARGV[1] : '' ); if ( $startdate =~ /^m/ ) { # month badusage() if ( $enddate ); $startdate = $today; $enddate = sprintf "%04d%02d31", $ty, $tm; $starttime = $time; } elsif ( $startdate =~ /^w/ ) { # week badusage() if ( $enddate ); $startdate = $today; $enddate = sprintf "%04d%02d%02d", Add_Delta_Days( $ty, $tm, $td, 7 ); $starttime = $time; } elsif ( $startdate =~ /^t/ ) { # tomorrow badusage() if ( $enddate ); $startdate = $enddate = nextday( $today ); $starttime = $time; } elsif ( $startdate =~ /^n/ ) { # next event badusage() if ( $enddate ); $nextonly = 1; $startdate = $today; $starttime = $time; } elsif ( $enddate and $startdate > $enddate ) { # ASSERT: $starttime eq '' # so this swop will only happen if no $starttime to worry about ( $startdate, $enddate ) = ( $enddate, $startdate ) } $starttime = '0000' if $starttime eq ''; $endtime = '2400'; # ASSERT: $starttime ne '' and ( ! $enddate or $startdate <= $enddate ) # nnnnnnnn -> yyyymmdd, nnnnnn -> yyyymm, nnnn -> yyyy, nn -> mm ( $sy, $sm, $sd ) = mydate( $startdate ); ( $ey, $em, $ed ) = ( $enddate ? mydate( $enddate ) : ( $sy, $sm, $sd ) ); $sd = 1 unless $sd; $sm = 1 unless $sm; $sy = $ty unless $sy; $ed = 31 unless $ed; $em = 12 unless $em; $ey = $ty unless $ey; $ey = '9999' if $nextonly; $startdate = sprintf "%04d%02d%02d", $sy, $sm, $sd; $enddate = sprintf "%04d%02d%02d", $ey, $em, $ed; # ASSERT: $starttime ne '' and $enddate and $startdate <= $enddate print "dates $startdate-$enddate from $CALFILE\n" if $VERBOSE; ######################################################################## # parse calendar file die "Cannot open $CALFILE for reading, quitting" unless -r $CALFILE; $dstart = $tstart = $dend = $tend = ''; $ndstart = '99990101'; $ntstart = '2401'; $nextout = ''; $/ = "\r\n"; open CALFILE or die "Cannot open $CALFILE, quitting"; $state = 0; while ( ) { chomp $_; if ( $state == 0 ) { # starting to parse file next if ( /^\s*$/ ); if ( /^BEGIN:VCALENDAR$/ ) { $state = 1; next; } else { die "Unexpected line (\$state=$state), quitting"; } } elsif ( $state == 1 ) { # got first header if ( m#^PRODID:-//(.*)//NONSGML (.*)//..$# ) { $prod1 = $1; $prod2 = $2; $state = 2; next; } else { die "Unexpected line (\$state=$state), quitting"; } } elsif ( $state == 2 ) { # got second header if ( /^TZ:(.*)$/ ) { $timezone = $1; $state = 3; next; } else { die "Unexpected line\n$_in $CALFILE, quitting"; } } elsif ( $state == 3 ) { # got third header if ( /^VERSION:(.*)$/ ) { $version = $1; if ( $version ne '1.0' ) { die "Version $version not supported in $CALFILE, quitting"; } $state = 4; print "timezone $timezone, calendar from $prod1/$prod2\n---\n" if $VERBOSE; } else { die "Unexpected line\n$_in $CALFILE, quitting"; } } elsif ( $state == 4 ) { # expecting calendar object if ( /^\s*$/ ) { next; } elsif ( /^BEGIN:VEVENT$/ ) { $state = 6; next; } elsif ( /^BEGIN:VTODO$/ ) { # skip for now $state = 5; next; } elsif ( /^END:VCALENDAR$/ ) { if ( $nextonly ) { print $nextout; } else { # doesn't sort ...-Mon 20031006 vs. ...-Fri 20031010 print sort { substr( $a, 4 ) cmp substr( $b, 4 ) } @output; } $state = 99; next; } else { die "Cannot handle $_ in $CALFILE, quitting"; } } elsif ( $state == 5 ) { # skip to next calendar object next unless /^\s*$/; $state = 4; next; } elsif ( $state == 6 ) { # expecting components of VEVENT if ( /^END:VEVENT$/ ) { # calendar event complete, process it and reset vars for next event process_event(); $dstart = $tstart = $dend = $tend = ''; $state = 4; } elsif ( /^UID:.*/ ) { # skip } elsif ( /^DCREATED:.*/ ) { # skip } elsif ( /^LAST-MODIFIED:.*/ ) { # skip } elsif ( /^SEQUENCE:.*/ ) { # skip } elsif ( /^PRIORITY:.*/ ) { # skip } elsif ( /^TRANSP:.*/ ) { # skip } elsif ( /^X-PILOTID:.*/ ) { # skip } elsif ( /^X-PILOTSTAT:.*/ ) { # skip } elsif ( /^X-PILOT-NOTIME:.*/ ) { # skip } elsif ( /^ORGNAME:.*/ ) { # skip } elsif ( /^STATUS:.*/ ) { # skip } elsif ( /^CLASS:(.*)$/ ) { # skip # should probably allow different classes to be queried } elsif ( /^DTSTART:(\d{8})T(\d{4})..$/ ) { $dstart = $1; $tstart = $2; } elsif ( /^DTEND:(\d{8})T(\d{4})..$/ ) { $dend = $1; $tend = $2; } elsif ( /^SUMMARY:(.*)$/ ) { $desc = $1; } elsif ( /^SUMMARY;QUOTED-PRINTABLE:(.*)$/ ) { $desc = $1; } elsif ( /^EXDATE:(.*)$/ ) { $exdate = $1; } elsif ( /^RRULE:(.*)$/ ) { $rrule = $1; } else { die "Cannot handle $_ in $CALFILE line $., quitting"; } next; } elsif ( $state == 99 ) { # expecting end of file unless ( /^\s*$/ ) { warn "Ignoring $_ at end of file, continuing"; } } else { die "Unexpected internal error, \$state=$state, quitting"; } } exit 0; # $Log: ccal,v $ # Revision 1.6 2003/10/16 12:48:02 andras # week now day+7 (was +6) # # Revision 1.5 2003/10/03 15:03:37 andras # fixed interval overlap logic, added sorting # sorting not perfect, but will only mis-sort events with same starting # days and different ending days, and then only in cases like Mon > Fri # # Revision 1.4 2003/10/03 14:40:44 andras # added shortcut m|w|t|n options, code cleanups # # Revision 1.3 2003/10/03 07:21:20 andras # tries harder to locate calendar # # Revision 1.2 2003/10/03 07:14:18 andras # fixed input parsing, better pretty printing, added useful verbose comments # # Revision 1.1 2003/10/01 20:18:31 andras # Initial revision #