Transforming nvarchar day duration setting into datetime - sql

I have a SQL Server function which converts a nvarchar day duration setting into a datetime value.
The day duration format is >days<.>hours<:>minutes<, for instance 1.2:00 for one day and two hours.
The format of the day duration setting can not be changed, and we can be sure that all data is correctly formatted and present.
Giving the function a start time and the day duration setting it should return the end time.
For instance: 2010-01-02 13:30 ==> 2010-01-03 2:00
I'm using a combination of charindex, substring and convert methods to calculate the value,
which is kind of slow and akward. Is there any other way to directly convert this day duration setting into a datetime value?

Not from what I can see. I would end up with a similar bit of SQL like you, using charindex etc. Unfortunately it's down to the format the day duration is stored in. I know you can't change it, but if it was in a different format then it would be a lot easier - the way I'd usually do this for example, is to rationalise the duration down to a base unit like minutes.
Instead of storing 1.2:00 for 1 day and 2 hours, it would be (1 * 24 * 60) + (2 * 60) = 1560. This could then be used in a straightforward DATEADD on the original date (date part only).
With the format you have, all approaches I can think of involve using CHARINDEX etc.

One alternative would be to build a string with the calculation. Then you can run the generated SQL with sp_executesql, specifying #enddate as an output parameter:
declare #startdate datetime
declare #duration varchar(10)
declare #enddate datetime
set #startdate = '2010-01-02 13:30'
set #duration = '0.12:30'
declare #sql nvarchar(max)
set #sql = 'set #enddate = dateadd(mi,24*60*' +
replace(replace(#duration,'.','+60*'),':','+') + ', #startdate)'
exec sp_executesql #sql,
N'#startdate datetime, #enddate datetime out',
#startdate, #enddate out
This creates a string containing set #enddate = dateadd(mi,24*60*0+60*12+30, #startdate) and then runs it.
I doubt this is faster than the regular charindex way:
declare #pos_dot int
declare #day int
declare #hour int
declare #minute int
select
#pos_dot = charindex('.',#duration),
#day = cast(left(#duration, #pos_dot-1) as int),
#hour = cast(left(right(#duration, 5), 2) as int),
#minute = cast(right(#duration, 2) as int),
#enddate = dateadd(mi, 24*60*#day + 60*#hour + #minute, #startdate)

Related

SQL date conversion HHMMSS.CCCNNNNNN to yyyy-mm-dd hh:mi:ss.mmm

I have data in this format : 114643.052303537 (HHMMSS.CCCNNNNNN).
I need to convert it to this format : 2018-04-25 12:40:59.573 (yyyy-mm-dd hh:mi:ss.mmm), strip of the date part ( i.e. 2018-04-25 ) and calculate the time difference between two formats.
Could you please help with this?
I need the time difference in hh:mi:ss.mmm format
The way to get this is to convert BOTH values to milliseconds (looking at only the time portion for the value that has a date); calculate the difference with simple subtraction, and then convert the result to hh:mi:ss.mmm with division and modulo operations.
declare #dt datetime = '2018-04-25 12:40:59.573'
declare #dunno varchar(16) = '114643.052303537'
Strip the date off the datetime and give it today's date
getdate() + right(convert(varchar,#dt,113),12)
Convert the varchar to time and give it today's date
getdate() + left(stuff(stuff(#dunno,3,0,':'),6,0,':'),8)
Find the milliseconds between them
datediff(millisecond,getdate() + left(stuff(stuff(#dunno,3,0,':'),6,0,':'),8),getdate() + right(convert(varchar,#dt,113),12))
Put it all together in your format
select
convert(char(13),
dateadd(millisecond,
datediff(millisecond,getdate() + left(stuff(stuff(#dunno,3,0,':'),6,0,':'),8),getdate() + right(convert(varchar,#dt,113),12)),
'01/01/00'),
14)
Depending on the speed of your server and other code, it'd be wise to use a variable for GETDATE() at the beginning to prevent millisecond, or even second differences during conversion.
declare #dt datetime = '2018-04-25 12:40:59.573'
declare #dunno varchar(16) = '114643.052303537'
declare #today datetime = getdate()
declare #dunno2 datetime
declare #dt2 datetime
set #dt2 = #today + right(convert(varchar,#dt,113),12)
set #dunno2 = #today + left(stuff(stuff(#dunno,3,0,':'),6,0,':'),8)
select
convert(char(13),
dateadd(millisecond,
datediff(millisecond,#dunno2,#dt2),
'01/01/00'),
14)

Direct access slower than using functions?

I was conducting some performance testing and have discovered something quite strange. I have set up a short script to time how long it takes to perform certain actions.
declare #date date
declare #someint int
declare #start datetime
declare #ended datetime
set #date = GETDATE()
DECLARE #count INT
SET #count = 0
set #start = GETDATE()
WHILE (#count < 1000)
BEGIN
--Insert test script here
END
set #ended = GETDATE()
select DATEDIFF( MILLISECOND, #start, #ended)
The table I was running tests againsts contains 3 columns, MDay, and CalDate. Every calendar date has a corresponding M(Manufacturing)Day. The table may look something like this:
MDay | CalDate
1 | 1970-01-01
2 | 1970-01-02
I wanted to test how efficient one of our functions was. This function simply takes in a date and returns the int MDay value. I used direct access, basically the same thing without the function, and tests resulted in this method take twice as long! Code I inserted into the loop is provided below. I used a random date in an attempt to eliminate caching (if exist).
Function
select #someint = Reference.GetMDay(DATEADD( D, convert(int, RAND() * 1000) , #date))
Definition for above
create Function [Reference].[GetMDay]
(#pCaLDate smalldatetime
)
Returns int
as
Begin
Declare #Mday int
Select #Mday = Mday
from Reference.MDay
where Caldate = #pCaLDate
Direct
select #someint = MDay from Reference.MDay where CalDate = DATEADD( D, convert(int, RAND() * 1000) , #date)
I even tried using a static #date for my direct code and the difference in times are negligible, so I know the convert call isn't holding it back.
What the heck is going on here?
Take a look at http://msdn.microsoft.com/en-us/library/ms178071%28v=sql.105%29.aspx is the execution plan the same on your sql server for both methods?

Converting JDE Julian date to Gregorian

I'm trying to convert JDE dates, and have amassed a large quantity of information and figured I'd try to do an SQL conversion function to simplify some tasks.
Here's the function I came up with, which I simply call "ToGregorian"
CREATE FUNCTION [dbo].[ToGregorian](#julian varchar(6))
RETURNS datetime AS BEGIN
DECLARE #datetime datetime
SET #datetime = CAST(19+CAST(SUBSTRING(#julian, 1, 1) as int) as varchar(4))+SUBSTRING(#julian, 2,2)+'-01-01'
SET #datetime = DATEADD(day, CAST(SUBSTRING(#julian, 4,3) as int)-1, #datetime)
RETURN #datetime
END
Takes a "julian" string.
Takes the first letter and adds it to century, starting from 19th.
Adds decade and years from the next 2 characters.
Finally adds the days, which are the final 3 characters, and subtracts 1 as it already had 1 day in the first setup. (eg. 2011-01-01)
Result ex: 111186 => 2011-07-05 00:00:00.000
In my opinion this is a bit clumsy and overkill, and I'm hoping there is a better way of doing this. Perhaps I'm doing too many conversions or maybe I should use a different method alltogether?
Any advice how to improve the function?
Perhaps a different, better, method?
Wouldn't mind if it could be more readable as well...
I've also got an inline version, where if for instance, I only have read privileges and can't use functions, which also looks messy, is it possible to make it more readable, or better?
CAST(REPLACE(Convert(VARCHAR, DATEADD(d,CAST(SUBSTRING(CAST([column] AS VARCHAR), 4,3) AS INT)-1, CAST(CAST(19+CAST(SUBSTRING(CAST([column] AS VARCHAR), 1,1) AS INT) AS VARCHAR)+SUBSTRING(CAST([column] AS VARCHAR), 2,2) + '-01-01' AS DATETIME)), 111), '/', '-') AS DATETIME)
The accepted answer is incorrect. It will fail to give the correct answer for 116060 which should be 29th February 2016. Instead it returns 1st March 2016.
JDE seems to store dates as integers, so rather than converting from strings I always go direct from the integer:
DATEADD(DAY, #Julian % 1000, DATEADD(YEAR, #Julian / 1000, '31-dec-1899'))
To go from a varchar(6) I use:
DATEADD(DAY, CAST(RIGHT(#Julian,3) AS int), DATEADD(YEAR, CAST(LEFT(#Julian,LEN(#Julian)-3) AS int), '31-dec-1899'))
I think it is more efficient to use native datetime math than all this switching back and forth to various string, date, and numeric formats.
DECLARE #julian VARCHAR(6) = '111186';
SELECT DATEADD(DAY, SUBSTRING(#julian,4,3)-1,
DATEADD(YEAR, 100 * LEFT(#julian,1)
+ 10 * SUBSTRING(#julian,2,1)
+ SUBSTRING(#julian,3,1),0));
Result:
===================
2011-07-05 00:00:00
Assuming this data doesn't change often, it may be much more efficient to actually store the date as a computed column (which is why I chose the base date of 0 instead of some string representation, which would cause determinism issues preventing the column from being persisted and potentially indexed). Even if you don't index the column, it still hides the ugly calculation away from you, being persisted you only pay that at write time, as it doesn't cause you to perform expensive functional operations at query time whenever that column is referenced...
Corrected for leap year
USE [master]
GO
/****** Object: UserDefinedFunction [dbo].[ToGregorian] Script Date: 08/18/2015 14:33:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ToGregorian](#julian varchar(6),#time varchar(6))
RETURNS datetime
AS
BEGIN
DECLARE #datetime datetime,#hour int, #minute int, #second int
set #time = ltrim(rtrim(#time));
set #julian = ltrim(rtrim(#julian));
if(LEN(#julian) = 5)
set #julian = '0' + #julian
IF(LEN(#time) = 6)
BEGIN
SET #hour = Convert(int,LEFT(#time,2));
SET #minute = CONVERT(int,Substring(#time,3,2));
SET #second = CONVERT(int,Substring(#time,5,2));
END
else IF(LEN(#time) = 5)
BEGIN
SET #hour = Convert(int,LEFT(#time,1));
SET #minute = CONVERT(int,Substring(#time,2,2));
SET #second = CONVERT(int,Substring(#time,4,2));
END
else IF(LEN(#time) = 4)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,2));
SET #second = CONVERT(int,Substring(#time,3,2));
END
else IF(LEN(#time) = 3)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,1));
SET #second = CONVERT(int,Substring(#time,2,2));
END
else
BEGIN
SET #hour = 0;
SET #minute = 0;
SET #second = #time;
END
SET #datetime = DATEADD(YEAR,100*CONVERT(INT, LEFT(#julian,1))+10*CONVERT(INT, SUBSTRING(#julian, 2,1))+CONVERT(INT, SUBSTRING(#julian,3,1)),0);
SET #datetime = DATEADD(DAY, CONVERT(INT,SUBSTRING(#julian, 4, 3))-1,#datetime);
SET #datetime = DATEADD(hour,#hour,#datetime)
SET #datetime = DATEADD(minute,#minute,#datetime);
SET #datetime = DATEADD(second,#second,#datetime);
RETURN #datetime
END
DATE(CHAR(1900000 + GLDGJ)) where GLDGJ is the Julian date value
I don't think anyone has mentioned it, but JDE has a table just for this.
It's the F00365 data table. As far as I know, it's a translation table just for this issue.
To get a Gregorian date, you join the F00365 table using the ONDTEJ field (which is the Julian date),and you return the ONDATE value, which is Gregorian.
e.g.
SELECT
DateReq.ONDATE
FROM F00101 NamesData
INNER JOIN F00365 DateReq
ON DateReq.ONDTEJ = NamesData.ABUPMJ
No math required. No weird issues with leap years.
To convert Julian date to Gregorian:
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0))
To convert Gregorian to Julian date:
(YEAR(#date) - 1900) * 1000 + DATEPART(DAYOFYEAR, #date)
Try it:
DECLARE #julian VARCHAR(6);
SET #julian = N'122129';
SELECT #julian [JulianDate],
DATEADD(YEAR, #julian / 1000, 0) [Year],
#julian % 1000 [DayOfYear],
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0)) [Date];
DECLARE #george DATETIME;
SET #george = '2022-5-9';
SELECT #george [Date],
YEAR(#george) [Year],
DATEPART(DAYOFYEAR, #george) [DayOfYear],
(YEAR(#george) - 1900) * 1000 + DATEPART(DAYOFYEAR, #george) [JulianDate];
Here a formula that can be used I have looking all over different site and I had even though I had to twick a bit has work well for me and you do not need to create any special function stuff:
DATEADD(DAY,CONVERT(INT,SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),5,3))-1,DATEFROMPARTS(SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),1,4),01,01))

UNIX_TIMESTAMP in SQL Server

I need to create a function in SQL Server 2008 that will mimic mysql's UNIX_TIMESTAMP().
If you're not bothered about dates before 1970, or millisecond precision, just do:
-- SQL Server
SELECT DATEDIFF(s, '1970-01-01 00:00:00', DateField)
Almost as simple as MySQL's built-in function:
-- MySQL
SELECT UNIX_TIMESTAMP(DateField);
Other languages (Oracle, PostgreSQL, etc): How to get the current epoch time in ...
If you need millisecond precision (SQL Server 2016/13.x and later):
SELECT DATEDIFF_BIG(ms, '1970-01-01 00:00:00', DateField)
Try this post:
https://web.archive.org/web/20141216081938/http://skinn3r.wordpress.com/2009/01/26/t-sql-datetime-to-unix-timestamp/
CREATE FUNCTION UNIX_TIMESTAMP (
#ctimestamp datetime
)
RETURNS integer
AS
BEGIN
/* Function body */
declare #return integer
SELECT #return = DATEDIFF(SECOND,{d '1970-01-01'}, #ctimestamp)
return #return
END
or this post:
http://mysql.databases.aspfaq.com/how-do-i-convert-a-sql-server-datetime-value-to-a-unix-timestamp.html
code is as follows:
CREATE FUNCTION dbo.DTtoUnixTS
(
#dt DATETIME
)
RETURNS BIGINT
AS
BEGIN
DECLARE #diff BIGINT
IF #dt >= '20380119'
BEGIN
SET #diff = CONVERT(BIGINT, DATEDIFF(S, '19700101', '20380119'))
+ CONVERT(BIGINT, DATEDIFF(S, '20380119', #dt))
END
ELSE
SET #diff = DATEDIFF(S, '19700101', #dt)
RETURN #diff
END
Sample usage:
SELECT dbo.DTtoUnixTS(GETDATE())
-- or
SELECT UnixTimestamp = dbo.DTtoUnixTS(someColumn)
FROM someTable
Sql Server 2016 and later have a DATEDIFF_BIG function that can be used to get the milliseconds.
SELECT DATEDIFF_BIG(millisecond, '1970-01-01 00:00:00', GETUTCDATE())
Create a function
CREATE FUNCTION UNIX_TIMESTAMP()
RETURNS BIGINT
AS
BEGIN
RETURN DATEDIFF_BIG(millisecond, '1970-01-01 00:00:00', GETUTCDATE())
END
And execute it
SELECT dbo.UNIX_TIMESTAMP()
I often need a unix timestamp with millisecond precision. The following will give you the current unixtime as FLOAT; wrap per answers above to get a function or convert arbitrary strings.
The DATETIME datatype on SQL Server is only good to 3 msec, so I have different examples for SQL Server 2005 and 2008+. Sadly there is no DATEDIFF2 function, so various tricks are required to avoid DATEDIFF integer overflow even with 2008+. (I can't believe they introduced a whole new DATETIME2 datatype without fixing this.)
For regular old DATETIME, I just use a sleazy cast to float, which returns (floating point) number of days since 1900.
Now I know at this point, you are thinking WHAT ABOUT LEAP SECONDS?!?! Neither Windows time nor unixtime really believe in leap seconds: a day is always 1.00000 days long to SQL Server, and 86400 seconds long to unixtime. This wikipedia article discusses how unixtime behaves during leap seconds; Windows I believe just views leap seconds like any other clock error. So while there is no systematic drift between the two systems when a leap second occurs, they will not agree at the sub-second level during and immediately following a leap second.
-- the right way, for sql server 2008 and greater
declare #unixepoch2 datetime2;
declare #now2 Datetime2;
declare #days int;
declare #millisec int;
declare #today datetime2;
set #unixepoch2 = '1970-01-01 00:00:00.0000';
set #now2 = SYSUTCDATETIME();
set #days = DATEDIFF(DAY,#unixepoch2,#now2);
set #today = DATEADD(DAY,#days,#unixepoch2);
set #millisec = DATEDIFF(MILLISECOND,#today,#now2);
select (CAST (#days as float) * 86400) + (CAST(#millisec as float ) / 1000)
as UnixTimeFloatSQL2008
-- Note datetimes are only accurate to 3 msec, so this is less precise
-- than above, but works on any edition of SQL Server.
declare #sqlepoch datetime;
declare #unixepoch datetime;
declare #offset float;
set #sqlepoch = '1900-01-01 00:00:00';
set #unixepoch = '1970-01-01 00:00:00';
set #offset = cast (#sqlepoch as float) - cast (#unixepoch as float);
select ( cast (GetUTCDate() as float) + #offset) * 86400
as UnixTimeFloatSQL2005;
-- Future developers may hate you, but you can put the offset in
-- as a const because it isn't going to change.
declare #sql_to_unix_epoch_in_days float;
set #sql_to_unix_epoch_in_days = 25567.0;
select ( cast (GetUTCDate() as float) - #sql_to_unix_epoch_in_days) * 86400.0
as UnixTimeFloatSQL2005MagicNumber;
FLOATs actually default to 8-byte doubles on SQL Server, and therefore superior to 32-bit INT for many use cases. (For example, they won't roll over in 2038.)
For timestamp with milliseconds result I found this solution from here https://gist.github.com/rsim/d11652a8336137832df9:
SELECT (cast(DATEDIFF(s, '1970-01-01', GETUTCDATE()) as bigint)*1000+datepart(ms,getutcdate()))
Answer from #Rafe didn't work for me correctly (MSSQL 20212) - I got 9 seconds of difference.
Necromancing.
The ODBC-way:
DECLARE #unix_timestamp varchar(20)
-- SET #unix_timestamp = CAST({fn timestampdiff(SQL_TSI_SECOND,{d '1970-01-01'}, CURRENT_TIMESTAMP)} AS varchar(20))
IF CURRENT_TIMESTAMP >= '20380119'
BEGIN
SET #unix_timestamp = CAST
(
CAST
(
{fn timestampdiff(SQL_TSI_SECOND,{d '1970-01-01'}, {d '2038-01-19'})}
AS bigint
)
+
CAST
(
{fn timestampdiff(SQL_TSI_SECOND,{d '2038-01-19'}, CURRENT_TIMESTAMP)}
AS bigint
)
AS varchar(20)
)
END
ELSE
SET #unix_timestamp = CAST({fn timestampdiff(SQL_TSI_SECOND,{d '1970-01-01'}, CURRENT_TIMESTAMP)} AS varchar(20))
PRINT #unix_timestamp
Here's a single-line solution without declaring any function or variable:
SELECT CAST(CAST(GETUTCDATE()-'1970-01-01' AS decimal(38,10))*86400000.5 as bigint)
If you have to deal with previous versions of SQL Server (<2016) and you only care for positive timestamps, I post here the solution I found for very distant dates (so you can get rid of the IF from #rkosegi's answer.
What I did was first calculating the difference in days and then adding the difference in seconds left.
CREATE FUNCTION [dbo].[UNIX_TIMESTAMP]
(
#inputDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
DECLARE #differenceInDays BIGINT, #result BIGINT;
SET #differenceInDays = DATEDIFF(DAY, '19700101', #inputDate)
IF #differenceInDays >= 0
SET #result = (#differenceInDays * 86400) + DATEDIFF(SECOND, DATEADD(DAY, 0, DATEDIFF(DAY, 0, #inputDate)), #inputDate)
ELSE
SET #result = 0
RETURN #result
END
When called to Scalar-valued Functions can use following syntax
Function Script :
USE [Database]
GO
/****** Object: UserDefinedFunction [dbo].[UNIX_TIMESTAMP] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[UNIX_TIMESTAMP] (
#ctimestamp datetime
)
RETURNS integer
AS
BEGIN
/* Function body */
declare #return integer
SELECT #return = DATEDIFF(SECOND,{d '1970-01-01'}, #ctimestamp)
return #return
END
GO
Call Function :
SELECT dbo.UNIX_TIMESTAMP(GETDATE());

Optimal way to convert to date

I have legacy system where all date fields are maintained in YMD format. Example:
20101123
this is date: 11/23/2010
I'm looking for most optimal way to convert from number to date field.
Here is what I came up with:
declare #ymd int
set #ymd = 20101122
select #ymd, convert(datetime, cast(#ymd as varchar(100)), 112)
This is pretty good solution but I'm wandering if someone has better way doing it
try this:
CONVERT(DATETIME, CONVERT(NVARCHAR, YYYYMMDD))
For example:
SELECT CONVERT(DATETIME, CONVERT(NVARCHAR, 20100401))
Results in:
2010-04-01 00:00:00.000
What you have is a pretty good soltuion.
Why are you looking for a better way?
I use exactly that, it has been working fine for me
As it is stored as an integer then you could potential extract the year, month and day by dividing by 100, 1000.
e.g.
DECLARE #Date INT
SET #Date = 20100401
DECLARE #Year INT
DECLARE #Month INT
DECLARE #Day INT
SET #Year = #Date / 10000
SET #Month = (#Date - (#Year * 10000)) / 100
SET #Day = #Date - (#Year * 10000) - (#Month * 100)
SELECT #Date, DATEADD(MONTH,((#Year-1900)*12)+#Month-1,#Day-1)
However, I have no idea if that is faster than the string comparison you already have. I think your solution is far cleaner and easier to read and would stick with that.