tags:

views:

559

answers:

1

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).

+1  A: 

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
Dennis Williamson
Thanks Dennis. I really appreciate your help.Unfortunately, my "date" command does not support the -d option. I'm running ksh on AIX 5.2. $ date -d "12/1/2009 -1 day" date: Not a recognized flag: d Usage: date [-u] [+"Field Descriptors"] $ I'll still click on the check-mark since your solution clearly works on other systems, and I didn't list my OS in the initial question.
Kevin Rettig
Hmmm... It took out my carriage returns when it posted my comment. I guess I need to read the docs on how to format my comments.
Kevin Rettig
Comments have very limited formatting - no CRs, for example.
Dennis Williamson

related questions