How do you calculate an expiration date in SQL Server? - sql-server

I need to calculate an expiration date. Should be pretty easy right? Well, my "lifespan" is in years, not days, and to make it more challenging, the years value can be fractional. Here is what I am starting with:
set #ExpirationDate = DATEADD(year, #LifeSpanYears, #BeginDate)
The problem is, DATEADD allows a decimal to be passed in, but if you read the documentation, the decimal is truncated to an int.
How do you calculate an expiration date with a fractional year?

This is the answer I came up with:
create function Reporting.CalcExpirationDate ( #LifeSpanYears decimal(9,2), #BeginDate date)
returns date
begin
return DATEADD(day, (#LifeSpanYears * 365.25), #BeginDate)
end
Then use it like this:
select Reporting.CalcExpirationDate (13 , '2015-09-01') as CalculatedDate, '2028-09-01' as ExpectedDate
union all select Reporting.CalcExpirationDate (13.5 , '2015-09-01') as CalculatedDate, '2029-03-01' as ExpectedDate
Notes: As I was working on this I realized that it is impossible to determine the exact expiration date using a fractional year as a lifespan. How do you know when to add leap days or not? If you really want the exact answer, you need lifespan in days.

In practice, the "lifespan in years" is probably a lifespan in months. If so, you might consider:
return dateadd(month, round(#LIfeSpanYears * 12, 0), #BeginDate)
This handles the issue with leap years. However, it comes at the cost of only resolving to months.

Related

Dealing with out of range value on varchar date conversion

I'm attempting to convert dates input in our system as text in the format YYYYMMDD into dates. Unfortunately our system allows the use of the 31st of any month to signify that it's the last day of the month that's important, for some functions like interest accrual etc.
I have a date showing as 20160931 which obviously fails to convert via
CONVERT(DATETIME, CONVERT(CHAR(8), [FIELD]))
and throws the out-of-range value error.
How can I overcome this, so that I can convert it to the correct value, in this case 30/09/2016?
Adapting #Shnugo's technic, I feel it's better to leave SQL to decide the end of month. Hence:
SELECT eomonth(CAST(y+m+'01' AS DATE))
FROM (VALUES(LEFT(#YourDate,4)
,SUBSTRING(#YourDate,5,2)
,SUBSTRING(#YourDate,7,2))) AS Parts(y,m,d)
Will give '2016-02-29' instead of '2016-02-28' with a constant '28' for Feb.
You might try something like this:
DECLARE #YourDate VARCHAR(100)='20160231';
SELECT CAST(y+m+dNew AS DATE)
FROM (VALUES(LEFT(#YourDate,4)
,SUBSTRING(#YourDate,5,2)
,SUBSTRING(#YourDate,7,2))) AS Parts(y,m,d)
CROSS APPLY
(
SELECT CASE WHEN CAST(m AS INT) IN(4,6,9,11) AND CAST(d AS INT)>30 THEN '30'
ELSE CASE WHEN CAST(m AS INT)=2 AND CAST(d AS INT)>28 THEN '28' ELSE d END
END AS dNew
) AS NewDay
And about a 29th of February you just have - additionally - to check if the year is to be divided by 4 :-)
UPDATE
Now it's on me to evolve #Irawan's technique :-)
Since SQL Server 2005 has not got the EOMONTH function, but it is surely better to let SQL Server do the calculation (29th of Feb implicitly solved!), I'd suggest this:
DECLARE #YourDate VARCHAR(100)='20160231';
SELECT DATEADD(SECOND,-1,DATEADD(MONTH,1,CAST(y+m+'01' AS DATETIME)))
FROM (VALUES(LEFT(#YourDate,4)
,SUBSTRING(#YourDate,5,2)
,SUBSTRING(#YourDate,7,2))) AS Parts(y,m,d)
This will - in any case - deliver the last second of the month...
If you want a plain DATE (without a time), you might just change the SECOND to DAY which will first jump to midnight of the first day of the next month and than go one day back...
UPDATE 2 Use existing dates if valid
Simple syntax...
DECLARE #YourDate VARCHAR(100)='20160229';
SELECT CASE WHEN ISDATE(#YourDate)=1 THEN #YourDate
ELSE DATEADD(SECOND,-1,DATEADD(MONTH,1,CAST(LEFT(#YourDate,4) + SUBSTRING(#YourDate,5,2) +'01' AS DATETIME)))
END AS CorrectDate;

SQL Server: datediff function resulted in an overflow when using MILLISECOND

I have the following query :
select CONVERT(varchar(12), DATEADD(MILLISECOND, DateDiff(MILLISECOND, '2014-08-04 10:37:28.713','2014-11-04 08:21:17.723'), 0), 114)
When I execute this, I get the error :
"The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart."
When I change the query to the following it works fine :
select CONVERT(varchar(12), DATEADD(SECOND, DateDiff(SECOND, '2014-08-04 10:37:28.713','2014-11-04 08:21:17.723'), 0), 114)
The problem is that I really need the MILLISECONDS as well.
A bit later response but may help.
In SQL 2016 MS introduced function DATEDIFF_BIG which will (according to type size) overflow in difference bigger than something like 290k years. But technet article have same time difference as basic DATEDIFF - https://msdn.microsoft.com/en-us/library/mt628058.aspx
See https://learn.microsoft.com/en-us/sql/t-sql/functions/datediff-transact-sql?view=sql-server-ver15#return-value
For millisecond, the maximum difference between startdate and enddate is 24 days, 20 hours, 31 minutes and 23.647 seconds.
If you need millisecond above that level, you'll need to write something custom.
In SQL Server 2016 there is a new function available: DATEDIFF_BIG
It solves exactly the overflow problem.
You don't need to refer to the miliseconds in your calculation.
This will do exactly the same as your script except the overflow:
SELECT CONVERT(varchar(12),
CAST('2014-11-04 08:21:17.723' as datetime) -
CAST('2014-08-04 10:37:28.713' as datetime)
, 114)
For me there was a big interval between two dates so i have used below code
declare #timetagInMillsecond bigint=CAST(CAST( cast(#timetag as
datetime) -'1970-01-01' AS decimal(38,10))*24*60*60*1000+0.5 as
bigint)
It works for me .
Use DATEDIFF_BIG to resolve the overflow issue
The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.
SELECT DATEDIFF_BIG(
millisecond,
SYSDATETIME(),
DATEADD(year, 1000, SYSDATETIME()) ) AS 'Milliseconds in 1000 years';
For SQL Server 2014, the following works around the 'int' limitation to obtain a "JavaScript Time Epoch". This assumes the start epoch is itself a date, which fits the local use-case leading to finding this question. The query requires adaptation to the specific question use-case which does not have this property.
declare #x as datetime = getdate()
-- epoch_delta_s_to_date * 1000 + day_delta_ms
select
cast(datediff(second, '1970-01-01', cast(#x as date)) as bigint) * 1000
+ cast(datediff(millisecond, cast(#x as date), #x) as bigint)
For the case of obtaining a "JavaScript Time Epoch" this is still subject to the Y2038 limitation of datediff(second, '1970-01-01', ..).

Calculating age in a T-SQL query

I know this has been asked but I found the following code that appears to be working correctly and just wanted to have all the pros here take a quick look at it.
I work in banking and need it to work 100% of the time and I've noticed that a lot of the queries can sometimes be incorrect. Also N.NameBirthdate is the column I am pulling from my database.
Thank you in advance.
SELECT
CASE
WHEN (MONTH(GETDATE()) * 100) + DAY(GETDATE()) >= (MONTH(N.NameBirthdate) * 100) + DAY(N.NameBirthdate)
THEN DATEDIFF(Year, N.NameBirthdate, GETDATE())
ELSE DATEDIFF(Year, N.NameBirthdate, GETDATE())-1
END AS 'Age',
Well, I'm not sure exactly if that will work 100% but are you open to other query ideas? I always use this:
SELECT FLOOR(DATEDIFF(d, N.NameBirthDate, GETDATE()) / 365.25) AS Age
if I need to calculate an age. The 365.25 is to allow for leap years.
But you may encounter slight inaccuracies if the person has not experienced a leap year and depending on the time of day the query is run. See this for more info
What you have should work. I might use a variation though just because I find it a bit more readable / obvious:
SELECT DATEDIFF(Year, N.NameBirthdate,getdate()) -
(case when month(N.NameBirthdate) > month(getdate()) or (month(N.NameBirthdate) = month(getdate()) and day(N.NameBirthdate) > day(getdate()) ) then 1 else 0 end)
To me, this makes sense because that's how you (or I do), determine someone's age. OK: what year were you born and what year is it now? What month are we in now? Is it your birth month? What day is it?
I personally don't like the leap year approach. First of all, it's not 365.25, but .24. To be precise it's 365.24219647. If you use .25 you will be wrong. Example: http://www.sqlfiddle.com/#!3/d41d8/8267
I know that this is old but I was looking around for a quick way to properly calculate someone's age and I couldn't find anything. These answers work fine but I did not want any case statement and I wanted it to be completely accurate so I developed this:
SELECT DATEPART(YY,GETDATE()-COALESCE(DOB,GETDATE()))-DATEPART(YY,0)
Ex.
DATEPART(YY,GETDATE()-COALESCE(DOB,GETDATE()))
DATEPART(YY,'07-30-2014' - '10-03-2000') = 1913...
1913 - DATEPART(YY,0)...
1913 - 1900 = 13
I like this method better because if the DOB is null then you will get 0 and no matter what the date system is, subtracting DATEPART(YY,0) will account for it. So instead of 1913 you would have 1917 - 1904 = 13 if somehow the date system was 1904(I know excel has this, does DBs??? IDK; IDC) Also, it's only 61 characters. If you DB allows for empty sets then you can use NULLIF like so:
SELECT DATEPART(YY,GETDATE()-COALESCE(NULLIF(DOB,''),GETDATE()))-DATEPART(YY,0)
SELECT DATEDIFF(yy, BirthDate, GETDATE()) - CASE
WHEN MONTH(BirthDate) > MONTH(GETDATE())
OR (MONTH(BirthDate) = MONTH(GETDATE())
AND DAY(BirthDate) > DAY(GETDATE()))
THEN 1
ELSE 0
END AS Age;

Getting today's midnight time as UTC

I have the following query which calculates today's midnight value (UTC) as a datetime:
SELECT CONVERT(DATE,GETDATE())+(GETDATE()-GETUTCDATE())
Result: 2011-11-03 19:00:00.000 (for GMT-5 on Nov. 4, 2011)
Not only that, but on occasion, it returns values like these:
2011-11-03 19:00:00.003
2011-11-03 19:00:00.007
2011-11-03 19:00:00.010
..., which are wrong!
There must be a better way to do this.
I already answered this with a solution using DATEADD and DATEDIFF with GETDATE() and GETUTCDATE(), similar to the example given in the original question, but since then I've discovered the datetimeoffset data type added in SQL Server 2008. This stores a datetime along with a timezone offset.
How you use this type will depend on whether you want to change the data type of your existing data. If you don't want to change anything, the following statement will return a datetime type with the local time of midnight:
SELECT CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset,
CONVERT(date, GETDATE())),
DATENAME(TzOffset, SYSDATETIMEOFFSET())))
You could also convert any UTC time into local time using:
SELECT CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset,
#myutctime,
DATENAME(TzOffset, SYSDATETIMEOFFSET())))
The datetimeoffset type is only available using SQL2008 and above. If you need to do this with 2005 and below, you can use a solution similar to the one in the original question, but altered to account for the fact that GETDATE() - GETUTCDATE() is not an atomic operation and will likely involve milliseconds of difference between when the two are executed.
SELECT DATEADD(minute,
DATEDIFF(minute, GETUTCDATE(), GETDATE()),
CONVERT(datetime, CONVERT(date, GETDATE())))
This will take the number minutes between GETDATE() and GETUTCDATE() and add them onto the local midnight time. Unfortunately, you have to convert back from date to datetime as DATEADD won't work with minutes if you give it a date. I'd suggest wrapping this into a user-defined function to make it look less verbose, e.g.
CREATE FUNCTION dbo.MidnightASUTC(#dt as datetime)
RETURNS datetime
AS
BEGIN
RETURN DATEADD(minute,
DATEDIFF(minute, GETUTCDATE(), GETDATE()),
CONVERT(datetime, CONVERT(date, #dt)))
END
SELECT dbo.MidnightAsUTC(GETDATE())
For a specific scenario like the one you've described ("today's midnight value (UTC) as a datetime"), a programmatic approach makes sense, but if you ever need to extend it to a different question (what was midnight UTC for this summer?), you may want to use a calendar table (to account for things like daylight savings time, etc).

Getting week number off a date in MS SQL Server 2005?

Is it possible to create an sql statement that selects the week number (NOT the day of week - or the day number in a week). I'm creating a view to select this extra information along with a couple of other fields and thus can not use a stored procedure. I'm aware that it's possible to create a UDF to do the trick, but if at all possible i'd rather only have to add a view to this database, than both a view and a function.
Any ideas? Also where i come from, the week starts monday and week 1 is the first week of the year with atleast 4 days.
Related:
How do I calculate the week number given a date?
Be aware that there are differences in what is regarded the correct week number, depending on the culture. Week numbers depend on a couple of assumptions that differ from country to country, see Wikipedia article on the matter. There is an ISO standard (ISO 8601) that applies to week numbers.
The SQL server integrated DATEPART() function does not necessarily do The Right Thing. SQL Server assumes day 1 of week 1 would be January 1, for many applications that's wrong.
Calculating week numbers correctly is non-trivial, and different implementations can be found on the web. For example, there's an UDF that calculates the ISO week numbers from 1930-2030, being one among many others. You'll have to check what works for you.
This one is from Books Online (though you probably want to use the one from Jonas Lincoln's answer, the BOL version seems to be incorrect):
CREATE FUNCTION ISOweek (#DATE DATETIME)
RETURNS INT
AS
BEGIN
DECLARE #ISOweek INT
SET #ISOweek = DATEPART(wk,#DATE)
+1
-DATEPART(wk,CAST(DATEPART(yy,#DATE) AS CHAR(4))+'0104')
-- Special cases: Jan 1-3 may belong to the previous year
IF (#ISOweek=0)
SET #ISOweek = dbo.ISOweek(CAST(DATEPART(yy,#DATE) - 1
AS CHAR(4))+'12'+ CAST(24+DATEPART(DAY,#DATE) AS CHAR(2)))+1
-- Special case: Dec 29-31 may belong to the next year
IF ((DATEPART(mm,#DATE)=12) AND
((DATEPART(dd,#DATE)-DATEPART(dw,#DATE))>= 28))
SET #ISOweek=1
RETURN(#ISOweek)
END
GO
You need the ISO week. From http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=60510, here's an implementation:
drop function dbo.F_ISO_WEEK_OF_YEAR
go
create function dbo.F_ISO_WEEK_OF_YEAR
(
#Date datetime
)
returns int
as
/*
Function F_ISO_WEEK_OF_YEAR returns the
ISO 8601 week of the year for the date passed.
*/
begin
declare #WeekOfYear int
select
-- Compute week of year as (days since start of year/7)+1
-- Division by 7 gives whole weeks since start of year.
-- Adding 1 starts week number at 1, instead of zero.
#WeekOfYear =
(datediff(dd,
-- Case finds start of year
case
when NextYrStart <= #date
then NextYrStart
when CurrYrStart <= #date
then CurrYrStart
else PriorYrStart
end,#date)/7)+1
from
(
select
-- First day of first week of prior year
PriorYrStart =
dateadd(dd,(datediff(dd,-53690,dateadd(yy,-1,aa.Jan4))/7)*7,-53690),
-- First day of first week of current year
CurrYrStart =
dateadd(dd,(datediff(dd,-53690,aa.Jan4)/7)*7,-53690),
-- First day of first week of next year
NextYrStart =
dateadd(dd,(datediff(dd,-53690,dateadd(yy,1,aa.Jan4))/7)*7,-53690)
from
(
select
--Find Jan 4 for the year of the input date
Jan4 =
dateadd(dd,3,dateadd(yy,datediff(yy,0,#date),0))
) aa
) a
return #WeekOfYear
end
go
Looks like the DATEPART mssql function should help you out with ...
DATEPART(wk, ‘Jan 1, xxxx’) = 1
Well I'll be.. turns out there is a way to set the first day of the week, DATEFIRST
SET DATEFIRST 1 -- for monday
Update: Now I understand better, what the OP wants.. which is custom-logic for this. I don't think MSSQL would have functions with such rich level of customization. But I may be wrong... I think you'll have to roll your own UDF here...sorry
FORGET THE OTHER ANSWERS
The question specifies "the week starts monday and week 1 is the first week of the year with atleast 4 days." This is ISO 8601 standard and what this answer provides. This function is used in production on our site.
This is all you need:
CREATE FUNCTION ISOweek (#DATE DATETIME)
RETURNS INT
AS
BEGIN
RETURN (datepart(DY, datediff(d, 0, #DATE) / 7 * 7 + 3)+6) / 7
END
GO
This will return you the week number of date entered in quotes
SELECT DATEPART( wk, 'enter the date over here' )
Looks like datepart will get you part of the way there, but you'll have to adjust to get your correct week number, based on the day of week of Jan 1 of the given year. I'm not familiar enough with T-SQL to do that, but it should be possible. Pity there isn't a mode argument as in MySQL
have you considered using the WEEK function?
This will get you the week of the year for the specified date that you pass in.
SELECT { fn WEEK(GETDATE()) } AS WeekNumber, { fn WEEK(CONVERT(DATETIME, '2008-01-01 00:00:00', 102)) } AS FirstWeekOfYear, { fn WEEK(CONVERT(DATETIME, '2008-12-31 00:00:00', 102)) } AS LastWeekOfYear
This outputs the following SQL2000 and SQL2005:
WeekNumber: 50
FirstWeekOfYear: 1
LastWeekOfYear: 53
I Hope this helps :)
Why yet again, people make mountains out of mole-hills, it astounds me?
So simple...
select DATEPART(wk, GETDATE())

Resources