cal's unfortunate problem with argument handling

July 2, 2016

Every so often I want to see a calendar just to know things like what day of the week a future date will be (or vice versa). As an old Unix person, my tool for this is cal. Cal is generally a useful program, but it has one unfortunate usage quirk that arguably shows a general issue with Unix style argument handling.

By default, cal just shows you the current month. Suppose that you are using cal at the end of June, and you decide that you want to see July's calendar. So you absently do the obvious thing and run 'cal 7' (because cal loves its months in decimal form). This does not do what you want; instead of seeing the month calendar for July of this year, you see the nominal full year calendar for AD 7. To see July, you need to do something like 'cal 7 2016' or 'cal -m 7'.

On the one hand, this is regrettably user hostile. 'cal N' for N in the range of 1 to 12 is far more likely to be someone wanting to see the given month for the current year than it is to be someone who wants to see the year calendar for AD N. On the other hand, it's hard to get out of this without resorting to ugly heuristics. It's probably equally common to want a full year calendar from cal as it is to want a different month's calendar, and both of these operations would like to lay claim to the single argument 'cal N' invocation because that's the most convenient way to do it.

If we were creating cal from scratch, one reasonably decent option would be to declare that all uses of cal without switches to explicitly tell it what you wanted were subject to heuristics. Then cal would have a license to make 'cal 7' mean July of this year instead of AD 7, and maybe 'cal 78' mean 'cal 1978' (cf the note in the V7 cal manpage). If you really wanted AD 7's year calendar, you'd give cal a switch to disambiguate the situation; in the mean time, you'd have no grounds for complaint. But however nice it might be, this would probably strike people as non-Unixy. Unix commands traditionally have predictable argument handling, even if it's not friendly, because that's what Unix considers more important (and also easier, if we're being honest).

In a related issue, I have now actually read the manpages for modern versions of cal (FreeBSD and Linux use different implementations) and boy has it grown a lot of options by now (options that will probably make my life easier if I can remember them and remember to use them). Reassuringly, the OmniOS version of cal still takes no switches; it's retained the V7 'cal [[month] year]' usage over all of these years.

Comments on this page:

By Jinks at 2016-07-03 09:11:21:

I think that there is an argument to be made that cal's behaviour is wrong since it violates the principle of least surprise.

Cal without arguments produces the calendar for the current month, so it's, in my opinion, reasonable to assume that an unqualified argument (for $1 < 12) would do the same but for the supplied month.

Not that it matters now, 30 years later. The behaviour of cal is set in stone just by virtue of "it's always been like that" now.

That's why I have this function. ;)

   mcal() { cal "$@" $(date +%Y) }

Funny, Christian, this is my ~/bin/dcal (as in “DWIM cal”):

v=$(( $1 + 0 ))
y=`date +%Y`
if   (( v <= 12 && v >= 1 ))   ; then cal $v $y
elif (( v <= ((y % 100)+40) )) ; then cal $(( (y/100)  *100 + v ))
elif (( v <= 99 ))             ; then cal $(( (y/100-1)*100 + v ))
else cal "$@"

I guess the Unix ethos is that if you want user-friendly UI, you sprinkle it on top

By James (trs80) at 2016-07-04 11:49:05:

The argument for "it's near the end of the month" is -3. I pass it by muscle memory default now, it only bites me on OS X (but then I don't use Solaris these days, or *BSD ever).

By Ewen McNeill at 2016-07-04 19:03:51:

I think it'd be fair to say that cal violates the Principle of Least Surprise in several ways -- it has an optional (positional) argument which is prepended to the command line, which is very un-unixy. And it changes from "display month" to "display year" mode when given a single (positional) argument. I expect this behaviour dates from well before anyone really thought about UI carefully.

Typically I find that I want to see the whole year calendar, so I ended up making myself a caly command, which displays the current year's calendar, and teaching myself to type that instead. But I do like the dcal idea (even if not the name as much).

The unixy way for cal to operate I think would be to take "-y" and "-m" for year and month, both optional, both in either order, with the defaults if omitted being the current value. And probably to use the "1-12 = month if unqualified, otherwise year if unqualified" heuristic to "do what I mean". (It looks like the MacOS/Linux versions do all of that, except the 1-2 heuristic; and AFAICT adding that heuristic breaks almost nothing in real life.)


By Mike G. at 2016-07-05 15:04:43:

To add to the fun, on the version of cal installed on Linux boxes here, the -m switch makes Monday the first day of the week. So you still get the calendar for 7 AD if you do 'cal -m 7'...

By vaclav at 2016-07-11 09:29:56:

gcal 7

Written on 02 July 2016.
« How backwards compatibility causes us pain with our IMAP servers
An irritating little bug in the latest GNU Emacs Python autoindent code »

Page tools: View Source, View Normal, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sat Jul 2 23:39:50 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.