I've seen this question answered in other languages but not the Korn Shell. I need to prevent a script from being run on the last business day of the month (we can assume M-F are business days, ignore holidays).
This function works in Bash, Korn shell and zsh, but it requires a date
command (such as GNU date
) that has the -d option:
function lbdm { typeset lbdm ldm dwn m y; (( m = $1 + 1 )); if [[ $m = 13 ]]; then m=1; (( y = $2 + 1 )); else y=$2; fi; ldm=$(date -d "$m/1/$y -1 day"); dwn=$(date -d "$ldm" +%u);if [[ $dwn = 6 || $dwn = 7 ]]; then ((offset = 5 - $dwn)); lbdm=$(date -d "$ldm $offset day"); else lbdm=$ldm; fi; echo $lbdm; }
Run it like this:
$ lbdm 10 2009
Fri Oct 30 00:00:00 CDT 2009
Here is a demo script broken into separate lines and with better variable names and some comments:
for Month in {1..12} # demo a whole year
do
Year=2009
LastBusinessDay=""
(( Month = $Month + 1 )) # use the beginning of the next month to find the end of the one we're interested in
if [[ $Month = 13 ]]
then
Month=1
(( Year++ ))
fi;
# these two calls to date could be combined and then parsed out
# this first call is in "American" order, but could be changed - everything else is localized - I think
LastDayofMonth=$(date -d "$Month/1/$Year -1 day") # get the day before the first of the month
DayofWeek=$(date -d "$LastDayofMonth" +%u) # the math is easier than Sun=0 (%w)
if [[ $DayofWeek = 6 || $DayofWeek = 7 ]] # if it's Sat or Sun
then
(( Offset = 5 - $DayofWeek )) # then make it Fri
LastBusinessDay=$(date -d "$LastDayofMonth $Offset day")
else
LastBusinessDay=$LastDayofMonth
fi
echo "$LastDayofMonth - $DayofWeek - $LastBusinessDay"
done
Output:
Sat Jan 31 00:00:00 CST 2009 - 6 - Fri Jan 30 00:00:00 CST 2009 Sat Feb 28 00:00:00 CST 2009 - 6 - Fri Feb 27 00:00:00 CST 2009 Tue Mar 31 00:00:00 CDT 2009 - 2 - Tue Mar 31 00:00:00 CDT 2009 Thu Apr 30 00:00:00 CDT 2009 - 4 - Thu Apr 30 00:00:00 CDT 2009 Sun May 31 00:00:00 CDT 2009 - 7 - Fri May 29 00:00:00 CDT 2009 Tue Jun 30 00:00:00 CDT 2009 - 2 - Tue Jun 30 00:00:00 CDT 2009 Fri Jul 31 00:00:00 CDT 2009 - 5 - Fri Jul 31 00:00:00 CDT 2009 Mon Aug 31 00:00:00 CDT 2009 - 1 - Mon Aug 31 00:00:00 CDT 2009 Wed Sep 30 00:00:00 CDT 2009 - 3 - Wed Sep 30 00:00:00 CDT 2009 Sat Oct 31 00:00:00 CDT 2009 - 6 - Fri Oct 30 00:00:00 CDT 2009 Mon Nov 30 00:00:00 CST 2009 - 1 - Mon Nov 30 00:00:00 CST 2009 Thu Dec 31 00:00:00 CST 2009 - 4 - Thu Dec 31 00:00:00 CST 2009
Note: I discovered during testing that if you try to use this for dates around World War II that it fails due to wartime time zones like CWT and CPT.
Edit: Here's a version that should run on AIX and other systems that can't use the above. It should work on Bourne, Bash, Korn and zsh.
function lbdN { cal $1 $2 | awk 'NF == 0 {next} FNR > 2 {week = $0} END {num = split(week, days); lbdN = days[num]; if ( num == 1 ) { lbdN -= 2 }; if ( num == 7 ) { lbdN-- }; print lbdN }'; }
You may have to make adjustments if your cal
starts weeks on Monday.
Here's how you can use it:
month=12; year=2009 # if these are unset or null, the current month/year will be used
if [[ $(date +%d) == $(lbdN $month $year) ]];
then
echo "Don't do stuff today"
else
echo "It's not the last business day of the month"
fi
making appropriate adjustments for your shell's if
...then
syntax, of course.
Edit: Bug Fix: The previous version of lbdN
failed when February ends on Saturday the 28th because of the way it used tail
. The new version fixes that. It uses only cal
and awk
.
Edit: For completeness, I thought it would be handy to include functions for the first business day of the month.
Requires date
with -d
:
function fbdm { typeset dwn d; dwn=$(date -d "$1/1/$2" +%u); d=1; if [[ $dwn = 6 || $dwn = 7 ]]; then (( d = 9 - $dwn )); fi; echo $(date -d "$1/$d/$2"); }
For May 2010:
Mon May 3 00:00:00 CDT 2010
Requires cal
and awk
only:
function fbdN { cal $1 $2 | awk 'FNR == 3 { week = $0 } END { num = split(week, days); fbdN = days[1]; if ( num == 1 ) { fbdN += 2 }; if ( num == 7 ) { fbdN++ }; print fbdN }'; }
For August 2010:
2