Finding the nth Particular Week in a Month – shell script

I see lot of request posted in internet to find out the day of nth week in a Month.

example:
what is the date of 3rd Sunday in October
What is the date of 2nd Friday in June 2012
what is the date of 4th Saturday in January 2011..etc..

The below shell script is used to find out the days for the nth particular week in a month.
Try it out and shout if you find any issues� (Script is tested in bash shell. OS � linux(ubuntu))

#!/bin/bash
###################################
# Script Name : finday.sh                                               
# Author : Kamaraj Subramanian                                 
# Website : www.unix.com                              
# Bugs : this thread   
###################################
USAGE()
{
    echo "----------------------------------------------------------------"
    echo "USAGE :: findday YEAR MONTH DAY WEEK"
    echo "Example :"
    echo "findday 2012 10 SU 3"
    echo "The above will tell the date of 3rd sunday in October 2012"
    echo "Valid values for YEAR ( 1 to 9999 )"
    echo "Valid values for MONTH ( 1 to 12 )"
    echo "Valid values for DAY ( SU, MO, TU, WE, TH, FR, SA )"
    echo "Valid values for WEEK ( 1 to 5 )"
    echo "----------------------------------------------------------------"
    exit 1
}

YearCheck()
{
    echo "$1" | grep -v "^[0-9]*$" >/dev/null 2>&1 && echo "Please enter the number 1 to 9999" && USAGE
    if [[ ! "${1}" -le "10000" || "${1}" -eq "0" ]]
    then
        echo "Enter the correct Year [1-9999]"
        USAGE
    fi
    
}

MonthCheck()
{
    echo "$1" | grep -v "^[0-9]*$" >/dev/null 2>&1 && echo "Please enter the number 1 to 12" && USAGE
    if [[ ! "${1}" -le "12" || "${1}" -eq "0" ]]
    then
        echo "Enter the correct Month [1-12]"
        USAGE
    fi
}
DayCheck()
{
    echo "$1" | egrep -v "^SU$|^MO$|^TU$|^WE$|^TH$|^FR$|^SA$" >/dev/null 2>&1 && echo "Valid values for DAY ( SU, MO, TU, WE, TH, FR, SA )" && USAGE
    [ "$1" == "SU" ] && WNO=7 && PNO=1
    [ "$1" == "MO" ] && WNO=6 && PNO=2
    [ "$1" == "TU" ] && WNO=5 && PNO=3
    [ "$1" == "WE" ] && WNO=4 && PNO=4
    [ "$1" == "TH" ] && WNO=3 && PNO=5
    [ "$1" == "FR" ] && WNO=2 && PNO=6
    [ "$1" == "SA" ] && WNO=1 && PNO=7
    
}
WeekCheck()
{
    if [[ ! "${1}" -le "5" || "${1}" -eq "0" ]]
    then
        echo "Enter the correct WEEK [1-5]"
        USAGE
    fi
}

if [ "$#" -ne "4" ]
then
    USAGE
else
    YearCheck $1
    MonthCheck $2
    DayCheck $3
    WeekCheck $4
    echo "---------------"
    echo "Given Inputs"
    echo "---------------"
    echo "YEAR  :: $1"
    echo "MONTH :: $2"
    echo "DAY   :: $3"
    echo "WEEK  :: $4" 
    echo "---------------"
    cal $2 $1 | awk -v n="$4" -v WNO="$WNO" -v PNO="$PNO" '
    {
    if(NR==3)
    {
        if(NF==WNO)
        {
            a=2;
        }
        else
        {
            a=3;
        }    
    }
    if(a && NR==(a+n))
    {
        if(length($PNO)>0)
        {
            printf("Output Date : %s\n",$PNO);
        }
        else
        {
            printf("Requested week date is not available for the given month year combination\n");
        }
        exit;
    }
    }'
fi

output

$ ./findday.sh 2012 12 SA 5
---------------
Given Inputs
---------------
YEAR  :: 2012
MONTH :: 12
DAY   :: SA
WEEK  :: 5
---------------
Output Date : 29
 
$ cal 12 2012
   December 2012      
Su Mo Tu We Th Fr Sa  
                   1  
 2  3  4  5  6  7  8  
 9 10 11 12 13 14 15  
16 17 18 19 20 21 22  
23 24 25 26 27 28 29  
30 31                 
 
$ ./findday.sh 2012 12 FR 5
---------------
Given Inputs
---------------
YEAR  :: 2012
MONTH :: 12
DAY   :: FR
WEEK  :: 5
---------------
Requested week date is not available for the given month year combination
3 Likes

It is kind and generous of you to share your script with the rest of us. Thank you very much.

I have a few observations which may or may not interest you. If they do not, then nevermind me.

Your script may work fine for interactive use, where a human invokes it at the command line and then reads the output, but it's not well designed for non-interactive use, such as feeding a pipeline or assigning a value to a variable.

To me, the interactive case isn't compelling; your script doesn't really provide much benefit as opposed to just using cal. Reading cal's output would give the same information with about the same effort.

However, where cal's output isn't very easy to work with non-interactively, your script's output could be a simple pleasure.

I would suggest, in keeping with UNIX's tradition of not saying much when there is little to say, that your script's only output be a single number, 0 through 31, followed by a newline.

To facilitate the case where someone only wants to know whether there's a 5th instance of a day in a month, the exit status could be set to non-zero.

Also, for non-interactive use especially, the script should print error, diagnostic, and usage messages to standard error, not standard output, so that they go to the correct place. It would cause confusion if usage information where sent into a pipe expecting script output. In its present form, your code will not play nice if standard error and standard output have been redirected to different places (which is the default behavior of a pipeline).

If you are attached to the currently verbose output, you could make it an option, such as -v. But, even in a verbose mode, it would be a good idea to kill the header info; it doesn't add any value. Also, a simple tab, instead of the whitespace and colons currently delimiting the two fields of each line, complements nicely the splitting behavior of cut , awk , read , and friends.

To summarize my suggestions with a mock terminal session:

$ ./findday.sh 2012 12 SA 5
29
$ echo $?
0
$ ./findday.sh 2012 12 FR 5
0
$ echo $?
1
$ ./findday.sh
Usage message here ...
$ echo $?
2

Now we can do things like:

# Run commands only if January 2013 begins with a Wednesday or has 5 Saturdays
if [ $(findday 2013 01 WE 1) -eq 1 ] || findday 2013 01 SA 5 >/dev/null; then
    cmd1
    cmd2
    ...
    cmdN
fi

Whether you are interested in my critique or not, thanks again for your contribution. It's appreciated.

Regards,
Alister

1 Like