Calculating Month Start/End With A Fixed Last Day Of Month - sql

I need to loop through each day within a month but with a fixed number as the last day of the month.
E.g. If the last day of the month is fixed as 30,
January would become:
Start of Month: Dec 31 2012 12:00AM
End of Month: Jan 30 2013 12:00AM
I have implemented the following which works perfectly except for the month of February. I can't seem to find a solution that will ensure this always works for every month no matter what the month end date is.
Any suggestions are much appreciated.
DECLARE #dt DATETIME
DECLARE #DayInstance DATETIME
DECLARE #LastDayOfMonth DATETIME
DECLARE #monthEndDate INT = 30
SET #dt = '2012-01-01 00:00:00.000'
WHILE #dt < GETDATE()
BEGIN
SET #DayInstance = #dt
SET #LastDayOfMonth = (SELECT CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,#dt))),DATEADD(mm,1,#dt)),101))
IF #monthEndDate > 0
BEGIN
SET #LastDayOfMonth = DATEADD(DAY,(#monthEndDate-DATEPART(dd,#LastDayOfMonth)),#LastDayOfMonth)
SET #DayInstance = DATEADD(MONTH, -1, #LastDayOfMonth)
SET #DayInstance = DATEADD(DAY, 1, #DayInstance)
END
PRINT 'Month Start Date: ' + CAST(#DayInstance AS NVARCHAR(20))
PRINT 'Month End Date: ' + CAST(#LastDayOfMonth AS NVARCHAR(20))
PRINT ''
WHILE #DayInstance <= #LastDayOfMonth
BEGIN
-- Going to do more stuff here
SET #DayInstance = DATEADD(DAY, 1, #DayInstance)
END
SET #dt = DATEADD(MONTH, 1, #dt)
END

I think that this query (it can be simplified still further, but I want to show how I was thinking) sets #StartDate and #EndDate to suitable dates - you can then do your iteration between these two values:
DECLARE #dt DATETIME
DECLARE #DayInstance DATETIME
DECLARE #monthEndDate INT = 30
SET #dt = '2012-01-01 00:00:00.000'
DECLARE #MagicDate1 DATETIME
DECLARE #MagicDate2 DATETIME
SELECT #MagicDate1 = DATEADD(day,#monthEndDate-1,'20010101'),
#MagicDate2 = DATEADD(day,#monthEndDate-1,'20001201')
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT #StartDate = DATEADD(day,1,DATEADD(month,DATEDIFF(month,'20010101',#dt),
#MagicDate2)),
#EndDate = DATEADD(month,DATEDIFF(month,'20010101',#dt),#MagicDate1)
select #StartDate,#EndDate
I first construct two "magic" dates. Those are, respectively the Nth day of January 2001 and the Nth day of December 2000, where N is the desired month end date. Notice that the choice of 2000/2001 was arbitrary, and never need to be changed.1
What we then do in the final expressions is to work out how many months have elapsed between January 2001 and your #dt variable. If we then add that same number of months onto our two "magic" dates then we end up with the Nth day of the same month as #dt and the Nth day of the previous month to #dt (Or the last day of either month if the month has fewer than N days).
Finally, we adjust what we've found as the Nth day of last month by adding 1 to it - which should be the "first" day of the current month.
1 The only important thing is to pick two months which both have 31 days and are consecutive in the calendar. The year is arbitrary.

You've slightly over-complicated things I think the following I believe does what you want it to do:
DECLARE #dt DATETIME
DECLARE #LastDayOfMonth DATETIME
SET #dt = '2012-01-01 00:00:00.000'
WHILE #dt < GETDATE()
BEGIN
SET #LastDayOfMonth = DATEADD(DAY,-1,(DATEADD(MONTH,1,#dt)))
PRINT 'Month Start Date: ' + CAST(#dt AS NVARCHAR(20))
PRINT 'Month End Date: ' + CAST(#LastDayOfMonth AS NVARCHAR(20))
PRINT ''
SET #dt = DATEADD(MONTH, 1, #dt)
END
This will work so long as #dt is the first of the month, otherwise you will have to create a new #FirstDayOfMonth variable and set this above the Setting of `#LastDayOfMonth', but the above should be a good starting point.

Related

Getting number of days for a specific month and year between two dates SQL

I want to retrieve the number of days between two dates that overlap a specific month.
For Example :
Month = 1
Year = 2020
StartDate ='2019-11-12'
ENDDate ='2020-1-13'
Result = 13 days
13 because there are 13 days between the dates that are in the selected month: January 2020
Other Example:
Month=9
Year =2019
StartDate = '2019-8-13'
ENDDate ='2020-1-1'
Result = 30 days
30 because there are 30 days between the dates that are in the selected month: September 2019
The generic formula for the number of overlapping days in two ranges is
MAX(MIN(end1, end2) - MAX(start1, start2) + 1, 0)
In your case you have one set of Start and End dates, you must construct the other from the given month and year using datefromparts and eomonth.
Unfortunately SQL Server doesn't support LEAST and GREATEST formulas as do MySQL and Oracle, so this is a bit painful to implement. Here's an example using variables:
declare #month int;
declare #year int;
declare #startDate date;
declare #endDate date;
declare #startOfMonth date;
declare #endOfMonth date;
declare #minEnd date;
declare #maxStart date;
set #month = 1;
set #year = 2020;
set #startDate = '2019-11-12';
set #endDate = '2020-01-13';
set #startOfMonth = datefromparts(#year, #month, 1)
set #endOfMonth = eomonth(#startOfMonth)
set #minEnd = case when #endDate < #endOfMonth then #endDate
else #endOfMonth
end;
set #maxStart = case when #startDate < #startOfMonth then #startOfMonth
else #startDate
end;
select case when datediff(day, #maxStart, #minEnd) + 1 < 0 then 0
else datediff(day, #maxStart, #minEnd) + 1
end as days_in_month
Output:
13
Demo on dbfiddle; this includes other sample date ranges.
You could implement something similar using a series of CTEs if the values are derived from a table.

How to get date to be the same day and month from one variable but in the year based on another variable?

I am trying to get the specific day of a Year.
Here's what I have tried till now:-
-- Declare few variables
DECLARE #Currentdate AS DATETIME
DECLARE #DueDate AS DATETIME
DECLARE #NewDate AS DATETIME
-- Set the variables properly, just for testing
SET #Currentdate = GETDATE()
SET #DueDate = DATEADD(MONTH, 2, DATEADD(YEAR, 1, #Currentdate))
-- Check the output
SELECT #Currentdate -- 2013-09-30 00:00:00.000
SELECT #DueDate -- 2014-11-30 00:00:00.000
So, I want to get the #NewDate based on the #Currentdate year.
For this I tried:-
SELECT #NewDate = DATEADD(DAY, DAY(DATEDIFF(day, 1, #DueDate)), DATEADD(MONTH, DATEDIFF(MONTH, 0, #Currentdate), 0))
SELECT #NewDate -- 2013-09-30 00:00:00.000
But it didn't worked. :(
My expected result is like:
-- 2013-11-30 00:00:00.000
-- Having the due date month and date same, but the year as current date one.
Any help is appreciated!
UPDATE
Sorry for all the confusion I have created. My question in simple words is:-
I want to get the a new date variable having the date and the month same as #DueDate variable but the year as given in the #Currentdate variable.
I hope that would clear things up a bit.
If the question is "given I have a particular datetime value in one variable, can I set another variable to be for the same day and month but in the current year" then the answer would be:
declare #DueDate datetime
declare #NewDate datetime
set #DueDate = '20141130'
--Need to set #NewDate to the same month and day in the current year
set #NewDate = DATEADD(year,
--Here's how you work out the offset
DATEPART(year,CURRENT_TIMESTAMP) - DATEPART(year,#DueDate),
#DueDate)
select #DueDate,#NewDate
I want to get the a new date variable having the date and the month same as #DueDate variable but the year as given in the #Currentdate variable.
Well, that's simply the above query with a single tweak:
set #NewDate = DATEADD(year,
--Here's how you work out the offset
DATEPART(year,#Currentdate) - DATEPART(year,#DueDate),
#DueDate)
Try this instead ?
DECLARE #Currentdate AS DATETIME
DECLARE #DueDate AS DATETIME
-- Set the variables properly, just for testing
SET #Currentdate = GETDATE()
SET #DueDate = DATEADD(MONTH, 2, DATEADD(YEAR, 1,
DateAdd(day, datediff(day, 0, #currentDate), 0)))
-- Check the output
SELECT #Currentdate -- 2013-09-30 18:32:35.310
SELECT #DueDate
Using DateAdd(day, datediff(day, 0, #DateTime), 0) strips off the time portion. You should also check out this SO Question/answer.
Try this one:
CAST(CAST( -- cast INT to VARCHAR and then to DATE
YEAR(GETDATE()) * 10000 + MONTH(#DueDate) * 100 + DAY(#DueDate) -- convert current year + #DueDate's month and year parts to YYYYMMDD integer representation
+ CASE -- change 29th of February to 28th if current year is a non-leap year
WHEN MONTH(#DueDate) = 2 AND DAY(#DueDate) = 29 AND ((YEAR(GETDATE()) % 4 = 0 AND YEAR(GETDATE()) % 100 <> 0) OR YEAR(GETDATE()) % 400 = 0) THEN 0
ELSE -1
END
AS VARCHAR(8)) AS DATE)

T-SQL - Get start and end date based on week number.

Our business considers a week from (Monday - Sunday). I need to write a T-SQL function, which passes in year, week no as parameters and it will return the start and end date of that week. However I've seen many examples but the problem lies within the year overlapping.
e.g December 26, 2011 (Monday) - January 01, 2012 (Sunday)... << Would want to consider this as the last week of 2011.
And also in T-SQL the datepart(ww,DATE) considers Sunday as the start of the week??
Or Am I better creating my own table with the week no and storing its start and end date?
DECLARE
#Year INT,
#Week INT,
#FirstDayOfYear DATETIME,
#FirstMondayOfYear DATETIME,
#StartDate DATETIME,
#EndDate DATETIME
SET #Year = 2011
SET #Week = 52
-- Get the first day of the provided year.
SET #FirstDayOfYear = CAST('1/1/' + CAST(#YEAR AS VARCHAR) AS DATETIME)
-- Get the first monday of the year, then add the number of weeks.
SET #FirstMondayOfYear = DATEADD(WEEK, DATEDIFF(WEEK, 0, DATEADD(DAY, 6 - DATEPART(DAY, #FirstDayOfYear), #FirstDayOfYear)), 0)
SET #StartDate = DATEADD(WEEK, #Week - 1, #FirstMondayOfYear)
-- Set the end date to one week past the start date.
SET #EndDate = DATEADD(WEEK, 1, #StartDate)
SELECT #StartDate AS StartDate, DATEADD(SECOND, -1, #EndDate) AS EndDate
You should create a table with the holidays and days you donĀ“t wnat to consider in your counts.
After this count the days between the initial date and final date. (Step1)
Select in your table to check how many days are between your ini and final dates. (Step2)
Finally subtract the result of step 2 with result of step 1;
This is the sort of thing where you're better off creating a calendar table: the main issue is knowing how far into the past/future to populate it, but beyond that, have the table schema include week number and date. Depending on what other date-based queries you want to do, you might want to have additional column to break the date down into its constituant parts (e.g., have three separate columns for day/month/year/name of day, etc).
How about this one:
--DROP FUNCTION dbo.GetBusinessWeekStart
CREATE FUNCTION dbo.GetBusinessWeekStart(
#Year SMALLINT,
#Week TINYINT
)
RETURNS DATETIME
AS
BEGIN
DECLARE #FirstMonday TINYINT
DECLARE #Result DATETIME
IF ISNULL(#Week,0)<1 OR ISNULL(#Year,0)<1900
BEGIN
SET #Result= NULL;
END
ELSE
BEGIN
SET #FirstMonday=1
WHILE DATEPART(dw,CONVERT(DATETIME, '01/0' + CONVERT(VARCHAR,#FirstMonday) + '/' + CONVERT(VARCHAR,#Year)))<>2
BEGIN
SET #FirstMonday=#FirstMonday+1
END
SET #Result=CONVERT(DATETIME, '01/0' + CONVERT(VARCHAR,#FirstMonday) + '/' + CONVERT(VARCHAR,#Year))
SET #Result=DATEADD(d,(#Week-1)*7,#Result)
IF DATEPART(yyyy,#Result)<>#Year
BEGIN
SET #Result= NULL;
END
END
RETURN #Result
END
GO
--Example
SELECT dbo.GetBusinessWeekStart(2011,15) [Start],dbo.GetBusinessWeekStart(2011,15)+6 [End]

SQL How to determine if date month date contains 29,30 or 31

This is what I am trying to do. I need to add one month to the transaction date so that the next month effective date
start on the same day as the starting effective date. If the starting Effective day is (30, 31) is not in the next month (eg. feb 28), then it should give (march 1st) as a next effective date. The transaction date is simply to know which month to use to add a month.
For example, the next Month effective date is showing fine as in this example
'2011-04-20'.
declare #StartEffectiveDate datetime
declare #transactiondate datetime
declare #NextMonthEffectivedate datetime
set #StartEffectiveDate = '2011-01-20'
set #transactiondate = '2011-03-14'
--calculating next month effective date. incremening transactiondate by 1, but on same day --as the starting effective date.
set #NextMonthEffectivedate = dateadd(month,month(#transactiondate)-month(#StartEffectiveDate)+1,#Starteffectivedate)
But,
if the #StartEffectiveDate is on '2011-01-31', #transactiondate = '2011-01-30', then the result for Next month effective date should be '2011-03-01' because 2011-02-31' is not a valid date.
How to check if next month date has the starting effective day or not. In this example, how to check if Feb has 31 or not. If it doesn't have 31, then it should show '2011-03-01'
Many thanks for your help!!!
After adding a month, compare DAY. If less because DATEADD goes to end of the month instead (eg 31 Jan to 28 Feb), then skip to next month
The DATEADD/DATEDIFF here skips to the start of the following month
declare #StartEffectiveDate datetime
set #StartEffectiveDate = '2011-01-20'
SELECT
CASE
WHEN DAY(#StartEffectiveDate) <= DAY(DATEADD (MONTH, 1, #StartEffectiveDate)) THEN DATEADD (MONTH, 1, #StartEffectiveDate)
ELSE DATEADD(day, 1, DATEADD (MONTH, 1, #StartEffectiveDate))
END
set #StartEffectiveDate = '2011-01-31'
SELECT
CASE
WHEN DAY(#StartEffectiveDate) <= DAY(DATEADD (MONTH, 1, #StartEffectiveDate)) THEN DATEADD (MONTH, 1, #StartEffectiveDate)
ELSE DATEADD(day, 1, DATEADD (MONTH, 1, #StartEffectiveDate))
END
set #StartEffectiveDate = '2011-02-28'
SELECT
CASE
WHEN DAY(#StartEffectiveDate) <= DAY(DATEADD (MONTH, 1, #StartEffectiveDate)) THEN DATEADD (MONTH, 1, #StartEffectiveDate)
ELSE DATEADD(day, 1, DATEADD (MONTH, 1, #StartEffectiveDate))
END
Edit: only need to add an extra day rather then some fancy DATEADD/DATEDIFF...
This should work:
declare #StartEffectiveDate datetime
declare #transactiondate datetime
declare #NextMonthEffectivedate datetime
set #StartEffectiveDate = '20110131'
set #transactiondate = '20110130'
--calculating next month effective date. incremening transactiondate by 1, but on same day --as the starting effective date.
set #NextMonthEffectivedate = dateadd(month,DATEDIFF(month,#StartEffectiveDate,#transactionDate)+1,#StartEffectiveDate)
if DATEPART(day,#StartEffectiveDate) <> DATEPART(day,#NextMonthEffectiveDate)
begin
--rounding occurred - next month isn't long enough.
set #NextMonthEffectivedate = DATEADD(day,1,#NextMonthEffectiveDate)
end
print #NextMonthEffectivedate
select datepart(day, getdate())
This gets you the day of the month. So here's the full script, though I didn't understand what you're doing with #StartEffectiveDate so I just kept that the same:
declare #StartEffectiveDate datetime
declare #transactiondate datetime
declare #NextMonthEffectivedate datetime
set #StartEffectiveDate = '2011-01-20'
set #transactiondate = '2011-03-30'
--calculating next month effective date. incremening transactiondate by 1, but on same day --as the starting effective date.
set #NextMonthEffectivedate =
case
when datepart(day, #transactiondate) > 28 then dateadd(month,2,dateadd(day,-1*(datepart(day, #transactiondate)-1),#transactiondate))
else dateadd(month,month(#transactiondate)-month(#StartEffectiveDate)+1,#Starteffectivedate)
end
select #NextMonthEffectivedate
Check last day of a month.
SELECT day(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0)))
Add 1 month in date.
SELECT DATEADD(month,13,getdate());
Check first day of the month
SELECT CAST(CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '/' +
CAST(MONTH(GETDATE()) AS VARCHAR(2)) + '/01' AS DATETIME)
We can create a scalar function for this.
CREATE FUNCTION dbo.DaysInMonth(#date DATETIME) RETURNS int
AS
BEGIN
RETURN (SELECT DAY(DATEADD(m, 1, #date - DAY(#date))))
END
This SQL statement gives last date of a month, you just need to replace GETDATE() with your desired datetime variable or your column name.
DECLARE #lastDateOfMonth AS INT= DATEPART(DD, DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, GETDATE()) + 1, 0)));
SQL does not directly support this. You would need to create a function that checks for the correct days. See an example
Here

Playing with Date Time in SQL Server

I am building an SSRS report.
In the report, the week always runs from Monday - Sunday.
And I want to find out the START and END dates of prior two weeks.
For example,
If current date = Jan 1, 2011
-- current week = Dec 27, 2010 to Jan 2, 2011
-- previous week = Dec 20, 2010 to Dec 26, 2010
I have tried the following, but seems like it fails when when current day = Sunday
DECLARE #DT DATETIME
DECLARE #Offset INT
DECLARE #CM DATETIME
DECLARE #PM DATETIME
DECLARE #PS DATETIME
--SET #DT = GETDATE()
SET #DT = '11/14/2010' -- Monday
SET #Offset = (DATEPART(WEEKDAY, #DT) - 2) * -1
SET #CM = DATEADD(DAY, #Offset, #DT)
SET #PM = DATEADD(DAY, -7, #CM)
SET #PS = DATEADD(DAY, -1, #CM)
SELECT #Offset AS Offset, #DT AS Date, #CM AS Monday, #PM AS [Previous Monday], #PS AS [Previous Sunday], DATEPART(WK, #PM) AS Week
How can I fix it?
Add a [Calendar] table to your DB with all the dates you need precalculated. You can then include fields with the day name, number, holiday etc. and simply look up the values you need.
It's much simpler than playing with DATEADD
A useful article on calendar tables: Why should I consider using an auxiliary calendar table?
Not sure if this is the most elegant solution but you could do a case statement with your #offset like this:
SET #offset = CASE WHEN DATEPART(weekday, #today) >= 2
THEN -(DATEPART(weekday, #today) - 2)
ELSE -(DATEPART(weekday, #today) + 5)
END
I believe it works for all cases.