How to find second and fourth Monday of the month?

Hi,

I have came across the scenario where, we have to run the script on second and fourth Monday of each month.

I have tried to search man page of date and also forum for it but, could not get any answer to this.

Can you please advise how can we get second and fourth Monday of the month?

Also, While searching for above problem, I have came across the thread http://www.unix.com/unix-for-dummies-questions-and-answers/156205-can-we-get-every-tuesday-mondays-date-current-week.html which has following code to find Monday of the current week.

cal | awk -v d="$(date +"%m %d %y")" 'BEGIN{split(d,a)} $0 ~ int(a[2]) {print a[1] $2 a[3]}'

I am unable to understand that code and that thread is closed. Can someone please explain this code?

Thanks in advance for your help.

cal without any further parameters prints the current month in 4 - 5 lines plus two header lines. In these lines, the awk searches for today and, if found, prints $2, the second field, which is Monday as the weeks in cal start with Sunday.

You probably want that script to be executed by cron. cron(1m) [opensolaris man page]

But also see the crontab entry: crontab(5) [centos man page]

*     *     *   *    *        command to be executed
-     -     -   -    -
|     |     |   |    |
|     |     |   |    +----- day of week (0 - 6) (Sunday=0)
|     |     |   +------- month (1 - 12)
|     |     +--------- day of        month (1 - 31)
|     +----------- hour (0 - 23)
+------------- min (0 - 59)

So this might be what you want: (untested)

* * * * 1/2,4 /path/to/script

This suggestion is based on what i read myself just now about cron.
hth

With GNU date and a recent bash, try this:

for MONTH in {01..12}
    do printf "%2s.%2s.%4s      %2s.%2s.%4s\n" \
                $(($(date -d"2015${MONTH}01" +"16 - %u - (%u==1?7:0)"))) $MONTH 2015 \
                $(($(date -d"2015${MONTH}01" +"30 - %u - (%u==1?7:0)"))) $MONTH 2015
    done
12.01.2015    26.01.2015
 9.02.2015    23.02.2015
 9.03.2015    23.03.2015
13.04.2015    27.04.2015
11.05.2015    25.05.2015
 8.06.2015    22.06.2015
13.07.2015    27.07.2015
10.08.2015    24.08.2015
14.09.2015    28.09.2015
12.10.2015    26.10.2015
 9.11.2015    23.11.2015
14.12.2015    28.12.2015

Feel free to adapt to other years.

Thanks for your reply. But, I do not want to use cron. I want to control second and fourth Monday through shell script.

---------- Post updated at 12:50 AM ---------- Previous update was at 12:48 AM ----------

Thanks. I will try this. But, can you please explain the logic used here.

date 's %u format specifier supplies the day-of-week of the first day in the target month, which then is subtracted from 16 (for second week) or 30 (4. wk, experimental values). Should the First be a Monday, subtract another 7 days.

Getting below errors, any suggestions?

$ for MONTH in {01..12}
>     do printf "%2s.%2s.%4s      %2s.%2s.%4s\n" \
>                 $(($(date -d"2015${MONTH}01" +"16 - %u - (%u==1?7:0)"))) $MONTH 2015 \
>                 $(($(date -d"2015${MONTH}01" +"30 - %u - (%u==1?7:0)"))) $MONTH 2015
>     done
date: invalid date `2015101'
date: invalid date `2015101'
 0. 1.2015       0. 1.2015
date: invalid date `2015201'
date: invalid date `2015201'
 0. 2.2015       0. 2.2015
date: invalid date `2015301'
date: invalid date `2015301'
 0. 3.2015       0. 3.2015
date: invalid date `2015401'
date: invalid date `2015401'
 0. 4.2015       0. 4.2015
date: invalid date `2015501'
date: invalid date `2015501'
 0. 5.2015       0. 5.2015
date: invalid date `2015601'
date: invalid date `2015601'
 0. 6.2015       0. 6.2015
date: invalid date `2015701'
date: invalid date `2015701'
 0. 7.2015       0. 7.2015
date: invalid date `2015801'
date: invalid date `2015801'
 0. 8.2015       0. 8.2015
date: invalid date `2015901'
date: invalid date `2015901'
 0. 9.2015       0. 9.2015
12.10.2015      26.10.2015
 9.11.2015      23.11.2015
14.12.2015      28.12.2015
$

Looks like your shell doesn't zero pad the MONTH sequence as does my bash . You may want to consider the various options that your shell offers for "brace expansion". Otherwise, prepend single digit MONTHs with a "0" by shell means.

---------- Post updated at 09:40 ---------- Previous update was at 09:39 ----------

or, use seq -w 1 12 .

Are you trying to determine if the script is started on the correct day, i.e. 2nd & 4th Monday only?

If so, start it every day and have a section to test the date at the top similar to this:-

date '+%d %w'|read dayofmonth dayofweek
((weekofmonth=1+$dayofmonth/7))

if [ $dayofweek -ne 1 -o \( $weekofmonth -ne 2 -a $weekofmonth -ne 4 \) ]
then
   printf "Not running today.\n"
   exit 0
fi

# Now able to run main script

Apologies if I have missed the point, but I hope that this helps.

Robin

Thanks Robin. That's what the intention is.

Could you please advise why you are adding 1 in the below code:

((weekofmonth=1+$dayofmonth/7))

Is it related to day of the week? Or It will be same irrespective of whether the month starts on Mon,Tue,Wed,Thu,Fri,Sat or Sun?

---------- Post updated at 07:48 PM ---------- Previous update was at 07:45 PM ----------

Thanks RudiC.
It is working now.

$ for MONTH in `seq -w 1 12`
>     do printf "%2s.%2s.%4s      %2s.%2s.%4s\n" \
>                 $(($(date -d"2015${MONTH}01" +"16 - %u - (%u==1?7:0)"))) $MONTH 2015 \
>                 $(($(date -d"2015${MONTH}01" +"30 - %u - (%u==1?7:0)"))) $MONTH 2015
> done
12.01.2015      26.01.2015
 9.02.2015      23.02.2015
 9.03.2015      23.03.2015
13.04.2015      27.04.2015
11.05.2015      25.05.2015
 8.06.2015      22.06.2015
13.07.2015      27.07.2015
10.08.2015      24.08.2015
14.09.2015      28.09.2015
12.10.2015      26.10.2015
 9.11.2015      23.11.2015
14.12.2015      28.12.2015
$

I have also managed to do the same using below code.

$ for MONTH in `seq -w 1 12`
> do
> cal $MONTH $YEAR | tail -n +3 | cut -c4-5 | tr -s '\n' ','  | sed 's/[ \t]*//g' | awk -F "," -v M=$MONTH -v Y=$YEAR '{if($1>7 || $1==""){printf "%2s.%2s.%4s\t%2s.%2s.%4s\n",$3,M,Y,$5,M,Y} else {printf "%2s.%2s.%4s\t%2s.%2s.%4s\n",$2,M,Y,$4,M,Y}}'
> done
12.01.2015      26.01.2015
 9.02.2015      23.02.2015
 9.03.2015      23.03.2015
13.04.2015      27.04.2015
11.05.2015      25.05.2015
 8.06.2015      22.06.2015
13.07.2015      27.07.2015
10.08.2015      24.08.2015
14.09.2015      28.09.2015
12.10.2015      26.10.2015
 9.11.2015      23.11.2015
14.12.2015      28.12.2015
$

Let me know if this code is fine.

For my part, in the line:-

((weekofmonth=1+$dayofmonth/7))

... the logic is that I divide the day of the month by seven (losing any remainder) and that gives me a value between zero and four. I add the 1 to make it easier to understand when testing, i.e. 1 for first week, 2 for second week etc.

That's all,
Robin

Ok. Understood, Thanks.

ksh93 have builtin support for such date queries

For example:

$ printf "%T\n"   "second monday june"
Mon Jun  8 00:00:00 EDT 2015
$ printf "%T\n"   "fourth monday june"
Mon Jun 22 00:00:00 EDT 2015

You can format the output anyway you like.

For example:

$ printf "%(%D)T\n"   "fourth monday june"
06/22/15

Thanks. Although my shell does not support it. It is good thing to know.

---------- Post updated at 09:26 PM ---------- Previous update was at 09:19 PM ----------

Hi Robin,

I had modified your code and tried to check for one year. It is showing some discrepancies.

Can you please advise here.

$ cat Monday_Date.sh
#!/bin/bash

YEAR=2015
for MONTH in `seq -w 1 12`
do
        for DATE in `seq -w 1 31`
        do
                dayofmonth=`date -d"${YEAR}${MONTH}${DATE}" +%d`
                dayofweek=`date -d"${YEAR}${MONTH}${DATE}" +%w`
                ((weekofmonth=1+$dayofmonth/7))

                if [ $dayofweek -ne 1 -o \( $weekofmonth -ne 2 -a $weekofmonth -ne 4 \) ]
                then
                        #printf "Not running today.\n"
                        continue
                else
                        MONDAY_DATE=`date -d"${YEAR}${MONTH}${DATE}" +%d.%m.%Y`
                        echo "$MONDAY_DATE"
                fi
        done
done

Output:

$ sh Monday_Date.sh 2>/dev/null
12.01.2015
26.01.2015
09.02.2015
23.02.2015



09.03.2015
23.03.2015
13.04.2015
27.04.2015

11.05.2015
25.05.2015
08.06.2015
22.06.2015

13.07.2015
27.07.2015
10.08.2015
24.08.2015
07.09.2015
21.09.2015

12.10.2015
26.10.2015
09.11.2015
23.11.2015

07.12.2015
21.12.2015
$

Second and Fourth Monday dates for December and September seems to be wrong.

That's because the algorithm assigns day 7 to the second week already, which is the case for the second Monday of September and December, 2015. On top, the empty lines are due to invalid dates for months that dont have a 31., or 29., 30., 31. for February. And, DATE and dayofmonth hold identical values. Try the modified script:

YEAR=2015
for MONTH in $(seq -w 1 12) 
  do for DATE in $(seq -w 1 31)
    do dayofweek=$(date -d"${YEAR}${MONTH}${DATE}" +%w)
       [ $? == 0 ] || continue
       weekofmonth=$(( 1 + (10#$DATE - 1) / 7 ))
       if [ $dayofweek -ne 1 -o \( $weekofmonth -ne 2 -a $weekofmonth -ne 4 \) ]
         then continue
         else MONDAY_DATE=`date -d"${YEAR}${MONTH}${DATE}" +%d.%m.%Y`
              echo "$MONDAY_DATE"
       fi
    done
  done

Thanks RudiC.
Can you please advise how are you coming up with the value 10 in below code? Basically, I want to understand the thought process while selecting the value, so the logic will be clear and will help in long run.

weekofmonth=$(( 1 + (10#$DATE - 1) / 7 ))

man bash :

August and September will make the calculation fail unless the base is specified. I can't tell how far this applies to other shells...

Ah, yes. I hadn't thought of Monday 7th being the first week.

Try:-

((weekofmonth=1+($dayofmonth-1)/7))

Sorry about that,
Robin :o

Thanks. Script works well.

When searched more, I found out that instead of using %d option of date, if we use %e it works well.

Please find below modified script:

#!/bin/bash

YEAR=2015
for MONTH in $(seq -w 1 12)
do
        for DATE in $(seq -w 1 31)
        do
                dayofmonth=$(date -d"${YEAR}${MONTH}${DATE}" +%e 2>/dev/null)
		[ $? == 0 ] || continue
                dayofweek=$(date -d"${YEAR}${MONTH}${DATE}" +%w)
                weekofmonth=$((($dayofmonth-1)/7+1))

                if [ $dayofweek -ne 1 -o \( $weekofmonth -ne 2 -a $weekofmonth -ne 4 \) ]
                then
                        #printf "Not running today.\n"
                        continue
                else
                        MONDAY_DATE=`date -d"${YEAR}${MONTH}${DATE}" +%d.%m.%Y`
                        echo "$MONDAY_DATE"
                fi
        done
done

Output:

$ sh Monday_Date.sh
12.01.2015
26.01.2015
09.02.2015
23.02.2015
09.03.2015
23.03.2015
13.04.2015
27.04.2015
11.05.2015
25.05.2015
08.06.2015
22.06.2015
13.07.2015
27.07.2015
10.08.2015
24.08.2015
14.09.2015
28.09.2015
12.10.2015
26.10.2015
09.11.2015
23.11.2015
14.12.2015
28.12.2015
$

---------- Post updated at 04:24 PM ---------- Previous update was at 04:21 PM ----------

Thanks Robin/RudiC and others for all your help.