Dealing with out of range value on varchar date conversion - sql-server

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;

Related

Cannot construct data type date, some of the arguments have values which are not valid

I have a date in my database which should not have included the year, so I need to construct the correct date by checking the date. (If the date is yet to come this year, I want to set 2017, if the date has passed, I want to set 2018)
(t.myDate is a nullable datetime)
SELECT
NewDate = CASE
WHEN DATEPART(dayofyear, GETDATE()) < DATEPART(dayofyear, t.myDate)
THEN DATEFROMPARTS('2017', DATEPART(MONTH,t.myDate), DATEPART(DAY, t.myDate))
ELSE DATEFROMPARTS('2018', DATEPART(MONTH,t.myDate), DATEPART(DAY,t.myDate))
END
FROM
MyTable t
However, in some cases I get the following error message:
Cannot construct data type date, some of the arguments have values which are not valid.
I'm not sure what the problem is, but I know some rows are NULL. So I have tried to get around the problem by using the CASE expression to simply use today's date in the cases where the problem is:
SELECT
NewDate = CASE
WHEN t.myDate is null
THEN GETDATE()
WHEN DATEPART(MONTH,t.myDate) < 1
THEN GETDATE()
WHEN DATEPART(MONTH,t.myDate) > 12
THEN GETDATE()
WHEN DATEPART(DAY,t.myDate) < 1
THEN GETDATE()
WHEN DATEPART(DAY,t.myDate) > 31
THEN GETDATE()
WHEN ISDATE(t.myDate) = 0
THEN GETDATE()
WHEN DATEPART(dayofyear, GETDATE()) < DATEPART(dayofyear, t.myDate)
THEN DATEFROMPARTS('2017', DATEPART(MONTH,t.myDate), DATEPART(DAY, t.myDate))
ELSE DATEFROMPARTS('2018', DATEPART(MONTH,t.myDate), DATEPART(DAY,t.myDate))
END
FROM
MyTable t
But I still get the same error. Either I'm testing for the wrong data quality issues, or the CASE expression is not working as I expect it to.
Also: It does not help to use a where clause for NULL rows:
WHERE t.myDate IS NOT NULL
How can I find the data quality issue, and how can I get around it?
(I'm using SQL Server 2012)
Apart from checking for NULL you may also have a problem with 29th of February. If you for example have date 2016-02-29 and you are trying to construct date 2017-02-29 you will get above error since this is not a valid date.
In general you could use cursor to loop over records and execute your logic inside TRY/CATCH. On exception you could print offending data and check what the problem is.

How to only keep the time portion of a datetime value (SQL Server)?

Here's what I use to get a HH:MM string:
CONVERT(varchar(5), time_requested, 108)
I'm thinking there may be a more elegant way, even maybe more efficient (see similar question on How to remove the time portion of a datetime value (SQL Server)?).
Requirements:
the final result has to be easily convertible to a string (in order to be able to concatenate some field time_created with a field such as date_created).
the following 2 cases must be covered: HH:MM and HH:MM:SS.
Declare #D datetime = GetDate() -- Data Type could be Time as well
Select CONVERT(varchar(8), #D, 108) -- for HH:MM:SS 11:27:26
Select CONVERT(varchar(5), #D, 108) -- for HH:MM 11:27
Failed to mention, if 2012+ you could also use Format()
Declare #D DateTime = '2016-10-22 13:30:25'
Select Format(#D,'HH:mm') -- 13:30
Select Format(#D,'hh:mm:ss tt') -- 01:30:25 PM
Your method is fine. It produces a string representation of the value.
You can also convert to a time data type:
select cast(time_requested as time)
But note that this is "format-less" -- it contains hours, minutes, seconds, and fractions of seconds. So, your method is probably the better approach.

Convert One Datetime format to another in SQL Server

I have a datetime2 format in my Database 2015-06-22 06:23:42.790. I need to convert this into the following format 22/06/2015 06:23:42.790.
Is it possible?
Here is one way to do this:
DECLARE #date DATETIME2 = '2015-06-22 06:23:42.790';
SELECT cast(convert(VARCHAR(10), cast(LEFT(#date, 10) AS DATE), 3) AS VARCHAR(10))
+ ' ' + substring(cast(#date AS VARCHAR(50)), 12, 12)
Query breakdown:
First part: take first 10 characters from your datefield and then convert it to date style 3 (dd/mm/yyyy).
Second part: Add a space between date and time.
Third part: cast your datefield as varchar and extract the time which should always start in the 12th position of your string.
Join them all together and there you have it! Hope this helps!
Don't try to convert the database layout. Year Month Day is how SQL server shows the date because it ignores any international date formats.
I notice you want it as 22/06/2015 are you in the UK ? In the USA it would be 06/22/2015 Not such a problem because it's obvious that the 22 is the day. But if the date was 05/06/2015 how would sql or anyone know what day or month you're talking about.
So, get in to the habit of working in the ISO format year month day.
You don't mention what programming language. When reading data out of the database youd read it into a datetime variable. That will convert the date correctly into whatever locale your user is using. Different languages have different ways of getting the date into a datettime variable.
If it's only for display-use you can convert to varchar with FORMAT() function:
DECLARE #tab TABLE
(
datevalue DATETIME2
)
INSERT INTO #tab VALUES(GETDATE())
SELECT datevalue,
FORMAT(datevalue,'dd/MM/yyyy hh:mm:ss.fff') as newformat
FROM #tab

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