Puzzle #2: cal(1)

(I've been fixing little smf(5) bugs, as well as revising our documentation, presentations and--most importantly--more block diagrams for this blog. But I bumped into an annoyance and thought I should share.)

As an young old-school Unix developer, I tend to live in terminal windows. One of my favourite commands is cal(1), which has a great default mode:

$ cal
   September 2004
 S  M Tu  W Th  F  S
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

But if you want to see an October calendar, you might get confused:

$ cal 10



                                10

         Jan                    Feb                    Mar
 S  M Tu  W Th  F  S    S  M Tu  W Th  F  S    S  M Tu  W Th  F  S
          1  2  3  4                      1                      1
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    2  3  4  5  6  7  8
12 13 14 15 16 17 18    9 10 11 12 13 14 15    9 10 11 12 13 14 15
19 20 21 22 23 24 25   16 17 18 19 20 21 22   16 17 18 19 20 21 22
26 27 28 29 30 31      23 24 25 26 27 28      23 24 25 26 27 28 29
                                              30 31
         Apr                    May                    Jun
 S  M Tu  W Th  F  S    S  M Tu  W Th  F  S    S  M Tu  W Th  F  S
       1  2  3  4  5                1  2  3    1  2  3  4  5  6  7
 6  7  8  9 10 11 12    4  5  6  7  8  9 10    8  9 10 11 12 13 14
13 14 15 16 17 18 19   11 12 13 14 15 16 17   15 16 17 18 19 20 21
20 21 22 23 24 25 26   18 19 20 21 22 23 24   22 23 24 25 26 27 28
27 28 29 30            25 26 27 28 29 30 31   29 30

         Jul                    Aug                    Sep
 S  M Tu  W Th  F  S    S  M Tu  W Th  F  S    S  M Tu  W Th  F  S
       1  2  3  4  5                   1  2       1  2  3  4  5  6
 6  7  8  9 10 11 12    3  4  5  6  7  8  9    7  8  9 10 11 12 13
13 14 15 16 17 18 19   10 11 12 13 14 15 16   14 15 16 17 18 19 20
20 21 22 23 24 25 26   17 18 19 20 21 22 23   21 22 23 24 25 26 27
27 28 29 30 31         24 25 26 27 28 29 30   28 29 30
                       31
         Oct                    Nov                    Dec
 S  M Tu  W Th  F  S    S  M Tu  W Th  F  S    S  M Tu  W Th  F  S
          1  2  3  4                      1       1  2  3  4  5  6
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    7  8  9 10 11 12 13
12 13 14 15 16 17 18    9 10 11 12 13 14 15   14 15 16 17 18 19 20
19 20 21 22 23 24 25   16 17 18 19 20 21 22   21 22 23 24 25 26 27
26 27 28 29 30 31      23 24 25 26 27 28 29   28 29 30 31
                       30

It's an interesting UI choice to assume that anyone would want the calendar for the year 10 C.E. Certainly I never do, and I'm pretty sure someone would have told me if UNIX systems were the professional historian's first choice for computing...

So today's puzzle is to make cal(1) more usable. If I enter cal month_num, give me the current month; If I enter "cal now" give me the 3-month window around the current month, like so:

$ cal now
   August 2004             September 2004          October 2004
 S  M Tu  W Th  F  S     S  M Tu  W Th  F  S     S  M Tu  W Th  F  S
 1  2  3  4  5  6  7              1  2  3  4                    1  2
 8  9 10 11 12 13 14     5  6  7  8  9 10 11     3  4  5  6  7  8  9
15 16 17 18 19 20 21    12 13 14 15 16 17 18    10 11 12 13 14 15 16
22 23 24 25 26 27 28    19 20 21 22 23 24 25    17 18 19 20 21 22 23
29 30 31                26 27 28 29 30          24 25 26 27 28 29 30
                                                31

Other than that, all other standard invocations of cal(1) should work as usual.

My example solution is a couple dozen line ksh(1) shell function, and I'll post it along with the best submissions. (Perl folks: no non-core modules, please.)

Comments:

A really gross first attempt is something like this:
$ cat cal-wrapper 
#!/bin/sh

if [ $# -eq 1 ]; then
    case $1 in
    [0-9]|[0-9][0-9])
        cal $1 `date '+%Y'` ;;
    now)
        month=`date '+%m' | sed 's/\^0//'`
        prev=$(( ( $month -1 - 1 ) % 12 + 1 ))
        [ $prev -lt 1 ] && prev=$(( $prev + 12 ))
        next=$(( ( $month -1 + 1 ) % 12 + 1 ))

        for lc in 1 2 3 4 5 6 7 8 ;do
            sh $0 $prev  | head -$lc | tail -1 | awk '{printf "%-22s",$0}'
            sh $0 $month | head -$lc | tail -1 | awk '{printf "%-22s",$0}'
            sh $0 $next  | head -$lc | tail -1 | awk '{printf "%-22s",$0}'
            echo ''
        done
        ;;
    \*)
        cal "$1" ;;
    esac
else
    cal "$@"
fi
The single month trick was easy to do with date(1). I have to admit that the multiple invocations of cal(1) when the "now" mode is invoked though are ugly. I might think of something better tomorrow, after I get some more sleep :-)

Posted by gkeramidas on September 29, 2004 at 10:13 AM PDT #

Here's my entry. I started doing this in the Bourne shell, but halfway through I felt just too dirty writing it. So, I whipped this up in Perl, instead. Enjoy (and if you think this is too verbose, be happy that I didn't "use strict;".... at least I didn't write it in lisp).

#!/usr/bin/perl -w

$real_cal = "/usr/bin/cal";

if (@ARGV != 1 || $ARGV[0] ne "now") {
        exec($real_cal, @ARGV) || die "could not exec $real_cal: $!\\n";
}

# Set $months to be our current year \* 12 + the current month of the year.
# Remember, $t[5] contains the number of years since 1900.  Also, $t[4] is
# the month of the year starting at zero (Jan == 0, Dec == 11).
@t = localtime(time());
$months = (($t[5] + 1900) \* 12) + $t[4];

# @cal will store the merged output of three months' output from cal(1).
@cal = ();

# Iterate over the three months for which we want data.
for $month ($months - 1, $months, $months + 1) {
        # append_cal wants the first argument to be an array reference,
        # the second argument to be the month, \*numbering from one, not zero\*,
        # and the last to be the year.
        append_cal(\\@cal, ($month % 12) + 1, int($month / 12));
}

# Print out our data, appending newlines.
foreach $line (@cal) {
        $line =~ s/\\s+$//;
        print $line, "\\n";
}

#
# append_cal - Append the calendar for the specified month and year to
#              an array of lines.
# Parameters:
#       $cal:   an array reference into which to store the output from cal(1).
#       $month: month of the year, suitable for passing directly to cal(1).
#               Note that this starts at one (Jan == 1, Dec == 12).
#       $year:  year, numbering from zero (2004 == 2004).
#
sub append_cal {
        my ($cal) = shift(@_);
        my ($month) = shift(@_);
        my ($year) = shift(@_);

        my ($fh);
        my ($index);
        my ($line);

        open($fh, "$real_cal $month $year|") ||
                die "opening pipe from cal: $!\\n";

        $index = 0;
        while (defined($line = <$fh>)) {
                chomp($line);

                if (!defined($$cal[$index])) {
                        $$cal[$index] = "";
                }

                $$cal[$index] .= sprintf("%-20s    ", $line);
        
                $index++;
        }

        close($fh);
}

Posted by DJ Gregor on September 29, 2004 at 02:41 PM PDT #

Ooops.. change that first <code>if</code> block to:

if (@ARGV == 1 && $ARGV[0] =~ m/\^\\d{1,2}$/) {
        exec($real_cal, $ARGV[0], (localtime(time()))[5] + 1900) ||
                die "could not exec $real_cal: $!\\n";
} elsif (@ARGV != 1 || $ARGV[0] ne "now") {
        exec($real_cal, @ARGV) || die "could not exec $real_cal: $!\\n";
}

Posted by DJ Gregor on September 29, 2004 at 02:48 PM PDT #

Heh. I've been meaning to write this for years, but never got around to it. :)

Here's a version in zsh, modeled after gkeramidas' solution, but gets the years wrapped properly, and uses two different constructs to get things side by side. I use zsh specifically for the =() construct, which sticks the contents of the output of the command inside in a temporary file, though once I'm using zsh, I take advantage of a few other features.

For the "now" clause, I've two ways to print the calendars side by side. The first, using sdiff, forks fewer processes, but I believe is likely to be more fragile, given the column cutting. The second, using join, is probably more robust, but forks eleven processes to do the job. Choose your poison.
#!/bin/zsh -f

PATH=/usr/bin

if [[ $# -eq 1 ]]; then
        case $1 in
        (<0-12>)
                cal $1 $(date +%Y)
                ;;
        ("now")
                this=( $(date +%m) $(date +%Y) )
                next=( $(( this[1] % 12 + 1)) $this[2] )
                prev=( $(( (this[1] - 2) % 12 + 1)) $this[2] )

                (( next[1] == 1 )) && (( ++next[2] ))
                (( prev[1] == 0 )) && (( --prev[2], prev[1] = 12 ))

                sdiff =(cal $=prev) =(sdiff -w 60 =(cal $=this) =(cal $=next)) | \\
                        cut -c 1-22,67-88,98-
                echo
                join -t$'\\t' -o 1.2,2.2 =(join -t$'\\t' -o 1.2,2.2 \\
                        =(cal $=prev | cat -n) =(cal $=this | cat -n) | expand -t 23 | cat -n) \\
                        =(cal $=next | cat -n) | expand -t 46
                ;;
        (\*)
                exec cal $0
                ;;
        esac
else
        exec cal $@
fi

Posted by Danek Duvall on September 29, 2004 at 04:14 PM PDT #

After a bit of sleep I realized there was indeed a bug in the wrapping around of months to previous or next years. The correct solution in ksh(1) syntax would be something like this:
year=$(date '+%Y')
mon=$(date '+%m')

pmon=$(( ( $mon - 2 ) % 12 + 1 ))
[ $pmon -eq 0 ] && pmon=12
pyear=$year
[ $pmon -gt $mon ] && pyear=$(( $pyear - 1 ))

nmon=$(( $mon % 12 + 1 ))
nyear=$year
[ $nmon -lt $mon ] && nyear=$(( $nyear + 1 ))
There's yet another bug in what I had posted. I was sitting on a FreeBSD workstation and the $(( expression )) syntax worked fine with /bin/sh. On Solaris this has to be done with backquoting if sh(1) is used, otherwise ksh should be used as the intepreter.

Posted by Giorgos Keramidas on September 29, 2004 at 07:32 PM PDT #

And voila, here's a version that hopefully has no bugs, is written in ksh(1) to make use of the $(( expr )) syntax for brevity, and is faster:
#!/bin/sh

if [ $# -eq 1 ]; then
    case $1 in
    [0-9]|[0-9][0-9])
        cal $1 `date '+%Y'` ;;
    now)
        year=$(date '+%Y')
        mon=$(date '+%m')

        pmon=$(( ( $mon - 2 ) % 12 + 1 ))
        [ $pmon -eq 0 ] && pmon=12
        pyear=$year
        [ $pmon -gt $mon ] && pyear=$(( $pyear - 1 ))

        nmon=$(( $mon % 12 + 1 ))
        nyear=$year
        [ $nmon -lt $mon ] && nyear=$(( $nyear + 1 ))

        { cal "$pmon" "$pyear" | sed -e 's/\^/a:/' ; \\
          cal "$mon" "$year"   | sed -e 's/\^/b:/' ; \\
          cal "$nmon" "$nyear" | sed -e 's/\^/c:/' ; } | \\
        awk 'BEGIN { ja = jb = jc = 0;}
        /\^a:/ {av[ja] = substr($0,3); ja++;}
        /\^b:/ {bv[jb] = substr($0,3); jb++;}
        /\^c:/ {cv[jc] = substr($0,3); jc++;}
        END {
          jmax = ja;
          if (jb > jmax) {jmax = jb;}
          if (jc > jmax) {jmax = jc;}
          for (ja = 0; ja < jmax; ja++) {
            printf "%-22s%-22s%-22s\\n", av[ja],bv[ja],cv[ja];
          }
        }'
        ;;
    \*)
        cal "$1" ;;
    esac
else
    cal "$@"
fi
This one uses three string arrays in awk(1) to store the output of cal(1) as it comes in and saves a hell of a lot of process invocations from my first attempt. That's probably why it is a lot faster too. That was fun playing with :-)

Posted by Giorgos Keramidas on September 29, 2004 at 07:52 PM PDT #

#!/bin/perl # # You are not supposed to understand this, just be in awe of my perl-foo. # use strict; use warnings; my ($m, $y, @my) = (localtime())[4, 5]; $m += 1; $y += 1900; if (@ARGV == 1 && $ARGV[0] eq 'now') { $my[1] = [ $m, $y ]; } elsif (@ARGV == 1 && $ARGV[0] =~ /\^\\d+$/ && $ARGV[0] >= 1 && $ARGV[0] <= 12) { $my[1] = [ $ARGV[0], $y ]; } else { exec('/usr/bin/cal', @ARGV) || die("Can't exec cal: $!\\n"); } $_ = $my[1][0] - 1; @{$my[0]} = $_ == 0 ? ( 12, $my[1][1] - 1 ) : ( $_, $my[1][1] ); $_ = $my[1][0] + 1; @{$my[2]} = $_ == 13 ? ( 1, $my[1][1] + 1 ) : ( $_, $my[1][1] ); my @cal = map([ map({ chomp($_); $_ } qx{/usr/bin/cal $_->[0] $_->[1]}) ], @my); for ($_ = 0; $_ < 7; $_++) { printf("%-20s %-20s %-20s\\n", map(shift(@{$_}) || '', @cal)); } exit(0);

Posted by Alan Burlison on September 30, 2004 at 11:51 PM PDT #

Again, with pre tags, sigh.
#!/bin/perl
#
# You are not supposed to understand this, just be in awe of my perl-foo.
#
use strict;
use warnings;
my ($m, $y, @my) = (localtime())[4, 5]; $m += 1; $y += 1900;
if (@ARGV == 1 && $ARGV[0] eq 'now') {
	$my[1] = [ $m, $y ];
} elsif (@ARGV == 1 && $ARGV[0] =~ /\^\\d+$/ && $ARGV[0] >= 1 && $ARGV[0] <= 12) {
	$my[1] = [ $ARGV[0], $y ];
} else {
	exec('/usr/bin/cal', @ARGV) || die("Can't exec cal: $!\\n");
}
$_ = $my[1][0] - 1;
@{$my[0]} = $_ == 0 ? ( 12, $my[1][1] - 1 ) : ( $_, $my[1][1] );
$_ = $my[1][0] + 1;
@{$my[2]} = $_ == 13 ? ( 1, $my[1][1] + 1 ) : ( $_, $my[1][1] );
my @cal = map([ map({ chomp($_); $_ } qx{/usr/bin/cal $_->[0] $_->[1]}) ], @my);
for ($_ = 0; $_ < 7; $_++) {
	printf("%-20s   %-20s   %-20s\\n", map(shift(@{$_}) || '', @cal));
}
exit(0);

Posted by Alan Burlison on September 30, 2004 at 11:53 PM PDT #

A further slight simplification is possible:
my @cal = map([ map({ chomp($_); $_ } qx{/usr/bin/cal $_->[0] $_->[1]}) ], @my);
can be replaced with:
my @cal = map([ map({ chomp($_); $_ } qx{/usr/bin/cal $_->[0] @{$_}, @my);
I've been able to reduce this to is 460 bytes (448 if you discount the #! line), any less and I feel I might risk compromising readibility:
#!/bin/perl
($m,$y)=(localtime())[4,5];$m+=1;$y+=1900;$n=@ARGV;$_=$ARGV[0];if($n==1&&$_ eq'now'){$a[1]=[$m,$y];}elsif($n==1&&$_=~/\^\\d+$/&&$_>=1&&$_<=12){$a[1]=[$_,$y];}else{exec('/usr/bin/cal',@ARGV)||die($!);}$_=$a[1][0]-1;@{$a[0]}=$_==0?(12,$a[1][1]-1):($_,$a[1][1]);$_=$a[1][0]+1;@{$a[2]}=$_==13?(1,$a[1][1]+1):($_,$a[1][1]);@c=map([map({chomp($_);$_}qx{/usr/bin/cal @$_})],@a);for($_=0;$_<7;$_++){printf("%-20s   %-20s   %-20s\\n",map(shift(@$_)||'',@c));}
Note in particular how it takes care to still produce the correct output in a leap year when 1st Febuary falls on a Sunday - see that bit there on the second line?

Posted by Alan Burlison on October 01, 2004 at 12:24 AM PDT #

And if I win this competition I'd just like to remind you that you \*still\* haven't given me my prize for winning your sort(1) competition - yes \*I\* was that anonymous poster.

Posted by Alan Burlison on October 01, 2004 at 12:33 AM PDT #

Well, imho, readability is already out of the window if I have to scroll at the 400th column! But that's probably just me and my archaic habit of fitting everything in 80 columns to avoid the "neck pendulum effect" ;-)

Posted by gkeramidas on October 01, 2004 at 08:50 AM PDT #

Actually someone over on my blog has kindly fixed a bug in the version I posted here, and between us we've reduced it down to 393 bytes, or 380 bytes if you exclude the #! line and the trailing newline.

Posted by Alan Burlison on October 01, 2004 at 10:01 AM PDT #

<p/> I missed out on this last week, too busy travelling around. <p/> I think my solution is more verbose than most and it certainly isn't most efficient (too much forking to run printf), but since I spent some time on it I thought I would post it anyway.
#!/bin/ksh

if [ $# == 2 ]
then
        /usr/bin/cal $1 $2
        exit 0
elif [ $# == 1 ]
then
        if [ $1 != now ]
        then
                /usr/bin/cal $1 $(date '+%Y')
                exit 0
        fi
else
        /usr/bin/cal
        exit 0
fi
integer year=$(date '+%Y')
integer now=$(date '+%m')
integer prev=$now-1
integer next=$now+1
integer i=0
integer j=0

store_cal()
{
        export IFS="\\n"
        cal $1 $2 | while read line
        do
                out[$i]=$line
                i=$i+3
                j=$j+1
        done
}

store_cal $prev $year
i=1
store_cal $now $year
i=2
store_cal $next $year
i=0
while (( i < j ))
do
        printf "%-20s  %-20s  %-20s\\n" \\
            "${out[$i]}" "${out[$i+1]}" "${out[$i+2]}"
        i=$i+3
done

Posted by Gary on October 06, 2004 at 07:14 PM PDT #

This is my version, done on 10 minutes:

#!/usr/bin/env perl

my $arg = shift @ARGV;
my @years = ( );
my @months = ( );
my %output = ( );
my @lt = localtime; # see manual for localtime in perl but:
# index 5 is year minus 1900,
# index 4 is month minus 1

if (not defined $arg or $arg eq '') {
system ('/usr/bin/env cal');
exit (0);
} elsif ($arg =~ m/\^now$/i) {
foreach (($lt [4] - 1)..($lt [4] + 1)) {
if ($_ < 0) {
push @years, $lt [5] + 1900 - 1;
push @months, $_ + 11;
} elsif ($_ > 11) {
push @years, $lt [5] + 1900 + 1;
push @months, $_ - 11;
} else {
push @years, $lt [5] + 1900;
push @months, $_ + 1;
}
}
} elsif ($arg =~ m/\^[0-9]{0,2}$/ and $arg+0 >= 1 and $arg+0 <= 12) {
push @years, $lt [5] + 1900;
push @months, $arg+0;
} else {
die 'usage: ' . $0 . ' [<month> | now]';
}

foreach my $i (0..$#years) {
$output{$i} = [split ("\\n", `/usr/bin/env cal $months[$i] $years[$i]`)];
}

# -- join output
foreach my $row (0..7) { # max of each cal output are 8 rows
print join (' 'x3, map { $_ . ' 'x(20-length ($_)) } map { $output{$_}->[$row] } (0..$#years) ) . "\\n";
}

# my email: Antonio.Santos at pt.jazztel.com

Posted by Ant�nio Santos on October 07, 2004 at 12:46 AM PDT #

#!/usr/bin/env perl

my $arg = shift @ARGV;
my @years = ( );
my @months = ( );
my %output = ( );
my @lt = localtime; # see manual for localtime in perl but:
# index 5 is year minus 1900,
# index 4 is month minus 1

if (not defined $arg or $arg eq '') {
system ('/usr/bin/env cal');
exit (0);
} elsif ($arg =~ m/\^now$/i) {
foreach (($lt [4] - 1)..($lt [4] + 1)) {
if ($_ < 0) {
push @years, $lt [5] + 1900 - 1;
push @months, $_ + 11;
} elsif ($_ > 11) {
push @years, $lt [5] + 1900 + 1;
push @months, $_ - 11;
} else {
push @years, $lt [5] + 1900;
push @months, $_ + 1;
}
}
} elsif ($arg =~ m/\^[0-9]{0,2}$/ and $arg+0 >= 1 and $arg+0 <= 12) {
push @years, $lt [5] + 1900;
push @months, $arg+0;
} else {
die 'usage: ' . $0 . ' [<month> | now]';
}

foreach my $i (0..$#years) {
$output{$i} = [split ("\\n", `/usr/bin/env cal $months[$i] $years[$i]`)];
}

# -- join output
foreach my $row (0..7) { # max of each cal output are 8 rows
print join (' 'x3, map { $_ . ' 'x(20-length ($_)+1) } map { $output{$_}->[$row] } (0..$#years) ) . "\\n";
}

# Here it goes new version, was a +1 missing on the padding of spaces on output.

Posted by Antonio Santos on October 07, 2004 at 12:52 AM PDT #

This ksh script weighs in at just 375 characters (including the #! line). Your turn, Alan.
#!/bin/ksh -p
z=/usr/bin/cal
c(){ $z $\*|sed 's/$/ /'|expand -t 23
}
date "+%Y %m"|read y m
d=0${1%%now}
[[ $d -lt 13 && $d -gt 0 ]]&&exec $z $d ${2:-$y}
[[ $1 != "now" ]]&&exec $z $\*
set -A a $y $y $y
set -A b -- $((m-1)) $m $((m+1))
f[1]='a[0]=$((y-1));b[0]=12'
f[12]='a[2]=$((y+1));b[2]=1'
eval ${f[$m]}
eval paste `for i in 0 1 2; do echo '<(c '${b[$i]} ${a[$i]}')'
done`
(note: the whitespace in the sed substitution is a single tab)

Posted by Dave Powell on October 08, 2004 at 12:54 PM PDT #

Follow-up: doing away with some unnecessary quoting saves 4 characters, leaving 371.

Posted by Dave Powell on October 08, 2004 at 06:56 PM PDT #

Even more shuffling produces a 365 character solution:
#!/bin/ksh -p
z=/usr/bin/cal
c(){ sed 's/$/ /'|expand -t23
}
date +%Y\\ %m|read y m
d=0${1%%now}
[[ $d -lt 13 && $d -gt 0 ]]&&exec $z $d ${2:-$y}
[[ $1 = now ]]||exec $z $\*
set -A a $y $y $y
set -A b -- $((m-1)) $m $((m+1))
f[1]='a[0]=$((y-1));b[0]=12'
f[12]='a[2]=$((y+1));b[2]=1'
eval ${f[$m]}
eval paste `for i in 0 1 2; do echo "<($z ${b[$i]} ${a[$i]}|c)"
done`

Posted by Dave Powell on October 08, 2004 at 07:37 PM PDT #

There's a space after the 'cal\\' (icky, but it saves a character). 310 bytes.
#!/bin/ksh -p
z=/usr/bin/cal\\
c(){ sed 's/$/ /'|expand -t23
}
date +%Y\\ %m|read y m
d=0${1%%now}
[[ $d -lt 13&&$d -gt 0 ]]&&exec $z$d ${2:-$y}
[[ $1 = now ]]||exec $z$\*
set -A a 0 0 0
f[1]='a[0]=-1'
f[12]='a[2]=1'
eval ${f[m]}
eval paste `for i in 0 1 2; do echo "<($z$(((m+i+10)%12+1)) $((y+a[i]))|c)"
done`

Posted by Dave Powell on October 13, 2004 at 10:02 AM PDT #

Jesus. And they call \*me\* crazy, ... Why not change z=/usr/bin/cal\\ to z=/bin/cal If Solaris already has a symlink, you might use it after all, you can save 4 more characters :))

Posted by Jaime Cardoso on October 15, 2004 at 12:21 AM PDT #

#!/bin/ksh -p c() { cal $(($1%12+1)) $(($1/12)) | sed -e 's/$/ /' | expand -21 } [[ $# -ne 1 ]] && exec cal "$@" integer y=$(date +%Y) [[ $1 != now ]] && { [[ $1 -lt 13 ]] && exec cal $1 $y || exec cal $1 } integer m=$(date +%m) integer d=$((y\*12+(m-1))) exec paste <(c $((d-1))) <(c $((d))) <(c $((d+1)))

Posted by Anonymous on October 21, 2004 at 07:49 AM PDT #

#!/bin/ksh -p
c() {
		cal $(($1%12+1)) $(($1/12)) | sed -e 's/$/	/' | expand -21
}
[[ $# -ne 1 ]] && exec cal "$@"
integer y=$(date +%Y)
[[ $1 != now ]] && {
	[[ $1 -lt 13 ]] && exec cal $1 $y || exec cal $1
}
integer m=$(date +%m)
integer d=$((y\*12+(m-1)))
exec paste <(c $((d-1))) <(c $((d))) <(c $((d+1)))

Posted by Anonymous on October 21, 2004 at 07:51 AM PDT #

Post a Comment:
Comments are closed for this entry.
About

sch

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
External blogs