#!/usr/bin/perl -w # # calpost v0.01 --crb3 03jun02/27jun02: # accept a filename as argument. compose and display a month-of-weeks # HTML calendar with the indicated file's text listed within their days. # immediate applications: # - dinner menu # - astrolog's horary output (astrolog -dm (offshoot is ascalpost.cgi)) # - calendar display for PlotLiner # 08oct02: too quick a hack. fixed bugs in commandline switch handling, # doy2date. # # for now, take the arg on the commandline rather than GET or POST or # other ENV clues from apache. # # v0.2 --crb3 02Oct06 # - bring getopts section up to current use # - alternate months colors # - title # - sigline at bottom # - highlight days with events in 'em --crb3 03Oct06 # # the "unless defined" phrasing allows params to be set in another # scripts which invokes this one by 'do'. # # 17Nov07: fix is_leapyr # $outfname="-" unless defined $outfname; # default to stdout $firstday=1 unless defined $firstday; # sunday==0 $wks2do="*" unless defined $wks2do; $idy=$imo=$iyr=$idow=$idoy=0; # integer date, day-of-week/year values $bnw=0 unless defined $bnw; $tsize="-1" unless defined $tsize; @dys = qw/sun mon tue wed thu fri sat/; @mons = qw/jan feb mar apr may jun jul aug sep oct nov dec/; @mondys=(0, 31, (28+31), (31+28+31), (30+31+28+31), (31+30+31+28+31), (30+31+30+31+28+31), (31+30+31+30+31+28+31), (31+31+30+31+30+31+28+31), (30+31+31+30+31+30+31+28+31), (31+30+31+31+30+31+30+31+28+31), (30+31+30+31+31+30+31+30+31+28+31), (31+30+31+30+31+31+30+31+30+31+28+31)); # # get options. # stop when the switches quit. switch-args can be DOS-style (packed # against switch char) or UNIX-style (separated from switch by space). # multiple switches per flag are not supported. # $altmo=1 unless defined $altmo; $hidys=1 unless defined $hidys; $chatty=$shutup=$debug=0; while(defined($ARGV[0]) and index($ARGV[0],'-')==0){ $arg=shift(@ARGV); # get any the switches $key=substr($arg,1,1); # get no-arg switches first substr($arg,0,2)=""; if($key eq "v"){ # verbose? $chatty ^= 1; next; }elsif($key eq "q"){ # silent running? $shutup^=1; next; }elsif($key eq "D"){ # debug $debug^=1; next; }elsif($key eq "a"){ # alternating colors for months $altmo^=1; next; }elsif($key eq "h"){ # highlight days with events $hidys^=1; next; } $arg =~ s/^\=//; # handles switch=arg $arg=shift(@ARGV) if($arg eq "" and ($ARGV[0] !~ /^\-\w/) ); # handles space-separated switch/arg if($key eq "w"){ # how many weeks ('*' -- to last date's wk) $wks2do=$arg; }elsif($key eq "o"){ # output filename, else stdout $outfname=$arg; }elsif($key eq 'f'){ # first day of week (sun==0) $firstday=0+$arg; }elsif($key eq 't'){ # calendar title $title=$arg; }elsif($key eq 'T'){ # calendar text size $tsize=$arg; }else{ warn "$0: unrecognized option -$key $arg\n"; } } if(defined($ARGV[0])){ # ** DEBUGGING ** die "calpost infile\n where infile is text list sorted by days\n" unless defined $ARGV[0]; $infname=$ARGV[0]; # final arg has to be file to grab $outfname=$ARGV[1] if (defined($ARGV[1]) and !defined($outfname)); }else{ $infname="/home/crb3/crb3/perl/c/cal/ly-test.txt"; $outfname="-"; } $lastday=$firstday-1; $lastday += 7 if $lastday <0; $title=$infname unless defined $title; $hbgcolor="#FFFFFF" unless defined $hbgcolor; $pgbgcolor="#A9A9A9" unless defined $pgbgcolor; $daycolor="#CFCFCF" unless defined $daycolor; $adycolor="#C0C0C0" unless defined $adycolor; $hdycolor="#FFFFCC" unless defined $hdycolor; if($bnw){ # black-and-white switch $hbgcolor=$pgbgcolor=$daycolor=$adycolor=$hdycolor="#FFFFFF"; } $motog=0; # toggle for month-colors $hname="UNDEF"; # # grab the input file and start packing the entries into a hash. # the key is YEAR_DAYOFYEAR, for sorting, and the value is # the day's events as an HTMLized string. # open(IFIL,"<$infname") or die "can't open infile $infname\n"; while(defined($inline=)){ chomp $inline; next if($inline =~ /^\s*[\#\;]/ or $inline =~ /^\s*$/); if($inline =~ /^\-?\s*\d{2}\-?[A-Za-z]{3}\-?\d{2,4}/){ # MILdate $inline =~ s/\:.*$//; # shave it. date MUST be on separate line. $inline =~ s/^\-\s+//; # shave any outline flag-char ($iyr,$imo,$idy)=MIL2day($inline); $idoy=date2doy($iyr,$imo,$idy); $hname=sprintf("%4.04d_%3.03d",$iyr,$idoy); $d{$hname}=""; # start a new entry }else{ $inline =~ s/^\-\s//; # whack off outliner ticks $d{$hname} .= "$inline\n"; } } close(IFIL); # # process the seized data. does the first entry date fall on the # first day of this calendar's week? if not, back that date off. # this calendar consists of whole weeks only. # # @ddy = sort keys (%d); # find the earliest one $first = $ddy[0]; ($iyr,$idoy)=split('_',$first,2); $idow=date2dow(doy2date($iyr,$idoy)); # what day-of-week? if($firstday != $idow){ # same as calendric first-day? $idow += 7 if($idow < $firstday); # if not, find that date. $dif = $idow - $firstday; $idoy -= $dif; if($idoy < 0){ # step back the calendar start $iyr--; $idoy += (is_leapyr($iyr) ? 366 : 365); } } if($wks2do eq '*'){ $last=$ddy[$#ddy]; ($lyr,$ldoy)=split('_',$last,2); $ldow=date2dow(doy2date($lyr,$ldoy)); # what day-of-week? $dif = $ldoy - $idoy; $yrs = $lyr - ($dyr=$iyr); while($yrs--){ $dif += (is_leapyr($dyr) ? 366 : 365); $dyr++; } $wks2do = int($dif / 7); if($ldow != $lastday){ # same as calendric first-day? $wks2do++; } }else{ $wks2do=0+$wks2do; # coerce to numeric for compares } # # showtime. generate the calendar. yeah, it's a bit cheesy-looking, with the # explicit day-and-date atop each day's entries, but it's quick and # unambiguous, and it's enough (for me) to be able to visualize defined plot # events. it's useful as-is as a design tool, and that's the target. # indents are verbatim, for readability of output. # open(OFIL,">$outfname") or die "can't make outfile $outfname\n"; print OFIL < $title

$title

EOT for($a=$firstday,$b=0;$b<7;$a++,$a %= 7, $b++){ print OFIL " \n"; } print OFIL " \n"; $dodaycolor=$daycolor; for($wkcnt=0;$wkcnt<$wks2do;$wkcnt++){ # WEEKS print OFIL " \n"; for($dycnt=0;$dycnt<7;$dycnt++){ # 7DAYS ($y,$m,$d)=doy2date($iyr,$idoy); if($altmo and ($d==1)){ $motog ^= 1; $dodaycolor=($motog ? $adycolor : $daycolor); } $hname=sprintf("%4.04d_%3.03d",$iyr,$idoy); $MIL=sprintf("%2.02d%s%4.04d",$d,$mons[$m-1],$y); $Mdow=$dys[date2dow($y,$m,$d)]; $thiscolor = ( ($hidys and exists($d{$hname})) ? $hdycolor : $dodaycolor); print OFIL " \n"; $idoy++; # increment to next day. if($idoy > (is_leapyr($iyr) ? 366 : 365)){ $iyr++; $idoy=1; } } print OFIL " \n"; } print OFIL <
$dys[$a]
\n"; print OFIL "

$Mdow $MIL

\n"; if(exists($d{$hname})){ print OFIL " \n ",$d{$hname},"
\n"; }else{ print OFIL "  \n"; } print OFIL "
 
generated from $infname by calpost-0.2 --crb3 02Oct06
EOT close(OFIL); ###-------------------- sub is_leapyr { my $y=abs(shift(@_)); return(0) if($y%4); return(0) if(!($y%100) and !($y%400)); return(1); } # # The following formula, which is for the Gregorian calendar only, may be # more convenient for computer programming. Note that in some programming # languages the remainder operation can yield a negative result if given a # negative operand, so mod 7 may not translate to a simple remainder. # # W = (k + floor(2.6m - 0.2) - 2C + Y + floor(Y/4) + floor(C/4)) mod 7 # # where floor() denotes the integer floor function, # k is day (1 to 31) # m is month (1 = March, ..., 10 = December, 11 = Jan, 12 = Feb) # Treat Jan & Feb as months of the preceding year # C is century (1987 has C = 19) # Y is year (1987 has Y = 87 except Y = 86 for Jan & Feb) # W is week day (0 = Sunday, ..., 6 = Saturday) # # Here the century and 400 year corrections are built into the formula. # The floor(2.6m - 0.2) term relates to the repetitive pattern that the # 30-day months show when March is taken as the first month. # -- Kevin Johnson (kevin@mercury.ig.utexas.edu) # in a sci.math posting 1997/04/28 # # this has a good range of a coupla centuries, anyway. below oct1752, # the GNU 'cal' differs markedly. # # date2dow. # take integer date (yr4, mo[1-12], dy[1-31?]), return day-of-week[0-6]. # sub date2dow { my($y,$m,$d)=@_; my($c,$k); # print "date2dow: m=$m, d=$d, y=$y\n" if $chatty; #setup $y-- if($m<3); $c = int($y/100); $y %= 100; $m--; # months go 0-based $m += 10; $m %= 12; # roll to 0=march $m++; # back to 1-based #alg $k = $d; $k += int(($m*2.6)-0.2); $k -= 2*$c; $k += $y; $k += int($y/4); $k += int($c/4); $k %= 7; # 0=sun return($k); } # # MIL2day. # convert a MIL date, with or without dashes, to day, month and year integers. # # sub MIL2day { my $mil=shift(@_); my($yr,$tmo,$mo,$dy); $mil =~ s/\^.+$//; # whack off any time (PlotLiner formatting) $mil =~ s/\-//g; # compact it from 00-mon-00 to 00mon00 # (just to reduce it to one format) $dy=substr($mil,0,2); $yr=substr($mil,5); $tmo=lc(substr($mil,2,3)); # lowercase it for comparison $mo=0; while($mons[$mo] ne $tmo and $mo < 13){ $mo++; } $mo++; # jan0 -> jan1 if($yr < 1000){ $yr += ($yr < 50 ? 2000 : 1900); # expand 49 to 2049, 50 to 1950. } return($yr,$mo,$dy); } # # date2doy. # given three integers for day, month and fullyear, where 01jan is 1,1, # return the date's day-of-the-year count, where 01jan is 1. # sub date2doy { my($yr,$mo,$dy)=(@_); my($doy); $mo--; # back to 0-based $doy=$mondys[$mo]+$dy; # index into lengths-table $doy++ if(is_leapyr($yr) and $mo>1); # adjust for leapyear return($doy); } # # doy2date. # given year and day-of-year count, return yr,mo,dy. # sub doy2date { my $yr=0; my $doy=0; $yr += shift(@_); # coerce incoming args to numeric $doy += shift(@_); my($mo,$dy); my $skip=0; if(is_leapyr($yr)){ # leapyear adjustments if($doy == $mondys[2]+1){ $mo=2; $dy=29; $skip=1; }elsif($doy > $mondys[2]){ # ly adjust $doy--; } } unless($skip){ for($mo=0;$mo<12;$mo++){ last if $doy <= $mondys[$mo+1]; } $dy=$doy-$mondys[$mo]; $mo++; } return($yr,$mo,$dy); } __END__ - open infile. for starts, expect dates to be MIL2year or MIL4year. - determine earliest date. - by default, determine last date, thus how many weeks to display. - switch for first-day-of-display-week - start composing calendar, using tables list file format: # comments the line 00mon00: <-- MIL2year ('27aug70') -or- 00mon0000: <-- MIL4year ('30apr2003') ...the colon is ignored if found. - item_details ...the '- ' is ignored if found. both are human readability aids. ...'item_details' is copied verbatim as the text of the line. this version assumes that the text file is relatively short, and sucks it all in. that can be refined to take in only five or so weeks' worth of stuff. a 2year (2-digit year) value will be expanded into 2000+ if it's under 50, into 1900+ otherwise. in a decade or two you might want to adjust that.