SQL Server : calculate Bi-Weekly Pay Period Dates - sql

I have the following to get bi-weekly pay period dates, The week starts on Sat
When I run it assuming today's date is 02/22/2016 I get the following
Pay Period Start: 02/13/2016
Pay Period End: 02/23/2016
But it should actually be 02/20/2016 - 3/4/2016 . It seems to be a week off.
If I start the week on Monday, then everything works fine.
What am I doing wrong, any help would be appreciated.
CREATE FUNCTION dbo.getFirstWeekDayDate (
#TargetDay DATETIME,
#strWeekDayName VARCHAR(25) --Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
)
RETURNS DATE
AS
BEGIN
DECLARE #Return DATE
-- Set to first of its year
SET #TargetDay = DATEADD(dd, -DATEPART(dayofyear, #TargetDay) + 1, #TargetDay)
;WITH Dates AS
(
SELECT #TargetDay AS DateVal
UNION ALL
SELECT DATEADD(d, 1, DateVal) AS DateVal
FROM Dates
WHERE DATEADD(d, 1, DateVal) < DATEADD(m, 1, #TargetDay)
)
SELECT #Return = MIN(DateVal)
FROM Dates
WHERE DATENAME(WEEKDAY,DateVal) = #strWeekDayName
RETURN #Return
END
GO
DECLARE
#periodstart date, #period int, #today date
set #today = '02/22/2016'
set #periodstart = dbo.getFirstWeekDayDate(#today,'Saturday') --get first sat
set #period = datediff(dd,#periodstart,#today)/14
select
#period AS period,
dateadd(dd, #period * 14, #periodstart) AS [payPeriodStart],
dateadd(dd, #period * 14 + 13, #periodstart) AS [payPeriodEnd]
Result
period payPeriodStart payPeriodEnd
----------- -------------- ------------
3 2016-02-13 2016-02-26

Ok. Thank you for all the comments. I decided to let the user specify first pay period end date in the settings and then I used that calculate the rest of the pay periods.

Related

nth day to nth month in SQL Server

I need to get date between two date range. That is nth day of nth month.
For example, I need to know 23rd day of every 2nd month between January 1, 2015 to December 30, 2015.
I need the query in T-SQL for SQL Server
You should use recursive query in MSSQL.
Here the first WITH DT is a table where you set up conditions:
WITH DT AS
(
SELECT CAST('January 1, 2015' as datetime) as dStart,
CAST('December 30, 2015' as datetime) as dFinish,
31 as nDay,
2 as nMonth
),
T AS
(
SELECT DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart), 0)
) as d,0 as MonthNumber
FROM DT
UNION ALL
SELECT DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart)
+T.MonthNumber+nMonth,0)
)as d, T.MonthNumber+nMonth as MonthNumber
FROM T,DT
WHERE DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart)
+T.MonthNumber+nMonth,0)
)<=DT.dFinish
)
SELECT d FROM T,DT WHERE DAY(d)=DT.nDay
SQLFiddle demo
Is this what you are trying to achieve?
DECLARE #startDate datetime
DECLARE #endDate datetime
DECLARE #monthToFind INT
DECLARE #dayToFind INT
SET #startDate = '01/01/2015'
SET #endDate = '12/31/2015'
SET #monthToFind = 2
SET #dayToFind = 20
IF MONTH(#startDate) + (#monthToFind - 1) BETWEEN MONTH(#startDate) AND MONTH(#endDate)
AND YEAR(#startDate) = YEAR(#endDate)
BEGIN
DECLARE #setTheDate datetime
SET #setTheDate = CAST(MONTH(#startDate) + (#monthToFind - 1) AS varchar) + '/' + CAST(#dayToFind AS varchar) + '/' + CAST(YEAR(#startDate) AS varchar)
SELECT DATENAME(DW,#setTheDate)
END
This is clearly homework, and the point of homework is to learn how things work and to solve problems, not to get others to do it for you. So - pointers for doing this properly, rather than an answer to copy and paste.
Numbers / tally tables are ideal for this sort of thing. Create a function that returns a list of sequential integers in a range. More general than a calendar table, and you can use it to derive a calendar table later if you need one.
When you've got that, DATEDIFF will give you the number of days between two dates. Use that to work out the size of your range, DATEADD to increment your date and possibly DATEPART to check that a date is the nth day of the month.
Mess about with those bits for a little while and you'll work it out.

How can I calculate the date of the upcoming second Monday in January relative to the current date in SQL?

How can I calculate the date of the upcoming second Monday in January relative to the current date using a SQL query?
Below is a query that I have which doesn't work if the current date is of the same year but prior to the upcoming second Monday of January.
declare #secondMondayOfJan date = dateadd(day , ((17 - datepart(dw,current_timestamp)) % 7) + 6, current_timestamp)
IF current_timestamp > #secondMondayOfJan
select #secondMondayOfJan as Deadline
ELSE
declare #firstDayOfNextYear date = datefromparts(year(current_timestamp)+1,1,1)
set #secondMondayOfJan = dateadd(day, ((17 - datepart(dw,#firstDayOfNextYear)) % 7) + 6, #firstDayOfNextYear)
select #secondMondayOfJan as Deadline
This looks quite verbose, but hopefully makes the logic clear, and doesn't depend on any particular settings (such as DATEFIRST or language settings):
;With Nums(n) as (
select 0 union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
), ThisAndNext as (
select
DATEADD(year,DATEDIFF(year,'20010101',GETDATE()),'20010108') as SecondWeek,
0 as Choose
union all
select
DATEADD(year,DATEDIFF(year,'20010101',GETDATE()),'20020108'),
1
), Combined as (
select DATEADD(day,n,SecondWeek) as Possible,Choose
from ThisAndNext cross join Nums
)
select top 1 Possible from Combined
where Possible >= DATEADD(day,DATEDIFF(day,0,GETDATE()),0)
and DATEPART(weekday,Possible) = DATEPART(weekday,'20150504')
order by Choose
Nums is just a small set of numbers - it can be replaced with a select from a numbers table, if you already have one.
ThisAndNext finds the 8th of this year and next year, as two separate rows.
Combined adds between 0 and 6 days onto the 8th of this year and next.
Finally, we select the first date from Combined that is (the following bullet points correspond to the lines of the WHERE clause)
equal to or greater than today's date (using another instance of the DATEADD/DATEDIFF pattern to remove the time element from GETDATE())
on a Monday (by comparing it to an arbitrary, well known Monday)
by preference, from this year rather than next
All taken together, this means that we have selected a future (or current) day that falls between the 8th and the 14th of January that is a Monday. If you don't want "today" to be a possible result, just change the >= comparison towards the end to be a >.
The logic behind this code is:
get the first day of the current year
get the first day of the next year
find the 2nd weekday of jan of the current year
find the 2nd weekday of jan of the next year
compare the current date if is greater than or less than or equal and based on this set the deadline to the correct 2nd weekday
It's setup to retrieve the 2nd monday, but changing the value of the variable #dw between 1 to 7, you will get the others weekday
(1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday)
You can play with this code at:
http://rextester.com/TVKYW75798
/*
THIS STATEMENT WILL RETURN THE 2ND WEEKDAY OF JAN BASED ON CURRENT DATE.
IF CURRENT_DATE = 2ND WEEKDAY OF JAN
THEN DEADLINE = CURRENT_DATE
IF CURRENT_DATE < 2ND WEEKDAY OF JAN
THEN DEADLINE = 2ND WEEKDAY OF JAN (CURRENT YEAR)
IF CURRENT_DATE > 2ND WEEKDAY OF JAN
THEN DEADLINE = 2ND WEEKDAY OF JAN (NEXT YEAR)
YOU CAN PLAY WITH VARIABLE #date and #dw TO TEST.
I LEAVE COMMENTED IN THE CODE FOUR SCENARIOS (YEAR 2015)
WHERE YOU WILL BE ABLE TO CHECK THE CONDITIONS ABOVE.
2015-01-12 WAS THE 2ND MONDAY OF JAN
*/
DECLARE #date DATE = GETDATE()
-- SET #date = '2015-01-01'
-- SET #date = '2015-01-11'
-- SET #date = '2015-01-12'
-- SET #date = '2015-01-13'
DECLARE #currentYearFirstDay DATE = CAST(datepart(yy, #date) AS VARCHAR) + '-01-01'
DECLARE #nextYearFirstDay DATE = DATEADD(yy, 1, #currentYearFirstDay)
DECLARE #d DATE
DECLARE #secondWeekdayCurrentYear DATE
DECLARE #secondWeekdayNextYear DATE
DECLARE #dw INT = 2 /*USE VALUES FROM 1 TO 7, WHERE 1 = SUNDAY AND 7 = SATURDAY*/
DECLARE #weekOfYear INT = DATEPART(ww,#d)
DECLARE #weekDay INT = DATEPART(dw,#d)
DECLARE #checkNextYear INT = 0
SET #d = #currentYearFirstDay
GOTO GET_2nd_WEEKDAY_FROM_FIRST_DAY_OF_YEAR
GET_2nd_WEEKDAY_FROM_FIRST_DAY_OF_YEAR:
BEGIN
SET #weekDay = DATEPART(dw, #d)
/*FIRST DAY OF YEAR GREATER THAN WEEKDAY, THEN 2nd WEEKDAY WILL BE THE 3rd WEEK*/
IF #weekDay > #dw
BEGIN
SET #d = DATEADD(ww, 2, #d)
END
/*FIRST DAY OF YEAR LESS THAN OR EQUAL WEEKDAY, THEN 2nd WEEKDAY WILL BE THE 2nd WEEK*/
ELSE
BEGIN
SET #d = DATEADD(ww, 1, #d)
END
IF #checkNextYear = 0
BEGIN
SET #secondWeekdayCurrentYear = DATEADD(dw, -(#weekDay - #dw), #d)
SET #d = #nextYearFirstDay
SET #checkNextYear = 1
GOTO GET_2nd_WEEKDAY_FROM_FIRST_DAY_OF_YEAR
END
ELSE
BEGIN
SET #secondWeekdayNextYear = DATEADD(dd, -(#weekDay - #dw), #d)
GOTO EXIT_BLOCK
END
END
EXIT_BLOCK:
SELECT CASE
WHEN #date > #secondWeekdayCurrentYear
THEN #secondWeekdayNextYear
ELSE
CASE
WHEN #date = #secondWeekdayCurrentYear
THEN #date
ELSE #secondWeekdayCurrentYear
END
END AS DEADLINE

calculating working hours in a month

I am working on an attendance software in asp.net, in it i have to make a report which will tell the user about the hours and everything...so far i have created the basic functionality of the system, i.e. the user can check in and check out...i am stuck at making the report...
I have to calculate the working hours for every month, so the user can compare his hours with the total hours...what i had in mind was to create a stored procedure which when given a month name and a year, returns an int containing working hours for that month....but i can seem to get at it....
so far i found out how to create a date from a given month and a date, and found out the last day of that month, using which i can find out the total days in month...now i cant seem to figure out how do i know how much days to subtract for getting the working days.
here's the so far code..
declare
#y int,
#m int,
#d int,
#date datetime
set #y = 2012
set #m = 01
set #d = 01
----To create the date first
select #date = dateadd(mm,(#y-1900)* 12 + #m - 1,0) + (#d-1)
----Last Day of that date
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#date)+1,0))
any help will be appreciated guys, thanks in advance....
The #theDate is any date on the month you want to calculate the work days. This approach does not take care about holidays.
DECLARE #theDate DATETIME = GETDATE()
SELECT MONTH(#theDate) [Month], 20 + COUNT(*) WorkDays
FROM (
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 28) AS theDate
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 29)
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 30)
) AS d
WHERE DATEPART(DAY, theDate) > 28
AND DATEDIFF(DAY, 0, theDate) % 7 < 5
Here you can consider the below sql server code to get the first and
last day of the given month and also ignore all the Saturdays and Sundays.
DECLARE #curr_date datetime=getdate()
DECLARE #st_date datetime,#ed_date datetime
select #st_date=DATEADD(mm,datediff(mm,0,#curr_date),0),#ed_date = DATEADD(mm,datediff(mm,-1,#curr_date),-1)
--select #st_date as first_day,#ed_date as last_day
SET DATEFIRST 1 --Monday as first day of week
select DATEADD(dd,number,#st_date) from master..spt_values
where DATEDIFF(dd,DATEADD(dd,number,#st_date),#ed_date) >= 0 and type='P'
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 6
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 7
But inorder to calculate the actual working hours, you will have to take into the consideration of following thigs
1.Calculate the time interval between swipe-in and swipe-outs between start and end time for a day.
2.Exclude all the time gap(employee not in office)
3.Consider the company holidays.
etc
Here is a UDF to count work days. You can pass any date of a month to this function. But usually you should use actual "calendar" table to calculate work days and insert in this table weekends, holidays,... and so on.
CREATE FUNCTION dbo.WorkDaysCount (#Date datetime)
RETURNS int AS
BEGIN
DECLARE #BeginOfMonth datetime
SET #BeginOfMonth=DATEADD(DAY,-DAY(#Date)+1,#Date);
DECLARE #EndOfMonth datetime
SET #EndOfMonth=DATEADD(Day,-1,DATEADD(Month,1,#BeginOfMonth));
DECLARE #cDate datetime
set #cDate=#BeginOfMonth
Declare #WorkDaysCount int
SET #WorkDaysCount=0
while #cDate<=#EndOfMonth
begin
if DATEPART(dw,#cDate) not in (1,7) SET #WorkDaysCount=#WorkDaysCount+1 -- not a Sunday or Saturday change (1,7) to (6,7) if you have other week start day (Monday).
set #cDate=#cDate+1;
end;
return (#WorkDaysCount);
END

Get first Sunday of next month using T-SQL

Looking for a way to get the date in the format "11/1/2009", which would be the first sunday of next month. I want to run this query after the first sunday in october to get the first sunday of the upcoming month. What is the best method to accomplish this with a T-SQL query?
Thanks
try this:
Declare #D Datetime
Set #D = [Some date for which you want the following months' first sunday]
Select DateAdd(day, (8-DatePart(weekday,
DateAdd(Month, 1+DateDiff(Month, 0, #D), 0)))%7,
DateAdd(Month, 1+DateDiff(Month, 0, #D), 0))
EDIT Notes:
The first of next Month is given by the expression:
DateAdd(Month, 1+DateDiff(Month, 0, #D), 0)
or by:
which can be modified to give the first of the month two months from now by changing the 1 to a 2:
DateAdd(Month, 2+DateDiff(Month, 0, #D), 0)
EDIT: In response to #NissanFan, and #Anthony: to modify this to return the first Monday Tuesday Wednesday, etc, change the value 8 to a 9, 10, 11, etc....
Declare #Sun TinyInt Set #Sun = 8
Declare #Mon TinyInt Set #Mon = 9
Declare #Tue TinyInt Set #Tue = 10
Declare #Wed TinyInt Set #Wed = 11
Declare #Thu TinyInt Set #Thu = 12
Declare #Fri TinyInt Set #Fri = 13
Declare #Sat TinyInt Set #Sat = 14
Declare #D Datetime, #FONM DateTime -- FirstofNextMonth
Set #D = [Some date for which you want the following months' first sunday]
Set #FONM = DateAdd(Month, 1+DateDiff(Month, 0, #D),0)
Select
DateAdd(day, (#Sun -DatePart(weekday, #FONM))%7, #FONM) firstSunInNextMonth,
DateAdd(day, (#Mon -DatePart(weekday, #FONM))%7, #FONM) firstMonInNextMonth,
DateAdd(day, (#Tue -DatePart(weekday, #FONM))%7, #FONM) firstTueInNextMonth,
DateAdd(day, (#Wed -DatePart(weekday, #FONM))%7, #FONM) firstWedInNextMonth,
DateAdd(day, (#Thu -DatePart(weekday, #FONM))%7, #FONM) firstThuInNextMonth,
DateAdd(day, (#Fri -DatePart(weekday, #FONM))%7, #FONM) firstFriInNextMonth,
DateAdd(day, (#Sat -DatePart(weekday, #FONM))%7, #FONM) firstSatInNextMonth
Just an FYI rather then coming up with some code to do this how about using a calendar table.
Take a look at this: http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
This also may help:
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=99696
Here is a query to get first working day of next month
DECLARE #DAYOFWEEK INT,#ReminderDate DateTime
SET #DAYOFWEEK = DATEPART( WEEKDAY,DateAdd(D,- Day(GetDate())+1, DATEADD(M,1,GetDate())) )
Print #DAYOFWEEK
If #DAYOFWEEK = 1
Set #ReminderDate = DateAdd(D,- Day(GetDate())+2, DATEADD(M,1,GetDate()))
Else If #DAYOFWEEK =7
Set #ReminderDate = DateAdd(D,- Day(GetDate())+3, DATEADD(M,1,GetDate()))
Else
Set #ReminderDate = DateAdd(D,- Day(GetDate())+1, DATEADD(M,1,GetDate()))
Print #ReminderDate
Reference taken from this blog:
SQL Server 2012 introduced one new TSQL EOMONTH to return the last day of the month that contains the specified date with an optional offset.
CREATE TABLE tbl_Test_EOMONTH
(
SampleDate DATETIME
)
GO
INSERT INTO tbl_Test_EOMONTH VALUES ('2015-12-20')
INSERT INTO tbl_Test_EOMONTH VALUES ('2015-11-08')
INSERT INTO tbl_Test_EOMONTH VALUES ('2015-10-16')
INSERT INTO tbl_Test_EOMONTH VALUES ('2015-09-26')
INSERT INTO tbl_Test_EOMONTH VALUES ('2016-01-31')
GO
SELECT
DATEADD(DAY,8-DATEPART(WEEKDAY,DATEADD(DAY,0,EOMONTH([SampleDate])))
,EOMONTH([SampleDate])) AS FirstSunday_ofTheNextMonth
FROM tbl_Test_EOMONTH
GO
You can use DATENAME to determine the day you want, I might recommend a loop to move the date from the 01 of the month in question to get to the first sunday.
So lets try:
DECLARE #DateTime DATETIME
Set to the date to start off with, then add 1 day until you find what you are looking for. Use datename with dw...
We have used this to determine weekends, but holidays will be a problem, where we use a table to store that.
Try this code as a function:
-- Variables
DECLARE #DATE DATETIME
DECLARE #DAY INT
DECLARE #DAYOFWEEK INT
DECLARE #TESTDATE DATETIME
-- Set
SET #TESTDATE = GETDATE()
SET #DATE = DATEADD( MONTH, 1, #TESTDATE )
SET #DAY = DATEPART( DAY, #TESTDATE )
SET #DATE = DATEADD( DAY, -#DAY + 1, #DATE )
SET #DAYOFWEEK = DATEPART( WEEKDAY, #DATE )
IF #DAYOFWEEK > 1
BEGIN
SET #DAYOFWEEK = 8 - #DAYOFWEEK
END
ELSE
BEGIN
SET #DAYOFWEEK = 0
END
SET #DATE = DATEADD( DAY, #DAYOFWEEK, #DATE )
-- Display
PRINT #TESTDATE
PRINT #DAY
PRINT #DAYOFWEEK
PRINT #DATE
Here is the non-system specific way to determine the first Sunday of the following month:
First, get the current month and add one month.
Next, set the date of that variable to be on the first.
Next, find the day value of that date (let's assume Mondays are 1 and Sundays are 7).
Next, subtract the day value of the 1st of the month from the day value of Sunday (7).
You now have the number of days between the first of the month and the first Sunday. You could then add that to the date variable to get the first Sunday, or, since we know the first of the month is 1, you could just add one to the difference (found in that last step above) and that is the date of the first Sunday. (You have to add one because it's subtracting and thus if the first of the given month IS Sunday, you'd end up with 0).
I have been looking through the T-SQL documentation and it is not at all intuitive as to how how you would use my method, but you will need the concept of "day of week number" to make it work no matter what.
This would be simplest with an auxiliary calendar table. See this link, for example.
However, it can be done without one, though it's rather tricky. Here I assume you want the first future date that is the first Sunday of a month. I've written this with a variable #now for - well - now, so it's easier to test. It might be possible to write more simply, but I think this has a better chance of being correct than quickly-written simpler solutions. [Corrected by adding DAY(d) < 8]
SET #now = GETDATE();
WITH Seven(i) AS (
SELECT -1 UNION ALL SELECT 0 UNION ALL SELECT 1
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
), Candidates(d) AS (
SELECT DATEADD(WEEK,i+DATEDIFF(WEEK,'19000107',#now),'19000107')
FROM Seven
)
SELECT TOP (1) d AS SoonestFutureFirstSunday
FROM Candidates
WHERE DAY(d) < 8 AND MONTH(d) >= MONTH(#now)
AND (MONTH(d) > MONTH(#now) OR DAY(d) > DAY(#now))
ORDER BY d; ORDER BY d;
I reckon that the answer is this
SELECT DATEADD(Month, DATEDIFF(Month, 0, GETDATE()) + 1, 0) + 6 - (DATEPART(Weekday,
DATEADD(Month,
DATEDIFF(Month,0, GETDATE()) + 1, 0))
+ ##DateFirst + 5) % 7 --FIRST sunday of following month

Calculating in SQL the first working day of a given month

I have to calculate all the invoices which have been paid in the first 'N' days of a month. I have two tables
. INVOICE: it has the invoice information. The only field which does matter is called 'datePayment'
. HOLYDAYS: It is a one column table. Entries at this table are of the form "2009-01-01",
2009-05-01" and so on.
I should consider also Saturdays and Sundays
(this might be not a problem because I could insert those days at the Hollidays table in order to consider them as hollidays if neccesary)
The problem is to calculate which is the 'payment limit'.
select count(*) from invoice
where datePayment < PAYMENTLIMIT
My question is how to calculate this PAYMENTLIMIT. Where PAYMENTLIMIT is 'the fifth working day of every month'.
The query should be run under Mysql and Oracle therefore standard SQL should be used.
Any hint?
EDIT
In order to be consistent with the title of the question the pseudo-query should the read as follows:
select count(*) from invoice
where datePayment < FIRST_WORKING_DAY + N
then the question can be reduced to calculate the FIRST_WORKING_DAY of every month.
You could look for the first date in a month, where the date is not in the holiday table and the date is not a weekend:
select min(datePayment), datepart(mm, datePayment)
from invoices
where datepart(dw, datePayment) not in (1,7) --day of week
and not exists (select holiday from holidays where holiday = datePayment)
group by datepart(mm, datePayment) --monthnr
Something like this might work:
create function dbo.GetFirstWorkdayOfMonth(#Year INT, #Month INT)
returns DATETIME
as begin
declare #firstOfMonth VARCHAR(20)
SET #firstOfMonth = CAST(#Year AS VARCHAR(4)) + '-' + CAST(#Month AS VARCHAR) + '-01'
declare #currDate DATETIME
set #currDate = CAST(#firstOfMonth as DATETIME)
declare #weekday INT
set #weekday = DATEPART(weekday, #currdate)
-- 7 = saturday, 1 = sunday
while #weekday = 1 OR #weekday = 7
begin
set #currDate = DATEADD(DAY, 1, #currDate)
set #weekday = DATEPART(weekday, #currdate)
end
return #currdate
end
I'm not 100% sure about whether the "weekday" numbers are fixed or might depend on your locale on your SQL Server. Check it out!
Marc
Rather than a Holidays table of days to exclude, we use the calendar table approach: one row for every day the application will ever need (thirty years spans a modest 11K rows). So not only does it have an is_weekday column, it has other things relevant to the enterprise e.g. julianized_date. This way, every possible date would have a ready-prepared value for first_working_day_this_month and finding it involves a simple lookup (which SQL products tend to be optimized for!) rather than 'calculating' it each time on the fly.
We have dates table in our application (filled with all dates and date parts for some tens of years), what allows various "missing" date manipulations, like (in pseudo-sql):
select min(ourdates.datevalue)
from ourdates
where ourdates.year=<given year> and ourdates.month=<given month>
and ourdates.isworkday
and not exists (
select * from holidays
where holidays.datevalue=ourdates.datevalue
)
Ok, at a first stab, you could put the following code into a UDF and pass in the Year and Month as variables. It can then return TestDate which is the first working day of the month.
DECLARE #Month INT
DECLARE #Year INT
SELECT #Month = 5
SELECT #Year = 2009
DECLARE #FirstDate DATETIME
SELECT #FirstDate = CONVERT(varchar(4), #Year) + '-' + CONVERT(varchar(2), #Month) + '-' + '01 00:00:00.000'
DROP TABLE #HOLIDAYS
CREATE TABLE #HOLIDAYS (HOLIDAY DateTime)
INSERT INTO #HOLIDAYS VALUES('2009-01-01 00:00:00.000')
INSERT INTO #HOLIDAYS VALUES('2009-05-01 00:00:00.000')
DECLARE #DateFound BIT
SELECT #DateFound = 0
WHILE(#DateFound = 0)
BEGIN
IF(
DATEPART(dw, #FirstDate) = 1
OR
DATEPART(dw, #FirstDate) = 1
OR
EXISTS(SELECT * FROM #HOLIDAYS WHERE HOLIDAY = #FirstDate)
)
BEGIN
SET #FirstDate = DATEADD(dd, 1, #FirstDate)
END
ELSE
BEGIN
SET #DateFound = 1
END
END
SELECT #FirstDate
The things I don`t like with this solution though are, if your holidays table contains all days of the month there will be an infinite loop. (You could check the loop is still looking at the right month) It relies upon the dates being equal, eg all at time 00:00:00. Finally, the way I calculate the 1st of the month past in using string concatenation was a short cut. There are much better ways of finding the actual first day of the month.
Gets the first N working days of each month of year 2009:
select * from invoices as x
where
datePayment between '2009-01-01' and '2009-12-31'
and exists
(
select
1
from invoices
where
-- exclude holidays and sunday saturday...
(
datepart(dw, datePayment) not in (1,7) -- day of week
/*
-- Postgresql and Oracle have programmer-friendly IN clause
and
(datepart(yyyy,datePayment), datepart(mm,datePayment))
not in (select hyear, hday from holidays)
*/
-- this is the MSSQL equivalent of programmer-friendly IN
and
not exists
(
select * from holidays
where
hyear = datepart(yyyy,datePayment)
and hmonth = datepart(mm, datePayment)
)
)
-- ...exclude holidays and sunday saturday
-- get the month of x datePayment
and
(datepart(yyyy, datePayment) = datepart(yyyy, x.datePayment)
and datepart(mm, datePayment) = datepart(mm, x.datePayment))
group by
datepart(yyyy, datePayment), datepart(mm, datePayment)
having
x.datePayment < MIN(datePayment) + #N -- up to N working days
)
Returns the first Monday of the current month
SELECT DATEADD(
WEEK,
DATEDIFF( --x weeks between 1900-01-01 (Monday) and inner result
WEEK,
0, --1900-01-01
DATEADD( --inner result
DAY,
6 - DATEPART(DAY, GETDATE()),
GETDATE()
)
),
0 --1900-01-01 (Monday)
)
SELECT DATEADD(day, DATEDIFF (day, 0, DATEADD (month, DATEDIFF (month, 0, GETDATE()), 0) -1)/7*7 + 7, 0);
select if(weekday('yyyy-mm-01') < 5,'yyyy-mm-01',if(weekday('yyyy-mm-02') < 5,'yyyy-mm-02','yyyy-mm-03'))
Saturdays and Sundays are 5, 6 so you only need two checks to get the first working day