I want to have a function that calculates the fiscal year. The fiscal year must begin on the first Monday in March. Thank you!
example:
CREATE FUNCTION dbo.fnc_FiscalYear( #AsOf DATETIME )
RETURNS INT
AS BEGIN
DECLARE #Answer INT
SET DATEFIRST 1
IF ( MONTH(#AsOf) < 3 )
or MONTH(#AsOf=3) and datename(weekday, #AsOf) = 'Monday' and datepart(day, #AsOf)>=1 and datepart(day, #AsOf)<=7;
SET #Answer = YEAR(#AsOf) - 1
ELSE SET #Answer = YEAR(#AsOf)
RETURN #Answer
END
GO
but it's not working
It looks as though there are a number of syntax errors with your script. Try this instead. I've removed the SETs and returned at the point of the if statements. Also, note the grouping of the if statements.
CREATE FUNCTION dbo.fnc_FiscalYear( #AsOf DATETIME )
RETURNS INT
AS BEGIN
DECLARE #Answer INT
IF (( MONTH(#AsOf) < 3 )
OR (MONTH(#AsOf) = 3
AND DATENAME(weekday, #AsOf) = 'Monday'
AND datepart(day, #AsOf) >= 1
AND datepart(day, #AsOf)<=7))
RETURN (YEAR(#AsOf) - 1)
RETURN YEAR(#AsOf)
END
GO
The logic for this is tricky -- especially for the first week of March:
CREATE FUNCTION dbo.fnc_FiscalYear (
#AsOf DATETIME
)
RETURNS INT AS
BEGIN
RETURN( CASE WHEN MONTH(#AsOf) < 3 THEN YEAR(#AsOf) - 1
WHEN MONTH(#AsOf) > 3 THEN YEAR(#AsOf)
WHEN DAY(#AsOf) >= 7 THEN YEAR(#AsOf)
WHEN DATENAME(#AsOf) = 'Monday' OR
DATENAME(#AsOf) = 'Tuesday' AND DAY(#AsOf) >= 2 OR
DATENAME(#AsOf) = 'Wednesday' AND DAY(#AsOf) >= 3 OR
DATENAME(#AsOf) = 'Thursday' AND DAY(#AsOf) >= 4 OR
DATENAME(#AsOf) = 'Friday' AND DAY(#AsOf) >= 5 OR
DATENAME(#AsOf) = 'Saturday' AND DAY(#AsOf) >= 26
THEN YEAR(#AsOf)
ELSE YEAR(#AsOf) - 1
END);
END ;
GO
( ##DateFirst + DatePart( weekday, SampleDate ) - 1 ) % 7 + 1 will always return an integer from 0 to 6 with 0 corresponding to Sunday regardless of the setting of DateFirst or Language and without string manipulation.
Tweaking the expression to ( ##DateFirst + DatePart( weekday, SampleDate ) - 2 ) % 7 shifts the start of the cycle from Sunday to Monday and the range to 0 to 6 which, in turn, simplifies the following code:
create function dbo.FiscalYear( #Date as Date )
returns Int
begin
return Year( #Date ) - case
when Month( #Date ) <= 2 then 1 -- January and February are always part of the prior year.
-- In March it is the prior year only in the first week and if the calculated
-- DoW is less than the day of the month.
when Month( #Date ) = 3 and Day( #Date ) < 7 and
( ##DateFirst + DatePart( weekday, #Date ) - 2 ) % 7 >= Day( #Date ) then 1
else 0 end;
end;
Test the function with sample data:
with
-- Generate sample dates for 20 years.
YearOffsets as (
select 0 as YearOffset
union all
select YearOffset + 1
from YearOffsets
where YearOffset < 20 ),
SampleDates as (
select Cast( DateAdd( year, YearOffset, '2000-02-28' ) as Date ) as SampleDate, 1 as Counter
from YearOffsets
union all
select DateAdd( day, 1, SampleDate ), Counter + 1
from SampleDates
where Counter < 10 )
-- Calculate the Fiscal Year for each sample date.
select SampleDate, DateName( weekday, SampleDate ) as WeekDay,
( ##DateFirst + DatePart( weekday, SampleDate ) - 2 ) % 7 as DayOfWeek, -- 0 = Monday.
dbo.FiscalYear( SampleDate ) as FiscalYear
from SampleDates
order by SampleDate;
Related
I am working in a algorithm that needs to be calculated based on the number of working days.
Sample data
CREATE TABLE Holidays
(
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO Holidays VALUES ('2019-07-16', '2019-07-17')
DECLARE #CurrentWeekDay INT
SET DATEFIRST 1
// Returns day of the week as int, ex... Monday = 0, Tue = 1....
SET #CurrentWeekDay = DATEPART (WEEKDAY, GETDATE()) - 1
this starts on Monday until Friday, 0,1,2,3,4, which is subtracted on the total days of the week
Now i have created 2 new variables
DECLARE #TotalHolidays INT, #IsSameWeek BIT
// If there is a holiday in the current week get the holiday date, else just cast 0
SET #IsSameWeek =
CASE
WHEN (SELECT DATEDIFF(WEEK,GETDATE(), (SELECT TOP 1 EndDate FROM Holidays))) = -1 THEN 0
ELSE
1
END
SET #TotalHolidays =
CASE
WHEN #IsSameWeek = 1 THEN (SELECT DATEDIFF(day, StartDate, EndDate ) FROM Holidays)
ELSE
0
END
so basically the total amount of days left in the week is calculated using this simple formula
DECLARE #TotalDays = 5,
SET #TotalDays = #TotalDays - #TotalHolidays - #CurrentWeekDay
this is all nice but this is only effective when the holidays start at the end of the week.
Datefirst
M T W T F
0 1 2 3 4
if Thursday and friday are holidays the algorithm works nicely, but if Wednesday is a holiday then it ruins everything
basically i need it so if wednesday is a holiday
Datefirst goes like
M T W T F
0 1 2 3
With all set this is the whole code
CREATE TABLE Holidays
(
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO Holidays VALUES ('2019-07-18', '2019-07-19')
DECLARE #TotalHolidays INT, #IsSameWeek BIT, #CurrentWeekDay INT
SET DATEFIRST 1
SET #CurrentWeekDay = DATEPART (WEEKDAY, GETDATE()) - 1
SET #IsSameWeek =
CASE
WHEN (SELECT DATEDIFF(WEEK,GETDATE(), (SELECT TOP 1 EndDate FROM Holidays))) = -1 THEN 0
ELSE
1
END
SET #TotalHolidays =
CASE
WHEN #IsSameWeek = 1 THEN (SELECT DATEDIFF(day, StartDate, EndDate ) FROM Holidays)
ELSE
0
END
DECLARE #DaysLeft INT
SET #DaysLeft = 5 - #TotalHolidays - #CurrentWeekDay
This will return 0 when it should return 2
I have a list of tasks,
for each I have a TaskID, startTime and StopTime as milliseconds from 1-1-1970 and a list of users (#Tasks).
I need to calculate the time spent by each user on the task splitted by day/night time, week or weekend, regular/overtime considering nighttime hours from 10:00 PM to 06:00 AM.
Surely there's a better solution but so far I got this:
IF OBJECT_ID('tempdb..#Tasks') IS NULL
BEGIN
create table #Tasks
(
TaskID nvarchar(50),
DateStart bigint,
DateStop bigint,
Staff nvarchar(100)
)
insert into #Tasks values
('C001',1554181200000,1554190200000,'john,jack'),
('C002',1554202800000,1554212700000,'tom,john'),
('C003',1554228000000,1554246900000,'john,franck'),
('C004',1554613200000,1554626700000,'john')
END
GO
declare
#UserName nvarchar(50)='john',
#DateFrom datetime='2019-04-01',
#DateTo datetime='2019-04-30',
#nStart time='06:00:00',
#nStop time='22:00:00'
select
startday as [Day],
sum([WeekDay]) as [WeekDay],
sum([WeekNight]) as [WeekNight],
sum([WeekendDay]) as [WeekendDay],
sum([WeekendNight]) as [WeekendNight],
sum([TotalMinutes]) as [TotalMinutes],
0 WeekDayOverTime,
0 WeekNightOvertime,
0 WeekendDayOvertime,
0 WeekendNightOvertime,
[UserName]
,timeframe
from
(
select
iif(isWeekend=1,NightMinutes,0) WeekendNight,
iif(isWeekend=0,NightMinutes,0) WeekNight,
iif(isWeekend=1,DayMinutes,0) WeekendDay,
iif(isWeekend=0,DayMinutes,0) [WeekDay],
TotalMinutes,
username,
startday,
timeframe
from
(
select
iif(Before6>0,Before6,0)+ iif(After22>0,After22,0) NightMinutes,
TotalMinutes-iif(Before6>0,Before6,0)- iif(After22>0,After22,0) DayMinutes,
TotalMinutes,
startday,
isWeekend,
UserName,
timeframe
from
(
Select
(t.datestop-t.datestart)/60000 TotalMinutes,
datediff(n,convert(time,DATEADD(SECOND,t.DateStart/1000,'1970-01-01')),#nStart) Before6,
datediff(n,#nStop,convert(time,DATEADD(SECOND,t.DateStop/1000,'1970-01-01'))) After22,
iif((((DATEPART(DW, convert(datetime,DATEADD(SECOND,t.DateStart/1000,'1970-01-01'))) - 1 ) + ##DATEFIRST ) % 7) IN (0,6),1,0) isWeekend,
convert(varchar(10),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),126) startday,
STUFF(( SELECT distinct ' ' + convert(varchar(5),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),108)+'-'+convert(varchar(5),DATEADD(SECOND,t.DateStop/1000,'1970-01-01'),108) AS [text()]
FROM #Tasks tt
--WHERE tt.taskID=t.TaskID
FOR XML PATH('') ), 1, 1, '' ) AS [timeframe],
#UserName UserName
FROM #Tasks t
WHERE t.Staff like '%'+#UserName+'%'
and DATEADD(SECOND,t.DateStart/1000,'1970-01-01') between #DateFrom and #DateTo
) z
) zz
) zzz
group by startday,username,timeframe
order by startday
I need now to :
1) group result by day, summing up WeekDay,WeekNight,WeekendDay,WeekendNight and TotalMinutes, and concatenating timeframe so to have for example on 2nd April "05:00-07:30|11:00-13:45|18:00-23:00"
2) Not sum up time between 12:00 and 12:30 (if applicable) since it is lunch time
3) considering that after 8 hours daily it has to be calculated as overtime, I have to split the total minutes between in time and overtime, but depending if overtime is by daytime or nighttime or on the weekend
4) eventually using a holiday table
in other words we should have this:
Day TotalMinutes WeekDay WeekNight WeekendDay WeekendNight WeekDayOverTime WeekNightOvertime WeekendDayOvertime WeekendNightOvertime UserName timeframe
02/04/2019 630 420 60 0 0 45 75 0 0 john 05:00-07:30|11:00-13:45|18:00-23:00
07/04/2019 225 0 0 165 60 0 0 0 0 john 05:00-08:45
because (on 2nd April) we have:
First Task:
60 minutes of Regular NightTime
90 minutes of Regular DayTime
Second Task:
165 minutes of Regular DayTime, but have to count only 135 due to lunch time
Third Task:
240 DayTime
75 NightTime
but since with Task 1 and 2 we sum up 285 minutes, only the first 185 Minutes of Third task are Regular DayTime: the remaining 45 are Overtime DayTime, and the following 75 of NightTime are actually OvertimeNightTime
In this approach the first CTE (properDates) get the Start and Stop Datetimes, then you don't need to repeat that formula over the query.
The second CTE(splittedMinutes) is to get the same data you get in your current approach, except for the first CROSS APPLY, which is splitting the timeframes crossing with lunch time. The second CROSS APPLY gets the number of minutes and isWeekend value.
In the third CTE(qualifiedMinutes) I am using a window partition to get the accumulated minutes and generate the overtimes when applies.
At the end I used a selective SUM to separate weekdays and weekends in the aggregates
;with properDates AS (
SELECT TaskID, DATEADD(SECOND,t.DateStart/1000,'1970-01-01') as DateStart,DATEADD(SECOND,t.DateStop/1000,'1970-01-01') as DateStop, Staff
FROM #Tasks t
WHERE Staff LIKE '%' + #UserName + '%'
), splittedMinutes AS (
select
CAST(p.DateStart AS DATE) as [Day],
TotalMinutes,
SUM(TotalMinutes) OVER (PARTITION BY CAST(p.DateStart AS DATE) ORDER BY b.start) AS cumulate,
TotalMinutes - EarlyMinutes - LateMinutes as DayTime,
EarlyMinutes + LateMinutes as NightTime,
isWeekend,
CONVERT(VARCHAR(5),b.Start,108) + '-' + CONVERT(VARCHAR(5),b.Stop,108) as [timeframe]
from properdates p
cross apply (
select CAST(p.DateStart As TIME) AS Start, #bStart as Stop WHERE CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart
union
select #bStop as Start, CAST(DateStop AS TIME) AS Stop WHERE CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop
union
select CAST(p.DateStart AS TIME) AS Start, CAST(p.DateStop AS TIME) AS Stop WHERE NOT (CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart) AND NOT (CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop)
) b
cross apply (
select
DATEDIFF(Minute, b.Start, b.Stop) as TotalMinutes,
(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart) + ABS(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart))) / 2 as EarlyMinutes,
(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)) + ABS(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)))) / 2 as LateMinutes,
CASE WHEN DATEPART(DW, p.DateStart) IN (1,7) THEN 1 ELSE 0 END AS isWeekend
) c
), qualifiedMinutes As (
SELECT Day, TotalMinutes, RegularDay, RegularNight, OvertimeDay, OvertimeNight, isWeekend, timeframe
FROM splittedMinutes
OUTER APPLY (
SELECT RegularDay = CASE WHEN cumulate <= #maxTime THEN DayTime WHEN DayTime - (cumulate - TotalMinutes - #maxTime) > 0 THEN ABS(cumulate - TotalMinutes - #maxTime) ELSE 0 END
) RD
OUTER APPLY (
SELECT OvertimeDay = DayTime - RegularDay
) OWD
OUTER APPLY (
SELECT RegularNight = CASE WHEN cumulate <= #maxTime THEN NightTime WHEN (cumulate - TotalMinutes - #maxTime + RegularDay) < 0 THEN NightTime + (cumulate - TotalMinutes - #maxTime + RegularDay) ELSE 0 END
) RWN
OUTER APPLY (
SELECT OvertimeNight = NightTime - RegularNight
) OWN
)
SELECT
Day,
#UserName and UserName,
SUM(TotalMinutes) AS TotalMinutes,
SUM(CASE WHEN isWeekend = 0 THEN RegularDay ELSE 0 END) AS WeekDay,
SUM(CASE WHEN isWeekend = 0 THEN RegularNight ELSE 0 END) AS WeekNight,
SUM(CASE WHEN isWeekend = 1 THEN RegularDay ELSE 0 END) AS WeekendDay,
SUM(CASE WHEN isWeekend = 1 THEN RegularNight ELSE 0 END) AS WeekendNight,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeDay ELSE 0 END) AS WeekDayOverTime,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeNight ELSE 0 END) AS WeekNightOvertime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeDay ELSE 0 END) AS WeekendDayOverTime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeNight ELSE 0 END) AS WeekendNightOvertime,
STUFF((SELECT '|' + timeframe FROM qualifiedMinutes tt WHERE tt.Day = q.Day ORDER BY timeframe FOR XML PATH('') ), 1, 1, '' ) AS [timeframe]
FROM qualifiedMinutes q
GROUP BY Day
i have the following code which does backward scheduling if Saturday or Sunday to move the date to Friday but why it returns 1900-01-06 00:00:00.000 rather 2017-03-31 13:18:12.300 i tried it getdate()-1 works fine which is Friday works great. I wounder why i doesn't work if Saturday or Sunday?
set datefirst 7 --Sunday
declare #ACUTBY datetime = getdate()
select
case datepart(WEEKDAY, #ACUTBY)
when 1 then 5
when 7 then 5
else #ACUTBY
end as [A-CUT BY]
the above code 1900-01-06 00:00:00.000
---- getdate()-1------
set datefirst 7 --Sunday
declare #ACUTBY datetime = getdate()-1
select
case datepart(WEEKDAY, #ACUTBY)
when 1 then 2
when 7 then 5
else #ACUTBY
end as [A-CUT BY]
the above code return 2017-03-31 13:18:12.300
Try select Cast( 5 as DateTime ).
You are mixing data types, e.g. an integer 5 and a datetime #ACUTBY, and the result of the case must be a single data type. Hence the numbers get converted to a DateTime. Since the base date for a DateTime is January 1st, 1900, adding 5 results in January 6th.
You might want something like:
declare #Samples as Table ( Sample Date );
insert into #Samples ( Sample ) values
( '20170402' ), ( '20170403' ), ( '20170404' ),
( '20170405' ), ( '20170406' ), ( '20170407' ), ( '20170408' );
select Sample, DatePart( dw, Sample ) as DoW,
case DatePart( dw, Sample )
when 1 then DateAdd( day, -2, Sample )
when 7 then DateAdd( day, -1, Sample )
else Sample end as WeekDay
from #Samples;
You are trying to merge two different return values.
select
case when datepart(WEEKDAY, #ACUTBY) = 1 then 2
when datepart(WEEKDAY, #ACUTBY) = 7 then 5
else datepart(WEEKDAY, #ACUTBY)
end as [A-CUT BY]
I'm writing a function in SQL Server 2012 that will need to know the number of 3 specific days of the month that have passed since a given date. I can do this with a while loop, but its slow and I was looking for a better way.
Here is what I have so far:
Let's assume that GETDATE() = '11/14/2016' and #productDate = '10/1/2016'
--Get the number of "units" that have passed since the date on the label
DECLARE #unitCount INT = 0;
DECLARE #countingDate DATE
SET #countingDate = DATEADD(DAY,1,#productDate);--add 1 to prevent counting the date on the label as the first unit
WHILE (#countingDate < CAST(GETDATE() As date ))
BEGIN
SELECT #unitCount = #unitCount +
CASE
WHEN DAY(#countingDate) = 1 OR DAY(#countingDate) = 10 OR DAY(#countingDate) = 20 THEN 1
ELSE 0
END
SET #countingDate = DATEADD(DAY,1,#countingDate);
END
This will result in #unitCount = 4
GETDATE() of '11/20/2016' would result in #unitCount = 5
Without using a numbers table
create function dbo.fn_DateCounter
(
#datefrom date,
#dateto date
)
returns int
as
begin
return
-- number of complete months
3 *
(
(DATEPART(YYYY, #dateto) * 12 + DATEPART(MM, #dateto))
-(DATEPART(YYYY, #datefrom) * 12 + DATEPART(MM, #datefrom))
- 1
)
-- add on the extras from the first month
+ case when DATEPART(DD, #datefrom) < 10 then 2
when DATEPART(DD, #datefrom) < 20 then 1
else 0
end
-- add on the extras from the last month
+ case when DATEPART(DD, #dateto) > 20 then 3
when DATEPART(DD, #dateto) > 10 then 2
else 1
end
end
go
select dbo.fn_DateCounter('01-jan-2000','01-jan-2000') -- 0
select dbo.fn_DateCounter('01-jan-2000','10-jan-2000') -- 0
select dbo.fn_DateCounter('01-jan-2000','11-jan-2000') -- 1
select dbo.fn_DateCounter('01-jan-2000','20-jan-2000') -- 1
select dbo.fn_DateCounter('01-jan-2000','21-jan-2000') -- 2
select dbo.fn_DateCounter('11-jan-2000','21-jan-2000') -- 1
select dbo.fn_DateCounter('11-jan-2000','21-feb-2000') -- 4
select dbo.fn_DateCounter('01-jan-2000','01-jan-2001') -- 36
select dbo.fn_DateCounter('01-jan-2000','11-jan-2001') -- 37
You can use a combination of sum, case, and the dbo.spt_values table:
declare #productDate datetime = '11/01/2016',
#unitCount int
;with nums as ( -- use a CTE to build a number list
select top 1000 number from master..spt_values
)
select #unitCount = sum(
case when day(dateadd(day, n, #productDate)) in (1, 10, 20)
then 1 else 0 end
) -- add 1 for each 1,10,20 we find
from (
select n = row_number() over (order by nums.number)
from nums cross join nums as num -- 1000*1000 = 1 million rows
) n
where dateadd(day, n, #productDate) < getdate()
select #unitCount
This will grab each date between #productDate and getdate(). The case statement will select 1 for each 1/10/20, and 0 for every other date. Finally, we take the sum of the result.
For 11/1 - 11/11, it returns 1.
For 1/1 - 11/11, the result is 31.
EDIT: In the CTE (with nums as...), we select 1-1000, and then we do a cross join which gives us a million records to work with. The answer is still limited, but now you can go ~2700 years with this.
I am creating a calendar table for my warehouse. I will use this as a foreign key for all the date fields.
The code shown below creates the table and populates it. I was able to figure out how to find Memorial Day (last Monday of May) and Labor Day (first Monday of September).
SET NOCOUNT ON
DROP Table dbo.Calendar
GO
Create Table dbo.Calendar
(
CalendarId Integer NOT NULL,
DateValue Date NOT NULL,
DayNumberOfWeek Integer NOT NULL,
NameOfDay VarChar (10) NOT NULL,
NameOfMonth VarChar (10) NOT NULL,
WeekOfYear Integer NOT NULL,
JulianDay Integer NOT NULL,
USAIsBankHoliday Bit NOT NULL,
USADayName VarChar (100) NULL,
)
ALTER TABLE dbo.Calendar ADD CONSTRAINT
DF_Calendar_USAIsBankHoliday DEFAULT 0 FOR USAIsBankHoliday
GO
ALTER TABLE dbo.Calendar ADD CONSTRAINT
DF_Calendar_USADayName DEFAULT '' FOR USADayName
GO
Declare #StartDate DateTime = '01/01/2000'
Declare #EndDate DateTime = '01/01/2020'
While #StartDate < #EndDate
Begin
INSERT INTO dbo.Calendar
(
CalendarId,
DateValue,
WeekOfYear,
DayNumberOfWeek,
NameOfDay,
NameOfMonth,
JulianDay
)
Values
(
YEAR (#StartDate) * 10000 + MONTH (#StartDate) * 100 + Day (#StartDate), --CalendarId
#StartDate, -- DateValue
DATEPART (ww, #StartDate), -- WeekOfYear
DATEPART (dw, #StartDate), -- DayNumberOfWeek
DATENAME (dw, #StartDate), -- NameOfDay
DATENAME (M, #StartDate), -- NameOfMonth
DATEPART (dy, #StartDate) -- JulianDay
)
Set #StartDate += 1
End
--=========================== Weekends
-- saturday and sunday
UPDATE dbo.Calendar SET USAIsBankHoliday = 1, USADayName += 'Weekend, ' WHERE DayNumberOfWeek IN (1, 7)
--=========================== Bank Holidays
-- new years day
UPDATE dbo.Calendar SET USAIsBankHoliday = 1, USADayName += 'New Year''s Day, ' WHERE (CalendarId % 2000) IN (101)
-- memorial day (last Monday in May)
UPDATE dbo.Calendar
SET USAIsBankHoliday = 1,
USADayName += 'Memorial Day, '
WHERE 1=1
AND CalendarId IN
(
SELECT MAX (CalendarId)
FROM dbo.Calendar
WHERE MONTH (DateValue) = 5
AND DATEPART (DW, DateValue)=2
GROUP BY YEAR (datevalue)
)
-- independence day
UPDATE dbo.Calendar SET USAIsBankHoliday = 1, USADayName += 'Independence Day, ' WHERE (CalendarId % 2000) IN (704)
-- labor day (first Monday in September)
UPDATE dbo.Calendar
SET USAIsBankHoliday = 1,
USADayName += 'Labor Day, '
WHERE 1=1
AND CalendarId IN
(
SELECT MIN (CalendarId)
FROM dbo.Calendar
WHERE MONTH (DateValue) = 9
AND DATEPART (DW, DateValue)=2
GROUP BY YEAR (datevalue)
)
-- thanksgiving day (fourth Thursday in November)
UPDATE dbo.Calendar
SET USAIsBankHoliday = 1,
USADayName += 'Thanksgiving Day, '
WHERE 1=1
AND CalendarId IN
(
SELECT Max (CalendarId)
FROM dbo.Calendar
WHERE MONTH (DateValue) = 11
AND DATEPART (DW, DateValue)=5
GROUP BY YEAR (datevalue)
)
-- christmas
UPDATE dbo.Calendar SET USAIsBankHoliday = 1, USADayName += 'Christmas Day, ' WHERE (CalendarId % 2000) IN (1225)
--=========================== Other named days
-- new years eve
UPDATE dbo.Calendar SET USADayName += 'New Year''s Eve, ' WHERE (CalendarId % 2000) IN (1231)
-- christmas eve
UPDATE dbo.Calendar SET USADayName += 'Christmas Eve, ' WHERE (CalendarId % 2000) IN (1224)
-- boxing day
UPDATE dbo.Calendar SET USADayName += 'Boxing Day, ' WHERE (CalendarId % 2000) IN (1226)
--=========================== Remove trailing comma
UPDATE dbo.Calendar SET USADayName = SubString (USADayName, 1, LEN (USADayName) -1) WHERE LEN (USADayName) > 2
SELECT * FROM dbo.Calendar
I am stumped on figuring out Thanksgiving day (Thursday of the last FULL week of November).
Edit:
Correction based on comment by John Sauer
Thanksgiving is the fourth Thursday of November. However, upon checking several years, I find that it has turned out to also be the Thursday of the last full week of Nov.
I am stumped on figuring out Thanksgiving day (Thursday of the last FULL week of November).
Last Saturday of November - 2
Take the last Saturday of November, and subtract two days ;)
WITH cal AS
(
SELECT CAST('2009-30-11' AS DATETIME) AS cdate
UNION ALL
SELECT DATEADD(day, -1, cdate)
FROM cal
WHERE cdate >= '2009-01-11'
)
SELECT TOP 1 DATEADD(day, -2, cdate)
FROM cal
WHERE DATEPART(weekday, cdate) = 6
For the complex algorithms, it's sometimes better to find a matching date from a set than trying to construct an enormous single-value formula.
See this article in my blog for more detail:
Checking event dates
declare #thedate datetime
set #thedate = '11/24/2011'
select 1
where datepart(dw, #thedate) = 5 and datepart(m, #thedate) = 11 AND (datepart(dd, #thedate) - 21) between 0 and 6
Is the date a Thursday in November and is there less than a week remaining.
We use a neat Scalar-valued Function in our database that we can call to determine whether or not a date is a NonWorkDay. We manually add special dates for Observed Holidays.
Create Function:
CREATE FUNCTION [dbo].[NonWorkDay](#date as DATETIME)
RETURNS bit
AS
BEGIN
if #date is not null
begin
-- Weekends
if DATEPART(weekday, #date)=1 return 1 --Sunday
if DATEPART(weekday, #date)=7 return 1 --Saturday
-- JAN
if month(#date)=1 AND day(#date)=1 return 1 -- New Years Day
-- MAY
if month(#date)=5 and DATEPART(weekday, #date)=2 and day(#date)>24 and day(#date)<=31 return 1 --Memorial Day
-- JULY
if month(#date)=7 AND day(#date)=4 return 1 -- July 4th
-- SEP
if month(#date)=9 and DATEPART(weekday, #date)=2 and day(#date)<=7 return 1--Labor Day
-- NOV
if month(#date)=11 and DATEPART(weekday, #date)=5 and day(#date)>21 and day(#date)<=28 return 1--Thanksgiving
if month(#date)=11 and DATEPART(weekday, #date)=6 and day(#date)>22 and day(#date)<=29 return 1 -- Black Friday
-- DEC
if month(#date)=12 AND day(#date)=24 return 1 -- Christmas Eve
if month(#date)=12 AND day(#date)=25 return 1 -- Christmas
if month(#date)=12 AND day(#date)=31 return 1 -- NYE
-- Office Closed for Observed Holiday Dates
if month(#date)=7 AND day(#date)=5 AND year(#date)=2021 return 1 -- 4th of July Observed for 2021
if month(#date)=12 AND day(#date)=26 AND year(#date)=2022 return 1 -- Christmas Day observed for 2022
if month(#date)=1 AND day(#date)=2 AND year(#date)=2023 return 1 -- New Years Day observed for 2023
if month(#date)=7 AND day(#date)=3 AND year(#date)=2026 return 1 -- 4th of July Observed for 2026
if month(#date)=7 AND day(#date)=5 AND year(#date)=2027 return 1 -- 4th of July Observed for 2027
end
return 0
END
GO
Call Function:
IF [dbo].[NonWorkDay](Getdate())= 0 -- If (not a NonWorkDay -ie not Sat or Sun)