Considering all the answers and comments I have finally reached a solution for this problem.
I have implemented a function to handle basic add/subtract for DATETIME2 datatype, and then I used it in a function to get intervals elapsed between two dates.
All calculations are at maximum precision of DATETIME2 and BIGINT and it should handle all cases.
this is the function to implement basic DATETIME2 arithmetic:
DROP FUNCTION FN_DATE2_MATH;
GO
CREATE FUNCTION FN_DATE2_MATH(
@D1 AS DATETIME2(7),
@D2 AS DATETIME2(7),
@OP AS INT=1, -- 1 = SUM, -1 = SUBTRACT
@OVERFLOW AS INT = NULL -- NULL = NULL VALUE, ELSE OVERFLOW ERROR
)
RETURNS DATETIME2(7)
AS
BEGIN
IF (@OP = -1) AND (@D1>@D2) BEGIN
DECLARE @DT DATETIME2(7) = @D1
SET @D1 = @D2
SET @D2 = @DT
END
DECLARE @B1 VARBINARY(8) = CONVERT(VARBINARY(8), REVERSE(SUBSTRING(CONVERT(VARBINARY(9), @D1),2,8)))
DECLARE @DD1 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @B1),1,3)
DECLARE @NS1 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @B1),4,5)
DECLARE @B2 VARBINARY(8) = CONVERT(VARBINARY(8), REVERSE(SUBSTRING(CONVERT(VARBINARY(9), @D2),2,8)))
DECLARE @DD2 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @B2),1,3)
DECLARE @NS2 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @B2),4,5)
DECLARE @DDR AS BIGINT
DECLARE @NSR AS BIGINT
IF @OP = 1 BEGIN
SET @NSR = CONVERT(BIGINT, @NS2) + CONVERT(BIGINT, @NS1)
IF @NSR>=864000000000 BEGIN
SET @NSR = @NSR - CONVERT(BIGINT, 864000000000)
SET @DD1 = CONVERT(VARBINARY(8), CONVERT(BIGINT, @DD1)-1)
END
SET @DDR = CONVERT(BIGINT, @DD2) + CONVERT(BIGINT, @DD1)
END ELSE
IF @OP = -1 BEGIN
SET @NSR = CONVERT(BIGINT, @NS2) - CONVERT(BIGINT, @NS1)
IF @NSR<0 BEGIN
SET @NSR = @NSR + CONVERT(BIGINT, 864000000000)
SET @DD1 = CONVERT(VARBINARY(8), CONVERT(BIGINT, @DD1)+1)
END
SET @DDR = CONVERT(BIGINT, @DD2) - CONVERT(BIGINT, @DD1)
END
-- CHECK OVERFLOW
IF @DDR NOT BETWEEN 0 AND 3652058 BEGIN
IF @OVERFLOW IS NULL
RETURN NULL
ELSE
RETURN DATEADD(DD, -1, CONVERT(DATETIME2(7), 0x070000000000000000)) -- GENERATE OVERFLOW
END
DECLARE @BR VARBINARY(8) = CONVERT(VARBINARY(3), @DDR)+CONVERT(VARBINARY(5), @NSR)
SET @BR = CONVERT(VARBINARY(8), REVERSE(@BR))
RETURN CONVERT(DATETIME2(7), 0x07+@BR)
END
GO
and this is the function to get periods elapsed:
DROP FUNCTION FN_DATE_DIFF2;
GO
CREATE FUNCTION FN_DATE_DIFF2(
@INTERVALTYPE AS VARCHAR(11),
@START AS DATETIME2(7),
@END AS DATETIME2(7)
)
RETURNS BIGINT
AS
BEGIN
DECLARE @DATEPART INT = CASE
WHEN @INTERVALTYPE IN ('0','nanosecond','ns') THEN 0
WHEN @INTERVALTYPE IN ('1','microsecond','mcs') THEN 1
WHEN @INTERVALTYPE IN ('2','millisecond','ms') THEN 2
WHEN @INTERVALTYPE IN ('3','second','ss','s') THEN 3
WHEN @INTERVALTYPE IN ('4','minute','mi','n') THEN 4
WHEN @INTERVALTYPE IN ('5','hour','hh') THEN 5
WHEN @INTERVALTYPE IN ('6','day','dd','d') THEN 6
WHEN @INTERVALTYPE IN ('7','week','wk','ww') THEN 7
WHEN @INTERVALTYPE IN ('8','month','mm','m') THEN 8
WHEN @INTERVALTYPE IN ('9','quarter','qq','q') THEN 9
WHEN @INTERVALTYPE IN ('10','year','yy','yyyy') THEN 10
ELSE
6 -- DEFAULT TO DAYS
END
DECLARE @BN0 VARBINARY(8) = 0x0000000000000000 -- 0001-01-01 00:00:00.0000000
DECLARE @DT0 AS DATETIME2(7) = CONVERT(DATETIME2(7), 0x07+@BN0) -- datetime2(7) = 0
--DECLARE @BNX VARBINARY(8) = 0xFFBF692AC9DAB937 -- 9999-12-31 23:59:59.9999999
--DECLARE @DTX AS DATETIME2(7) = CONVERT(DATETIME2(7), 0x07+@BNX) -- datetime2(7) = 0
DECLARE @DT1 AS DATETIME2(7)
DECLARE @DT2 AS DATETIME2(7)
DECLARE @DP AS DATETIME2(7)
DECLARE @VB1 VARBINARY(8) = CONVERT(VARBINARY(8), REVERSE(SUBSTRING(CONVERT(VARBINARY(9), @START),2,8)))
DECLARE @DD1 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @VB1),1,3) -- DAYS FROM 0 TO START
DECLARE @NS1 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @VB1),4,5) -- NS FROM 0 TO START
DECLARE @VB2 VARBINARY(8) = CONVERT(VARBINARY(8), REVERSE(SUBSTRING(CONVERT(VARBINARY(9), @END),2,8)))
DECLARE @DD2 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @VB2),1,3) -- DAYS FROM 0 TO END
DECLARE @NS2 VARBINARY(8) = SUBSTRING(CONVERT(VARBINARY(8), @VB2),4,5) -- NS FROM 0 TO END
DECLARE @NSR AS BIGINT = CONVERT(BIGINT, @NS2) - CONVERT(BIGINT, @NS1) -- NS RESULT NOT BIASED
IF @NSR<0 BEGIN
SET @NSR = @NSR + CONVERT(BIGINT, 864000000000) -- NS RESULT
SET @DD1 = CONVERT(VARBINARY(8), CONVERT(BIGINT, @DD1)+1) -- ADD CARRY
END
DECLARE @DDR AS BIGINT = CONVERT(BIGINT, @DD2) - CONVERT(BIGINT, @DD1) -- DAYS RESULT
DECLARE @RES BIGINT
SET @RES = CASE @DATEPART
WHEN 0 THEN @DDR*CONVERT(BIGINT, 864000000000)+ @NSR -- NS
WHEN 1 THEN @DDR*CONVERT(BIGINT, 86400000000) + @NSR/CONVERT(BIGINT, 10) -- MCS
WHEN 2 THEN @DDR*CONVERT(BIGINT, 86400000) + @NSR/CONVERT(BIGINT, 10000) -- MS
WHEN 3 THEN @DDR*CONVERT(BIGINT, 86400) + @NSR/CONVERT(BIGINT, 10000000) -- SS
WHEN 4 THEN @DDR*CONVERT(BIGINT, 1440) + @NSR/CONVERT(BIGINT, 600000000) -- MI
WHEN 5 THEN @DDR*CONVERT(BIGINT, 24) + @NSR/CONVERT(BIGINT, 36000000000) -- HH
WHEN 6 THEN @DDR -- DD
WHEN 7 THEN @DDR / 7 -- WK (BOTH INT, RES = INT)
END
IF @DATEPART IN (8,9,10) BEGIN
SET @DT1 = CASE @DATEPART
WHEN 8 THEN DATEADD(MM, DATEDIFF(MM, @DT0, @START), @DT0)
WHEN 9 THEN DATEADD(QQ, DATEDIFF(QQ, @DT0, @START), @DT0)
WHEN 10 THEN DATEADD(YY, DATEDIFF(YY, @DT0, @START), @DT0)
END
SET @DP = DBO.FN_DATE2_MATH(@START, @END, -1, 0) -- ELAPSED TIME (DIFF)
SET @DT2 = DBO.FN_DATE2_MATH(@DT1, @DP, 1, 0) -- SHIFT DATE (ADD)
SET @RES = CASE @DATEPART
WHEN 8 THEN DATEDIFF(MM, @DT1, @DT2)
WHEN 9 THEN DATEDIFF(QQ, @DT1, @DT2)
WHEN 10 THEN DATEDIFF(YY, @DT1, @DT2)
END
END
RETURN @RES
END
GO
You can call it this way:
DECLARE @D1 DATETIME2(7)
DECLARE @D2 DATETIME2(7)
DECLARE @DP VARCHAR(20)
SET @D1 = '31/12/2016'
SET @D2 = '01/01/2017'
SET @DP = 'YY'
SELECT @D1 DATE_START, @D2 DATE_END, @DP DATE_PART, DBO.FN_DATE_DIFF2(@DP, @D1, @D2) INTERVALS
SET @D1 = '01:31'
SET @D2 = '03:20'
SET @DP = 'HH'
SELECT @D1 DATE_START, @D2 DATE_END, @DP DATE_PART, DBO.FN_DATE_DIFF2(@DP, @D1, @D2) INTERVALS
SET @D1 = '01/01/0001'
SET @D2 = '31/12/9999 23:59:59.9999999'
SET @DP = 'NS'
SELECT @D1 DATE_START, @D2 DATE_END, @DP DATE_PART, DBO.FN_DATE_DIFF2(@DP, @D1, @D2) INTERVALS
and you will get:
DATE_START DATE_END DATE_PART INTERVALS
2016-12-31 00:00:00.0000000 2017-01-01 00:00:00.0000000 YY 0
1900-01-01 01:31:00.0000000 1900-01-01 03:20:00.0000000 HH 1
0001-01-01 00:00:00.0000000 9999-12-31 23:59:59.9999999 NS 3155378975999999999
this should work
DATEDIFF(???, 0, RightDateTime - LeftDateTime)... Such that your example would beDATEDIFF(Hour, 0, '03:20' - '01:31')? (May need explicit CAST to DATETIME when using string literals though)DATEDIFFwith aCASEandDATETIME2datatype is the best option, both for precision and ease of use. please see my answer.-operator you can't use it with DATETIME2 values, so if you did need ns precision (or earlier dates) you'd have an easier time expanding on the approach I suggested. And I've worked with enough databases that disagree about whatDateA - DateBmeans that I don't like to use that syntax, but that's just my point of view...