Introduction
This challenge was inspired by a seemingly simple task requested at work that couldn't easily be done in Microsoft Teams. At least not from what we could find!
Task
Your task, if you choose to accept it, is to output the last week day of any month in any year.
Input
You can input the desired month/year in whatever format you like, but it must take in an input. Having these hard-coded into the calculations doesn't count!
Input examples include but are not limited to:
08/2023October 16781/2067
Output
Your output must be the last week day of the month in the below format:
Tuesday 31st
Test cases
| Input | Output |
|---|---|
10/2023 |
Tuesday 31st |
February 2017 |
Tuesday 28th |
09/1674 |
Friday 28th |
1/2043 |
Friday 30th |
Assumptions / Stipulations
- You can assume the inputs are always in a workable format and not null
- Calendar is the Gregorian Calendar
- Monday, Tuesday, Wednesday, Thursday and Friday are all week days. Saturday and Sunday are NOT valid week days for this task
- You can ignore public holidays
PS. I got the test cases from timeanddate.com
Current Winner (28th November 2023)
PHP - Kaddath - 55 Bytes https://codegolf.stackexchange.com/a/266340/91342
21 Answers 21
Bash and GNU Date, (削除) 84 (削除ここまで) 83 bytes
Thanks to gildux for shaving 1 byte.
s=(1 2)
m=11ドル+month
date -d$m-$((s[`date -d$m +%w`]+1))day +%A\ %eth|sed s/1th/1st/
Input format is the month name and year as a single word, e.g. October2023 or sep2026.
An English locale (such as the standard C locale) is required for the day names to be in English.
You might need to unset TZ environment variable to get proleptic Gregorian dates.
How it works
m=11ドル+month: Create adateinput that is one month later than the start of the target month (ie. the first day of the following month). GNU Date allows us to omit all the spaces, which is helpful.date -d$m +%w: Obtain the day of week of the first day of the following month.$((s[...]+1)): We'll need to back up one day, plus an additional day if following month starts on Sunday (weekday 0) or an additional two days if it starts on Monday (weekday 1).The array
sis incomplete; the arithmetic works because empty string concatenates with+1to give the result we want.date -d$m-$((...))day +%A\ %eth: Format that last working day, appendingthto give a candidate ordinal.|sed s/1th/1st/: Rewrite31thas31st. The last working day must be in the range 26th...31st, so there are nonds orrds to consider.
Full demo
#!/bin/bash
last-working-day()
{
s=(1 2)
m=11ドル+month
date -d$m-$((s[`date -d$m +%w`]+1))day +%A\ %eth|sed s/1th/1st/
}
test "$*" || set -- $(for i in {0..35}; do date -d 1jan00+${i}month +%b%Y; done)
for i
do
printf '%s: %s\n' $i "$(last-working-day $i)"
done
Output (no args):
Jan2000: Monday 31st
Feb2000: Tuesday 29th
Mar2000: Friday 31st
Apr2000: Friday 28th
May2000: Wednesday 31st
Jun2000: Friday 30th
Jul2000: Monday 31st
Aug2000: Thursday 31st
Sep2000: Friday 29th
Oct2000: Tuesday 31st
Nov2000: Thursday 30th
Dec2000: Friday 29th
Jan2001: Wednesday 31st
Feb2001: Wednesday 28th
Mar2001: Friday 30th
Apr2001: Monday 30th
May2001: Thursday 31st
Jun2001: Friday 29th
Jul2001: Tuesday 31st
Aug2001: Friday 31st
Sep2001: Friday 28th
Oct2001: Wednesday 31st
Nov2001: Friday 30th
Dec2001: Monday 31st
Jan2002: Thursday 31st
Feb2002: Thursday 28th
Mar2002: Friday 29th
Apr2002: Tuesday 30th
May2002: Friday 31st
Jun2002: Friday 28th
Jul2002: Wednesday 31st
Aug2002: Friday 30th
Sep2002: Monday 30th
Oct2002: Thursday 31st
Nov2002: Friday 29th
Dec2002: Tuesday 31st
-
\$\begingroup\$ Woohoo! Shell ahead of the golfing languages. At least for now; I expect that won't last long.. \$\endgroup\$Toby Speight– Toby Speight2023年10月28日 16:44:03 +00:00Commented Oct 28, 2023 at 16:44
-
\$\begingroup\$ Save one more character by replacing
s[$(date -d$m +%w)]with weirds[`date -d$m +%w`]\$\endgroup\$gildux– gildux2023年10月31日 16:59:12 +00:00Commented Oct 31, 2023 at 16:59 -
1\$\begingroup\$ Thanks @gildux - sometimes ingrained good habits are detrimental to one's golf... \$\endgroup\$Toby Speight– Toby Speight2023年11月03日 07:50:06 +00:00Commented Nov 3, 2023 at 7:50
Factor + math.text.english, 110 bytes
[ last-day-of-month dup weekend? [ friday< ] when
dup day-name swap day>> dup ordinal-suffix [I ${} ${}${}I] ]
Takes a timestamp (date object) as input.
last-day-of-monthget the last day of the monthdup weekend?is it a weekend?[ friday< ] whenthen change it to the Friday beforedup day-nameget the day name ("Monday", "Tuesday", etc.)swap day>>get the day number of the monthdup ordinal-suffixget its ordinal suffix[I ${} ${}${}I]interpolate it all together and print
Python, 129 bytes
from datetime import*
def f(y,m,d=31,s="st"):
try:q=date(y,m,d);1/(q.weekday()<5);print(f'{q:%A %d}{s}')
except:f(y,m,d-1,"th")
-
\$\begingroup\$ I love how the
trydoes double duty, very clever. Also your recognition that only 31 needs the "st" suffix. \$\endgroup\$Mark Ransom– Mark Ransom2023年10月29日 02:48:30 +00:00Commented Oct 29, 2023 at 2:48 -
\$\begingroup\$ The "st" default parameter is from Arnauld, I can't take credit for that. \$\endgroup\$corvus_192– corvus_1922023年10月29日 09:59:36 +00:00Commented Oct 29, 2023 at 9:59
PHP, -F 55 bytes
<?=date('l jS',strtotime("$argn+1month last weekday"));
Input is in "yyyy-mm" format in my example, but strtotime supports many formats
It's so rare that PHP is competitive! I knew one day would be the glory of this pile of unconsistent mess, that I came to like golfing with ;) I needed to add one month because using strtotime without a day number returns the first day, and last is actually a relative term (opposite of next).
Probably still golfable, still searching for edge cases where it could not work (relative month +1 month can be broken I think, -1 month is for sure)
NOTE: my first version was 4 bytes shorter ++$argn." last weekday", I liked it because it uses PHP's string increment, but even if it works with the tests cases, it will fail for December -> "2023-12" would give "2023-13" and strtotime is not really clever
-
\$\begingroup\$ Simply excellent blue elephant. And the better answer by now. \$\endgroup\$gildux– gildux2023年10月31日 02:44:45 +00:00Commented Oct 31, 2023 at 2:44
-
\$\begingroup\$ I was asking myself if Perl could do the same, but it will be a bit more longer because one need to import
Date::Parse(forstr2time) orTime::Piece(forstrptime) :D \$\endgroup\$gildux– gildux2023年10月31日 16:30:03 +00:00Commented Oct 31, 2023 at 16:30 -
1\$\begingroup\$ @Kaddath so far this is the shortest answer! You'll be the winner soon if no shorter answers come in :-) \$\endgroup\$Mark Harwood– Mark Harwood2023年11月01日 15:48:11 +00:00Commented Nov 1, 2023 at 15:48
-
1\$\begingroup\$ @gildux it's actually the code parsing aspect that made me like golfing with it, in lots of place you can omit spaces and stuff in a weird way, like the space removed in
+1monthbecause of the way the parser works \$\endgroup\$Kaddath– Kaddath2023年11月03日 10:15:57 +00:00Commented Nov 3, 2023 at 10:15 -
1\$\begingroup\$ PHP share many aspects with PERL, which was born with also the focus to ease text-parsing in system scripts: that's why people can write dirty and unmaintainable things... But it was also though to allow more traditional programming ways (it tend to embrase many paradigm) However, it's a very old langage so it standard miss some stuffs are missing from it's standard library. \$\endgroup\$gildux– gildux2023年11月03日 23:13:20 +00:00Commented Nov 3, 2023 at 23:13
JavaScript (ES6), 129 bytes
-1 thanks to @l4m2
-2 thanks to @Shaggy
Expects ([year, month]), as integers.
f=(D,d=31,s="st")=>(q=new Date([...D,d])).getDate()>9&&q.getDay()%6?q.toLocaleString("EN",{weekday:"long"})+' '+d+s:f(D,d-1,"th")
Or 126 bytes if we assume a default English locale.
Commented
f = ( // recursive function taking:
D, // D[] = [year, month]
d = 31, // d = day, starting at 31
s = "st" // s = suffix, initialized to "st"
) => //
( q = new Date([ // build the date q for:
...D, // the year and month stored in D[]
d // the day d
]) //
).getDate() > 9 // if the resulting day is greater than 9
&& // (i.e. we didn't overflow to the next month)
q.getDay() // and the 0-based day-of-week index
% 6 ? // is neither 0 or 6 (Sunday / Saturday):
q.toLocaleString( // return the name of the day
"EN", { // obtained with toLocaleString()
weekday: "long" // set up to English
} //
) + //
' ' + // followed by a space
d + // followed by the day
s // followed by the suffix
: // else:
f(D, d - 1, "th") // recursive call with d - 1 and s = "th"
-
1
-
1
(Pure) Zsh, 126 bytes
zmodload zsh/datetime
s=strftime
for d ({31..9})$s -sT %A\ %dth `$s -r %F 1ドル-$d`&&[[ $T = [^S]*$d\th ]]&&break
<<<${T/1th/1st}
Using the builtin strftime rather than any external programs.
zmodload zsh/datetime
# "strftime" is long enough that setting $s and using $s twice is shorter than "strftime" twice.
s=strftime
# start at the 31st of the month, count downward
for d ({31..9})
# Get the timestamp of "YYYY-MM-DD" with strftime -r (reverse/strptime)
# and find "Nameofday DDth" from timestamp. (-s)ave in $T
$s -sT %A\ %dth `$s -r %F 1ドル-$d` &&
# if Nameofday begins with an S or we end up on a different day of month
# (like 02-31 converted to 03-03), keep going.
[[ $T = [^S]*$d\th ]] && break
# Fix suffix
<<<${T/1th/1st}
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 2043instead of1/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
BEGINbut 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.
C (gcc), (削除) 270 (削除ここまで) 250 bytes
char*s[]={"Mon","Tues","Wednes","Thurs","Fri","sth"};l(y){return y/4-y/100+y/400;}
f(m,y){int d,p=l(y);y-=d=m<3;d=6+y+p+(31*(m+=12*d-2))/12;
d+=y=31-(12652612>>(m*2-2)&3)+(m>11&p-l(y));d%=7;y-=p=d&d/4*3;
printf("%sday %d%.2s\n",s[d-p],y,s[5]+(y<31));}
Doing it the hard way - a bunch of mod 7 math to count extra days since the first Monday of Year 0.
p=l(y) returns the number of leap years since 0.
d=m<3 is true for Feb and March
y-=d...m+=12*d-2 sets m to the month, where Mar is 1 and Jan and Feb are 11 and 12 of the prior year.
d=6+y+p+31*m/12 sets d to the number of extra days between the first Monday in year 0 and the last day of the prior month. (the 31*m/12 gives the correct pattern of +3s and +2s)
(m>11&p-l(y) is 1 if it's February of a leap year
12652612 is "31-days" for each month encoded in 2 bits, so
y=31-(12652612>>(m*2-2)&3)+... sets y to the last day in the query month.
d+=y...;d%=7 gets the day of the week (0==Mon) for the query.
p=d&d/4*3 sets p to the days needed to truncates Sat/Sun to Fri (4)
y-=p fixes the last day of the month
s[d-p] is the day string,
2 characters from s[5]+(y<31) is "st" or "th"
-
1\$\begingroup\$ 220 bytes \$\endgroup\$ceilingcat– ceilingcat2025年06月30日 12:44:35 +00:00Commented Jun 30 at 12:44
Ruby, (削除) 99 89 (削除ここまで) 102 bytes
->y,m{(1..4).find{|d|(A=Date.new y,m,-d).wday<5}
A.strftime"%A %d#{A.day>30?'st':'th'}"}
require'time'
Explanation
Anonymous function tacking year and month as numbers.
We find which of the last 4 days of month ( checking backwards using -d ) are wdays (week days 0-indexed) less than 5 saving each time date object in constant A for later use.
When it's found loop is stopped so A is the last weekday of the month.
Then we format the output, we can only have 31st or XXth.
Python3, 153 bytes
from datetime import*
T=timedelta(1)
def f(m,y):
d=date(y+(m>11),m%12+1,1)-T
while d.weekday()>4:d-=T
return d.strftime('%A %d')+['th','st'][d.day>30]
-
\$\begingroup\$ 133 bytes \$\endgroup\$SuperStormer– SuperStormer2023年10月27日 16:42:59 +00:00Commented Oct 27, 2023 at 16:42
-
\$\begingroup\$ @SuperStormer Thank you very much, updated \$\endgroup\$Ajax1234– Ajax12342023年10月27日 16:51:14 +00:00Commented Oct 27, 2023 at 16:51
-
\$\begingroup\$ Unfortunately, 153 bytes to handle the "st"/"th" properly. \$\endgroup\$SuperStormer– SuperStormer2023年10月27日 18:04:51 +00:00Commented Oct 27, 2023 at 18:04
-
\$\begingroup\$ @SuperStormer Thanks, updated \$\endgroup\$Ajax1234– Ajax12342023年10月27日 18:07:41 +00:00Commented Oct 27, 2023 at 18:07
-
2\$\begingroup\$ -2 bytes:
d.strftime('%A %d')+'tsht'[d.day>30::2]\$\endgroup\$corvus_192– corvus_1922023年10月27日 21:00:02 +00:00Commented Oct 27, 2023 at 21:00
Charcoal, (削除) 137 (削除ここまで) 136 bytes
×ばつ13+4%ι12¦5⟧7θ≔⊟θη≔⊟θθ≔−+28%−θη7∨¬θ⊗¬⊖θθ§⪪"⟲1U↘ዬ@2¦À≧≡FGv_E✂2↘" +θηday Iθ§⪪stth2‹θ31
Try it online! Link is to verbose version of code. Explanation: Uses my code for Zeller's congruence from my answer to ASCII Calendar Planner.
≔I⪪S/θ
Split the date on /.
×ばつ12⊟θ⊟θ2θ
Convert to months since March 1, 1 BC.
×ばつ13+4%ι12¦5⟧7θ
Use a modified Zeller's congruence to extract the day of the week of the first day of next month and this month as a list.
≔⊟θη≔⊟θθ
Extract the list entries into variables to save bytes.
≔−+28%−θη7∨¬θ⊗¬⊖θθ
Calculate the last working day of the month.
§⪪"..." +θηday
Output the day of the week.
Iθ
Output the day of the month.
§⪪stth2‹θ31
Output the ordinal suffix.
Vyxal, 104 bytes
S1⁄2⌊Ḃ÷dN„4/?›13*5/014=[1›11"4*1JḊ÷¬∧∨28:›"i|300⇧5%2+]:£Wf⌊∑7%:\∇2<[›-6]⇩`°⋏ ß0 †ƛ ßɖ »£`⌈ið„:31=«⟇'Ḋ«1⁄2iW∑
Try it Online! | All test cases
Takes the year and the month (one-based) seperately. January and February are considered the 13th and 14th months of the previous year.
Vyxal has no Date/Time built-ins, so this uses a variation of Zeller's Congruence.
S1⁄2⌊Ḃ÷dN„4/?›13*5/014=[1›11"4*1JḊ÷¬∧∨28:›"i|300⇧5%2+]:£Wf⌊∑7%:\∇2<[›-6]⇩`°⋏ ß0 †ƛ ßɖ »£`⌈ið„:31=«⟇'Ḋ«1⁄2iW∑
S1⁄2⌊Ḃ÷dN„4/?›13*5/ ## First Part of Zeller's Congruence Formula
S1⁄2⌊ # Split year into two halves [century, year of the century]
Ḃ # Bifurcate, push this and its reverse
÷dN # Push each item to stack, multiply the year of the century by -2
„ # Rotate stack ([century, year of the century] is on top)
4/ # Divide both by 4
?›13*5/ # Multiply month + 1 with 13/5
014=[1›11"4*1JḊ÷¬∧∨28:›"i|300⇧5%2+] ## Calculate number of days in month
014=[ ] # If month is 14 (February) ...
[ | # Leap year calculation:
1› Ḋ # Does year + 1 divide ...
11"4*1J # ... [400, 4, 100] (Pair [100, 1] * 4 and join 100)
÷¬∧∨ # Split on stack, logical not, and, or
¬∧∨ # [ (year + 1 divides 4 and not 100) or (year + 1 divides 400) ]
28:›"i # Index into [28, 29]
| ] # Calculate days for other months:
0⇧5%2 # ((Year+2) % 5) % 2 == 0
0⇧5%2 # Maps [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1] for months 3 to 13 (March=3, ..., January=13)
30 + # Add result to 30
:£Wf⌊∑7% ## Second Part of Zeller's Congruence Formula
:£ # Save a copy of number of days in month in register
Wf⌊∑ # Wrap terms in list, take floor and sum all terms
7% # Result modulo 7 (Returns weekday as Saturday=0, Sunday=1, ...)
:\∇2<[›-6]⇩`°⋏ ß0 †ƛ ßɖ »£`⌈i ## Calculate workday (as day of the week and month)
:\∇ # Push the number of days in the month (register), weekday, weekday
2<[ ] # If weekday < 2 (Saturday or Sunday)
›-6 # Subtract weekday+1 from number of days in month (last workday), Push 6 (Friday)
`°⋏ ß0 †ƛ ßɖ »£` # Compressed string of workdays
⇩ ⌈i # Index workday-2 into list (Monday=0, Tuesday=1, ...)
ð„:31=«⟇'Ḋ«1⁄2i ## Day of the Month formatting
ð„ # Push space and rotate stack
:31= # Is the day of the month 31? (Returns 1 or 0)
«⟇'Ḋ«1⁄2i # Compressed string "thst", halve and index into string
W∑ # Join all values in the stack (workday as day of the week, space, workday as day of the month)
PowerShell Core, 118 bytes
param($y,$m)for(;!((($k=Date "$y/$m/1"|% *hs 1|% *ys(--$d))|% d*k)%6)){}"{0:dddd} {0:dd}"-f$k+('st','th')[$k.Day-ne31]
Takes in two parameters, $year and $month, returns a string.
Less golfed:
param($y,$m)
for(;(($k=(Date "$y/$m/1").AddMonths(1).AddDays(--$d)).DayOfWeek-in"Saturday","Sunday")){}
"{0:dddd} {0:dd}"-f$k+('st','th')[$k.Day-ne31]
Finds the last day of the month, while it is a Saturday or a Sunday, check one day before Then formats the date
Go, 234 bytes
import(."fmt";."time")
func f(m,y int)string{M,e:=Month(m),"th"
t,W:=Date(y,M,31,0,0,0,0,UTC),(Time).Weekday
for w:=W(t);t.Month()!=M||w<1||w>5;{t=t.Add(Hour*-24);w=W(t)}
if t.Day()>30{e="st"}
return Sprintf("%s %d%s",W(t),t.Day(),e)}
Explanation
import(."fmt";."time")
func f(m,y int)string{M,e:=Month(m),"th"
// start the date off at the 31st of the month, regardless of the month
t,W:=Date(y,M,31,0,0,0,0,UTC),(Time).Weekday
// subtract 1 day while...
for w:=W(t);
// the months don't match, or
t.Month()!=M
// the day is not a weekday
||w<1||w>5;{t=t.Add(Hour*-24);w=W(t)}
// replace "31th" with "31st"
if t.Day()>30{e="st"}
return Sprintf("%s %d%s",W(t),t.Day(),e)}
Google Sheets / Microsoft Excel, 76 bytes
=let(d,workday(eomonth(A1,)+1,-1),text(d,"dddd d")&if(day(d)=31,"st","th"))
Put the month and year in cell A1 in one of the formats specified in the question (or any other valid date format), and the formula in B1.
The formula gets the first day of the next month and then finds the previous weekday before that, which is the same as the last weekday of the specified month.
The day number of the last weekday of a month will never end in 2 or 3, so it is enough to add st when the day is 31 and use th with any other date.
The formula is for Google Sheets, but I tested it in the most recent version of Microsoft Excel for the web and it seems to work the fine there as well.
The Google Sheets epoch is 30 December 1899. The formula will work with dates from January 1900 to December 99999. In Microsoft Excel, the range of valid dates seems more limited, and formula will work from January 1900 to November 9999.
| input | expected | formula |
|---|---|---|
| 10/2023 | Tuesday 31st | Tuesday 31st |
| February 2017 | Tuesday 28th | Tuesday 28th |
| 09/1674 | Friday 28th | #NUM! |
| 1/2043 | Friday 30th | Friday 30th |
Python, 152 bytes
Since there is already multiple python answers I've tried to use the calendar library since I never used it before. It just uses the fact that the object monthcalendar gives weeks of the month and days of those weeks as a 2d array ->then just a check for highest number in the first 5 days of last week.
from calendar import *
def f(y,m,d=31,s="st"):
if d in monthcalendar(y, m)[-1][:5]:
print(day_name[weekday(y,m,d)],str(d)+s)
else: f(y,m,d-1,s="th")
-
\$\begingroup\$ Welcome to Code Golf, and nice answer! \$\endgroup\$2023年11月14日 14:12:48 +00:00Commented Nov 14, 2023 at 14:12
-
\$\begingroup\$ @RydwolfPrograms thank you, looking forward to my stay and hopefully mastering some actual skillz \$\endgroup\$Ondrej Stepanek– Ondrej Stepanek2023年11月15日 14:11:19 +00:00Commented Nov 15, 2023 at 14:11
R, (削除) 111 (削除ここまで) (削除) 109 (削除ここまで) (削除) 100 (削除ここまで) 98 bytes
\(a,`?`=paste0)sub("1th","1st",(d=format(sort(as.Date(a?-1:-31),T),"%A %e")?"th")[d<"S"|d>"T"])[1]
Inputs a string in yyyy-mm format, then creates a vector of dates for days 1 to 31 (paste negative numbers, the minus being the separation sign here). Actually, only days 26 to 31 are needed, but that way a byte is spared. The invalid dates are automatically converted to NAs which get lost in the sorting step. Argument T allows to sort in decreasing order, so that the first element will be the last date. d<"S" are Friday and Monday and d>"T" are Thursday, Tuesday and Wednesday.
APL(Dyalog Unicode), (削除) (削除ここまで)66 bytes SBCS
{⊃'Dddd DDoo'(1200⌶)⊃⌽w/⍨0≠6|7|w←∊{0::⍬⋄, ̄1 1 ⎕DT⊂⍵} ̈(⊂⌽⍵), ̈25+⍳6}
woo-hoo. Shaggy waiting room. Takes date as m y.
bash and BSD date, 96 bytes
As an alternative to Toby Speight's answer but using BSD implementation instead of GNU one
s=(1 2)
m="-v2ドルy -v1ドル -v+1m -v1d"
date $m -v-$((s[`date $m +%w`]+1))d +%A\ %eth|sed s/1th/1st/
It has the following limitations:
- months can be integers only, not strings (
Octoberorsepfor example) - separator is changed from
/to blanks which is natural shell arguments separator
There's certainly room for tweaking.
C#, 143 bytes
(y,m)=>new[]{DateTime.DaysInMonth(y,m)is{}z?z:z,z-1,z-2}.Select(x=>new DateTime(y,m,x).ToString("dddd dd")+(x>30?"st":"th")).First(x=>x[0]!=83)
Explanation
f = (y, m) => // lambda that takes year and month
new[] { // create new implicitly typed array
DateTime.DaysInMonth(y, m) // get number of days in month
is {} z ? z : z, // hack to set variable z to this value inline
z-1, z-2 // add the 2 days previous to z
} // array contains the last 3 days of the given month in descending order
.Select( // transform the elements of the array
x => new DateTime(y, m, x) // create new datetime with the given year and month
.ToString("dddd dd") // convert to string and format
+ (x > 30 ? "st" : "th")) // add "st" if is the 31st, "th" otherwise
.First( // get first element that matches condition
x => x[0] != 83) // first character is not "S" (i.e not day isn't saturday or sunday)
Scala 3, 235 bytes
235 bytes, it can be golfed more.
Golfed version. Attempt This Online!
(y,m)=>{var d=LocalDate.of(y,m,1).lengthOfMonth;var q=LocalDate.of(y,m,d);while(q.getDayOfWeek==SATURDAY||q.getDayOfWeek==SUNDAY){d-=1;q=LocalDate.of(y,m,d)};val s=if(d<2)"st"else"th";println(s"${q.getDayOfWeek} ${q.getDayOfMonth}$s")}
Ungolfed version. Attempt This Online!
import java.time.{LocalDate, DayOfWeek}
object Main {
def main(args: Array[String]): Unit = {
printLastWeekdayOfMonth(2023, 10)
printLastWeekdayOfMonth(2017, 2)
printLastWeekdayOfMonth(1674, 9)
printLastWeekdayOfMonth(2043, 1)
printLastWeekdayOfMonth(2023, 11)
printLastWeekdayOfMonth(2023, 11)
}
def printLastWeekdayOfMonth(year: Int, month: Int): Unit = {
var day = LocalDate.of(year, month, 1).lengthOfMonth() // Start from the last day of the month
var date = LocalDate.of(year, month, day)
// Loop backwards to find the last weekday (Monday to Friday)
while (date.getDayOfWeek == DayOfWeek.SATURDAY || date.getDayOfWeek == DayOfWeek.SUNDAY) {
day -= 1
date = LocalDate.of(year, month, day)
}
// Print the date in the required format with 'st' or 'th'
val suffix = if (day == 1) "st" else "th"
println(s"${date.getDayOfWeek} ${date.getDayOfMonth}$suffix")
}
}
09/1674should result inFriday 28th, see here \$\endgroup\$