I always enjoy learning tricks from Dave Taylor's Work the Shell column, and I understand that sometimes the example problems need to be somewhat contrived to fit within an article.
However, I think he might be thinking too hard about how to determine the day of the week for a date in the past. I use this simple one-liner:
$ date -d "7/20/1969" +"%a" Sun
You also can use %w for a numeric 0–6 answer:
$ date -d "7/20/1969" +"%w" 0
I also use a variant of this command to convert from “seconds since epoch”, which often is found in log files, to a more readable date format:
$ date -d @1234567890 Fri Feb 13 18:31:30 EST 2009
As always, thanks for the tips, Dave. I hope you enjoy these in return.
—
Alan
Dave Taylor replies: Thanks for the message. The problem is that not all versions of Linux have this more sophisticated date command. I'm old-school. I have no problem with long, complicated solutions anyway.
I have been reading Dave Taylor's command-line column (Work the Shell) in Linux Journal for some time, and I think that, although his command-line solutions are, in many ways, quite useful, it looks like he seems to have some problems with creating algorithms to solve the core portion of the problems.
For example, I have no problem with his parsing of the input, or loop controls, in the calendar program, but I think I have come up with a much more effective solution to the portion of the problem related to the determination of the year, by looking at the problem in a different way.
I examined the problem and decided that you don't actually need to search for the day in the calendar itself, in order to determine where it is in its week. All you need to know is what day of the week it will fall on, if you look at the calendar of the given month.
To do this, you can examine a generic month and, from that, determine where in the week the month begins. So, I came up with the following solution.
Given: 1) month in which event occurred (month), 2) day of month on which event occurred (dom), 3) day of week on which $dom occurred (dow). Find: year in which $dom occurred on $dow, in $month (year).
1. Label the days of the week as follows: Sun = 6, Mon = 5, ..., Fri = 1, Sat = 0.
2. Assign the value for dow, using the appropriate value, from above.
3. Calculate how many days will be in the first week of the target month:
days=$(( $(( $dom + $dow - 1 )) % 7 + 1 ))
Now, you know how many days are in the first week of this calendar month, in the target calendar year ($days).
So, you can find out if the test month matches this value, like this (well, not really, but if you compare the two values, this will tell if you've found the right year). Also, this awk script is predicated on the fact that cal has its first line of days on the third line of its output (FNR == 3):
cal $month $year | awk "FNR == 3 { print NF }"
If this value equals $days, then the current value of $year is the correct year; otherwise, you will have to loop, decrementing $year (by one) at each iteration, until the value for $year is correct.
My version of the loop looks like this. Please see if you can make it better! Specifically, I ended up having to create another variable, $caldays (calendar days, the number of days in the first week of the test month). Notice that in order to make even this work, I had to enclose the entire thing in backticks, or I got errors:
while true ; do caldays=`cal $month $year | awk "FNR == 3 { print NF }"` if [ $caldays -eq $days ] ; then cal $month $year exit 0 # non-error exit else year=$(( $year - 1 )) fi done
By the way, the most years you will need to go back is ten (except, of
course, when $month=February and
$dom=29, in which case, you may have to
go back significantly farther, as this condition can
occur only in a
year divisible by four ($year %4 -eq 0))! Also, this version of the program
actually prints the calendar for the target month (cal $month
$year).
I just realized that this script does not check to make certain that the
month actually contains $dom, but that realistically should be checked
before searching for the existence of the target date, else the input
data is/are invalid—that is, September, April, June and November have only
30 days;
Feb has 28 (or 29, in leap years) days, and the rest have 31 days,
but I know you already knew that.
—
Dave Johnson
Dave Taylor replies: Thanks for your note and interesting solution. As with any script, there are a number of different solution paths. What I'm really trying to illustrate in my Work the Shell column is the “solution journey” more than how to find and implement the optimal algorithm. I knew from the get-go that there were better mathematical formulas I could use, and indeed, one colleague has assured me that there's a mathematical formula that will solve this puzzle without any invocations of cal or anything like that. I'm unable to find it with Google, but that's another story. In any case, thanks for your smart and interesting solution!
In the Letters section in the September 2011 issue, the Letter titled “What Day Is It?”, and the script provided therein, was written by Peter Ljubic (not Eric Miller). We apologize for the error.—Ed.
In our article, “Linux Standard Base: State of Affairs”, in the
August 2011 issue, one of our timeline
graphics reported the addition of Java to LSB 4.0, without mentioning that it
was added as a “trial-use” standard (proposed for inclusion, but not
required). We regret the error.
—
Jeff Licquia
This was me, not too long ago, over Aptos, California (Monterey Bay), near my home. It was my fourth tandem jump, but when I went back and looked at the pics, I thought “this should be in LJ!” It's my favorite LJ shirt, “May the source be with you”.