POSIX shell, 168 bytes
Not the shortest possible with the shell, but it doesn't use any bashism and is funny (see explanation bellow.)
cal $@|tail -2|awk 'NF>1{split("S Mon Tues Wednes Thurs Fri",d)
a=6;if(NF==1){b=$1-2}else if(NF==7){b=$6}else{a=NF;b=$NF}
print d[a]"day",(b==31)?b"st":b"th"}'|tail -1
With the following limitations:
- months are given as integer only, not strings (January or jan for example)
- separator is changed from slash to blanks (i.e.
1 2043
instead of 1/2043
) which is natural shell arguments separator
Explanation
We can use the POSIX cal
to visually catch the last weekday.
$ # for February 2017
$ cal 2 2017
February 2017
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
The last week is on the visually shown last line (but there's an extra blank line), so with POSIX tail
(for golfing purpose, tail -n -2
is shorten tail -2
)
$ # last week of February 2017
$ cal 2 2017 | tail -n -2
26 27 28
Finally we should pick the last day… Because the column position vary, awk
is a better candidate than cut
here (But one can prefer perl
or some other scripting langage, but I'm trying to use commands that should be always available), hence the following script:
BEGIN { # on start, create array of days names
split("Su Mo Tu We Th Fr Sa", d)
}
NF > 1 { # on filled line, print last column name and value
print d[NF],$NF
}
If the script is put in file LastDay.awk
, our chaining become:
$ # last day of February 2017
$ cal 2 2017 | tail -n -2 | awk -f LastDay.awk
Tu 28
However, note that the first column is Sunday, and we need to deal with boundaries. The modification is:
BEGIN { # on start, create array of days names
split("S Mo Tu We Th", d)
}
NF > 1 { # check last column
if (NF==7) {
print "Fr",$6 # Saturday: we read the previous field
} else if (NF==1) {
print "Fr",($1-2) # Sunday: we count two days less
} else {
print d[NF],$NF # Week-Day: pick column name and value
}
}
For golfing purpose,
- I try to use ternary operator (which is an expression that expects three other expressions) without success
- Initial array creation is not done in
BEGIN
but each time, and rule is evaluate in any case…
- Replace
$1 $2
with $@
(thanks Toby Speight for the comment) and remove extra spaces
But, in order to comply with the output requirements, we have to add more characters for days.
NF > 1 { split("S Mon Tues Wednes Thurs Fri", d)
if (NF==7) { # sat
print d[6] "day",$6
} else if (NF==1) { # sun
print d[6] "day",($1-2)
} else { # non w-e
print d[NF] "day",$NF
}
}
For compliance, we have to deal with ordinals too. To avoid repeating ourself many times, let's opt for assignments and put formatting at end.
NF > 1 { split("S Mon Tues Wednes Thurs Fri", d)
if (NF==7) { # sat
a=d[6]
b=$6
} else if (NF==1) { # sun
a=d[6]
b=($1-2)
} else { # non w-e
a=d[NF]
b=$NF
}
print a "day", (b==31)?b"st":b"th"
}
Addendum
Well, cal 12 2001
shows that there wasn't simply a blank line as I believed, the formatting is for six weeks… Then, before invoking AWK, our selection should be changed so:
- cal $1 $2 | tail -n -2
+ cal $1 $2 | grep -v '^ *$' | tail -n -1
Alternatively, the AWK output (Friday 28th
and Monday 31st
) must be filtered though tail -n -1
. Either add 14 characters before (and save NF > 1
condition), or add 10 characters after (and keep 4 characters condition). Fifty-fifty, so it's a matter of taste.
09/1674
should result inFriday 28th
, see here \$\endgroup\$