This one is much harder than it ought to be. I’ve done similar work using datename
, but only because I know all the systems I use will be configured with English as the default language. Without that assumption, you have to use datepart(dw, ...
, and you also have to worry about SET DATEFIRST
. I am also assuming “weekday” means “Mon, Tue, Wed, Thu, Fri”, and excluding Saturday and Sunday. (Or does TFIG apply across the globe?)
Thus, walking through my methodology, we start with what you had:
DECLARE
@Date datetime
,@BOM datetime
--SET @Date = '1/4/14' -- Should return Jan 31, 2013
SET @Date = '5/4/14' -- Should return May 30, 2014 (not May 31)
SET @BOM = DATEADD(m, DATEDIFF(m, 0, @date) + 1, 0)
PRINT @BOM
This sets @BOM (beginning of month) to the first day of the month after whatever you have in @Date. (I split this across multiple statements, because otherwise you have to repeat the function all over the place in the code below.)
Next, you have to “move back” from this day by one, two, or three days. If BOM is Monday, -3 to get to the prior Friday; if BOM is Sunday, -2 to get to Friday; otherwise, -1 will land you on a weekday. Based on the values returned by datepart
, there’s no wifty algorithm I can think of to generate that -1/-2/-3 spread, so I use a case statement (I reject looping routines out of hand—far too kludgy for database work).
PRINT datepart(dw, @BOM) -- To see what is is
PRINT dateadd(dd, case
when datepart(dw, @BOM) = 2 then -3
when datepart(dw, @BOM) = 1 then -2
else -1
end
,@BOM)
Alas, this only works if your SQL instance is configured with the default “first day of week” setting; this is checked via PRINT @@datefirst
, where 7 (the default) = Sun, 6 = Sat, and so forth. Once again, no wifty algorithm suggests itself, and the case statement turns into a mess:
PRINT dateadd(dd, case
when @@datefirst = 7 and datepart(dw, @BOM) = 2 then -3
when @@datefirst = 7 and datepart(dw, @BOM) = 1 then -2
when @@datefirst = 6 and datepart(dw, @BOM) = 3 then -3
when @@datefirst = 6 and datepart(dw, @BOM) = 2 then -2
when @@datefirst = 5 and datepart(dw, @BOM) = 4 then -3
when @@datefirst = 5 and datepart(dw, @BOM) = 3 then -2
when @@datefirst = 4 and datepart(dw, @BOM) = 5 then -3
when @@datefirst = 4 and datepart(dw, @BOM) = 4 then -2
when @@datefirst = 3 and datepart(dw, @BOM) = 6 then -3
when @@datefirst = 3 and datepart(dw, @BOM) = 5 then -2
when @@datefirst = 2 and datepart(dw, @BOM) = 7 then -3
when @@datefirst = 2 and datepart(dw, @BOM) = 6 then -2
when @@datefirst = 1 and datepart(dw, @BOM) = 1 then -3
when @@datefirst = 1 and datepart(dw, @BOM) = 7 then -2
else -1
end
,@BOM)
Ugly, or what? And looping structures have to account for this as well. Of course, if you can rely on always having the same language on your SQL Instances, it’s that much simpler:
PRINT dateadd(dd, case
when datename(dw, @BOM) = 'Monday' then -3
when datename(dw, @BOM) = 'Sunday' then -2
else -1
end
,@BOM)
Any or all of the above can and should be “concatenated down” into a single statement or query (or, better, a function); if you can safely make assumptions about your installation’s language and/or first day of week, you can shorten it even more.