1

I'm trying to pick off the first Monday, second Tuesday, second Saturday, etc. out of the cal function and piping awk commands. For this month, I can get the first Tuesday correctly with the command below the calendar:

    December 2018
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

>cal | awk 'NF == 7 && NR > 2 {print $3; exit}'
>4

But this doesn't always work, see the test for March 2016 below:

     March 2016
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

>cal 3 2016 | awk 'NF == 7 && NR > 2 {print $3; exit}'
>8

Why does it work for the top example but not the bottom? I would think the bottom example should look at the third line and find the correct result of 1. Is there a fool-proof way to snag these dates using awk -- maybe checking if the column is empty and moving to the next line?

7
  • The third row doesn't have 7 fields
    – Jeff Schaller
    Commented Dec 7, 2018 at 19:44
  • Do you have to use cal? Do you have GNU date?
    – Jeff Schaller
    Commented Dec 7, 2018 at 19:51
  • I see about the NF==7 part, but removing that gets hairy if Sunday and Monday are missing and the first day of the month is Tuesday. I do have date.
    – kstats9pt3
    Commented Dec 7, 2018 at 19:54
  • 1
    yes you can use substr: cal | awk 'NR>1 && (d=substr($0,2*3,3)+0){print d; exit}'
    – user313992
    Commented Dec 7, 2018 at 20:15
  • What about other key dates like the first Thursday, second Monday, third Friday, etc.? The answer works but could you explain the syntax?
    – kstats9pt3
    Commented Dec 7, 2018 at 20:18

4 Answers 4

1

Here's another solution for you

cal | awk 'NR >= 3 { tu = substr($0,7,2) +0 } tu { print tu; exit }'
4

cal 3 2016 | awk 'NR >= 3 { tu = substr($0,7,2) +0 } tu { print tu; exit }'
1

It picks out the Tuesday column (characters 7 and 8), and delivers the first non-zero value. The approach can be safely extrapolated to any day of the week.

1
  • This is a simple, clean function -- thanks!
    – kstats9pt3
    Commented Dec 10, 2018 at 14:06
1

With GNU date:

For the first Tuesday of the current month:

for day in 0{1..7}; do [[ "$(date -d $(date +%Y%m${day}) +%a)" = "Tue" ]] && echo "$day";done

For the first Tuesday of a given year and month:

year=2016
month=03
for day in 0{1..7}; do [[ "$(date -d ${year}${month}${day} +%a)" = "Tue" ]] && echo "$day";done

A "Tuesday" variation in awk:

cal | awk -F "" '!/[[:alpha:]]/ && $7$8 ~ /[0-9]/ { print $7$8;exit }'

This checks the hard-coded position of columns 7 and 8 (Tuesday's) in the output (notice the field separator is null, splitting the input on every character) -- after skipping any lines that have letters in them (the header lines). Adjust the 7 & 8 to get other days.

To make it clearer what the previous command is doing, the awk command's fields are numbered per column:

123456789            <--- awk fields $1, $2, $3, ... $9
    December 2018
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
3
  • Thanks, Jeff. These examples are great. I'm not sure I understand the 7 & 8 part regarding the columns -- how do these values get the third column in cal? I plugged in other values and don't see the pattern.
    – kstats9pt3
    Commented Dec 7, 2018 at 20:14
  • Take a look at the recent edit and see if that helps.
    – Jeff Schaller
    Commented Dec 7, 2018 at 20:18
  • Ah that makes sense -- thanks for the clarification. We have a lot of files released on first, second, third [day of the week] of the month etc. and some are released but not updated until the following Saturday. Since crontab cannot handle this type of schedule these little functions are perfect. Thanks again.
    – kstats9pt3
    Commented Dec 7, 2018 at 20:25
0

Both gcal (GNU) and ncal (BSD) families support options for transposing rows with columns:

$ ncal
    March 2024        
Su     3 10 17 24 31
Mo     4 11 18 25   
Tu     5 12 19 26   
We     6 13 20 27   
Th     7 14 21 28   
Fr  1  8 15 22 29   
Sa  2  9 16 23 30   

$ gcal -i

March 2024

Sunday          3 10 17 24 31
Monday          4 11 18 25   
Tuesday         5 12 19 26   
Wednesday       6 13 20 27   
Thursday        7 14 21 28   
Friday       1  8 15 22 29   
Saturday     2  9 16 23 30   

When invoked this way, it’s easier to pluck ordinal positions by day of week. Use awk to first pattern match the desired day of week, then print column N-1 to get the Nth instance of that day of week.

For example, 1st Tuesday of specific months and years:

$ ncal 12 2018 | awk '/^Tu/ { print $2; }'
4
$ ncal 3 2016 | awk '/^Tu/ { print $2; }'
1

To get the 5th Friday of the current month (which happens to have one):

$ ncal | awk '/^Fr/ { print $6; }’
29
0

In ksh93:

$ printf '%(%F)T\n' 'first monday this month'
2024-03-04
$ printf '%(%F)T\n' 'second tuesday this month'
2024-03-12
$ printf '%(%F)T\n' '2nd tuesday in 2016-03'
2016-03-08

(note that 4th, 5th don't work in current versions of ksh93u+m; fourth, fifth do though).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .