Today in History, Brought to You by UNIX

·

I accidentally came across a very interesting UNIX utility, calendar. Despite its name, it does not actually show you a calendar (there’s cal for that), but rather, it shows you past events that occurred on any given day. The calendar program is pre-installed on BSD systems (including macOS). If you’re on Linux, you might need to install it:

  • Ubuntu: sudo apt install bsdmainutils
  • Fedora: sudo dnf install calendar

You can then create a calendar file like so:

mkdir -p ~/.calendar
echo "#include <calendar.world>" > ~/.calendar/calendar

Then, just run calendar. Here’s what today’s entries look like:

Sep 10 	National Day in Belize
Sep 10 	Moon Festival in Taiwan
Sep 10 	Korean Thanksgiving Day (Chusuk) in South Korea
Sep 10 	Gandalf escapes from Orthanc
Sep 10 	Mountain Meadows Massacre.  Mormons kill Gentile wagon train, 1857
Sep 11 	National Holiday in Chile
Sep 11 	Ethiopian New Year in Ethiopia
Sep 11 	Anniversary of military coup in Chile
Sep 11 	Terrorists destroy World Trade Center in New York, 2001
Sep 11 	CIA-sponsored terrorists overthrow Chilean government, murder President Allende, 1973

You can #include additional calendars - the full list can be seen via ls /usr/share/calendar - or add your own events in the same format.

To get daily reminders of your calendar, run sudo crontab -e and add the following:

0 12 * * * calendar -a

This will mail you everyday at 12 PM with a notification in your terminal, which you can read by running mail and hitting the return key, followed by q and return to quit.

History

The original version of calendar was released with V7 UNIX, which was the last UNIX to come out of Bell Labs. It had a very short man page, with the following description:

Calendar consults the file ‘calendar’ in the current directory and prints out lines that contain today’s or tomorrow’s date anywhere in the line. Most reasonable month-day dates such as ‘Dec. 7,’ ‘december 7,’ ‘12/7,’ etc., are recognized, but not ‘7 December’ or ‘7/12’. On weekends ‘tomorrow’ extends through Monday.

When an argument is present, calendar does its job for every user who has a file ‘calendar’ in his login directory and sends him any positive results by mail(1). Normally this is done daily in the wee hours under control of cron(8).

It essentially reads a file like this:

Jan. 1 New Year
Jan. 2 Break New Year's resolutions

and prints out the days that match today and tomorrow.

I searched for the original implementation out of curiosity, and found it on the helpful UNIX history repo:

/* /usr/lib/calendar produces an egrep -f file
   that will select today's and tomorrow's
   calendar entries, with special weekend provisions

   used by calendar command
*/
#include <time.h>

#define DAY (3600*24L)

char *month[] = {
	"[Jj]an",
	"[Ff]eb",
	"[Mm]ar",
	"[Aa]pr",
	"[Mm]ay",
	"[Jj]un",
	"[Jj]ul",
	"[Aa]ug",
	"[Ss]ep",
	"[Oo]ct",
	"[Nn]ov",
	"[Dd]ec"
};
struct tm *localtime();

tprint(t)
long t;
{
	struct tm *tm;
	tm = localtime(&t);
	printf("(^|[ (,;])((%s[^ ]* *|%d/)0*%d)([^0123456789]|$)\n",
		month[tm->tm_mon], tm->tm_mon + 1, tm->tm_mday);
}

main()
{
	long t;
	time(&t);
	tprint(t);
	switch(localtime(&t)->tm_wday) {
	case 5:
		t += DAY;
		tprint(t);
	case 6:
		t += DAY;
		tprint(t);
	default:
		t += DAY;
		tprint(t);
	}
}

A surprisingly short file. At first glance, it doesn’t seem to be reading anything and just prints a few lines. C has unbreakable backwards compatibility, so we can try to compile and run it:

cc -o calendar calendar.c && ./calendar

The output:

(^|[ (,;])(([Ss]ep[^ ]* *|9/)0*10)([^0123456789]|$)
(^|[ (,;])(([Ss]ep[^ ]* *|9/)0*11)([^0123456789]|$)

This is when the comment at the top of the file comes in handy:

/* /usr/lib/calendar produces an egrep -f file
   that will select today's and tomorrow's
   calendar entries, with special weekend provisions
   used by calendar command
*/

It prints a fancy regex to be used by egrep for selecting today’s and tomorrow’s date. Where’s egrep getting called? In /usr/bin/calendar:

PATH=/bin:/usr/bin
tmp=/tmp/cal$$
trap "rm $tmp; exit" 0 1 2 13 15
/usr/lib/calendar >$tmp
case $1 in
-)
	sed '
		s/\([^:]*\):.*:\(.*\):[^:]*$/y=\2 z=\1/
	' /etc/passwd \
	| while read x
	do
		eval $x
		if test -r $y/calendar; then
			egrep -f $tmp $y/calendar 2>/dev/null  | mail $z
		fi
	done;;
*)
	egrep -f $tmp calendar
esac

The shell script does a couple of interesting things. First, it uses a trap command to run cleanup actions on exit, which in this case is to remove the temporary file that will hold the regexes. The file is created on the next line via /usr/lib/calendar which is the short C program we just compiled. Then, it checks if an argument is given, and if so, runs sed on /etc/passwd with yet another regex which returns each line as y=/home/dir z=username. This is then piped into a while loop and evald to get them as separate variables. Finally, a check for the calendar file is done in each user’s home directory, and if it exists, egrep is run on it via the regex file and the resulting events of the day are mailed to the user.

The implementation is interesting because it’s an excellent example of composition and the UNIX philosophy. It uses C for the core logic, egrep for filtering, sed for querying, mail for notifications and finally cron for running it all.


At some point during the development of 4.1BSD, someone came up with the clever idea of running the calendar file through cpp, the C preprocessor, taking the composition approach even further. This two-line change would allow “a global data base of dates to be included in users’ calendar files” as the commit mentions. Users could now use existing calendars, such as ones for holidays and world events, with a simple #include.