• Jeff Moden (1/16/2014)


    Eirikur Eiriksson (1/16/2014)


    Thank you for the spackle Jeff but it looks like there is a small crack in it as it makes the assumption that the end time is always greater than the start time. This is the reason why I prefer to use double cast, first to varbinary and then to bigint.

    SELECT

    StartDT

    ,EndDT

    ,Duration = STUFF(CONVERT(VARCHAR(20),EndDT-StartDT,114),1,2,DATEDIFF(hh,0,EndDT-StartDT))

    ,DurationI = STUFF(CONVERT(VARCHAR(20),StartDT-EndDT,114),1,2,DATEDIFF(hh,0,StartDT-EndDT))

    ,cast(cast(StartDT as varbinary(128)) as bigint) As StartTicks

    ,cast(cast(EndDT as varbinary(128)) as bigint) AS EndTicks

    ,cast(cast(StartDT as varbinary(128)) as bigint)-cast(cast(EndDT as varbinary(128)) as bigint) as XmY

    ,cast(cast(EndDT as varbinary(128)) as bigint)-cast(cast(StartDT as varbinary(128)) as bigint) as YmX

    FROM #JBMTest;

    You're correct and I could have added a simple statement, such as "Assuming two non-null dates where the StartDT is less than or equal to the EndDT,...", to make sure that no one would have to assume.

    Shifting gears to the code you posted...

    While I very much appreciate alternate methods for just about everything, I have to admit that the code you wrote for the StartTicks and EndTicks columns has me confused. Why are they labeled as "Ticks"? Because DATETIME is based on two integers where one integer represents the number of whole days and the other represents the number of 1/300ths of a second that have passed since midnight, I don't understand where the word "Ticks" comes into play. Without further explanation or esoteric knowledge, one might think that you were implying "clock ticks" and, without some additional calculation, they are not. Further, since the maximum hex value of the "Time" portion for a DATETIME datatype is 018B81FF (23:59:59.997) and falls drastically short of the FFFFFFFF value before the rollover to the next int (day), the VARBINARY/BIGINT converted value can't represent "ticks" of equal duration of any kind. A simple conversion of DATETIME to FLOAT would get you a lot closer to some form of "ticks" value.

    This is the reason why I prefer to use double cast, first to varbinary and then to bigint.

    I'm even more confused after that statement. Please explain how your code, as it is posted, ensures that the StartDT and EndDT values are in the proper order even if they were to produce the required human-readable output (and it IS required by most). From what I can see, the code doesn't resolve that problem. Unless I'm missing something, it simply presents two columns where an additional decision would still need to be made as to whether or not the two DATETIMEs were in the correct order or not.

    And, of course, although most would never approach the limit and as you have yourself recognized, your conversions do have a limit at 148 years whereas the code in the article does not.

    I must be missing something. Please explain.

    First let me apologize for the inaccuracy and incompleteness of my initial postings and for implying that a direct cast of the DATETIME to BIGINT would hold any sensible meaning.

    To finish what I started;

    DATETIME is a structure of two integers, signed integer for the number of days starting at -53690 for 1753-01-01 which are the number of days until 1900-01-01.

    For this reason, the subtraction of two equal datetime values returns 1900-01-01.

    It can hold 3012153 days or 2958463 days after 1900-01-01 which is 9999-12-31.

    And as you correctly said, the second part of the DATETIME structure holds the time from midnight in 1/300th parts of a second, that is 25920000 intervals per day. This makes 78,075,031,679,999 (1/300th) parts for the full day range of the DATETIME, well within the limits of the BIGINT, 2^63-1 (9,223,372,036,854,775,807). Since the storage size is the same for both data types, I prefer to store both date-time and duration/period as (1/300th) parts BIGINT if to be used for temporal calculations.

    The calculation is quite quick and straight forward; extract the day part, add 53690, then multiply by 25920000 and add the time part. To extract the day value one can either shift the bytes by division of 0xFFFFFFFF or using SUBSTRING, the latter tends to be faster. For the time part OR or XOR with an integer of 0 (zero).

    A sample test code snip using the #JBMTest;

    DECLARE @Bitbucket BIGINT;

    SET STATISTICS TIME ON;

    SELECT @Bitbucket = ABS(

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(EndDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(EndDT AS VARBINARY(8)) ^ 0)) -

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(StartDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(StartDT AS VARBINARY(8)) ^ 0))

    )

    FROM #JBMTest;

    SET STATISTICS TIME OFF;

    GO

    A MIN-MAX demonstration

    DECLARE @MinDT DateTime;

    DECLARE @MaxDT DateTime;

    SELECT

    @MinDT ='1753-01-01 00:00:00.000',

    @MaxDT ='9999-12-31 23:59:59.998';

    SELECT

    @MinDT AS MinDateTime

    ,@MaxDT AS MaxDateTime

    ,ABS(

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(@MaxDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(@MaxDT AS VARBINARY(8)) ^ 0)) -

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(@MinDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(@MinDT AS VARBINARY(8)) ^ 0))

    ) AS TP_300

    ,ABS(

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(@MaxDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(@MaxDT AS VARBINARY(8)) ^ 0)) -

    ((CAST((53690 +

    CAST(SUBSTRING(CAST(@MinDT AS VARBINARY(8)),1,4) AS int)) AS BIGINT) * 25920000)

    + (CAST(@MinDT AS VARBINARY(8)) ^ 0))

    ) / 25920000 AS NumberOfDays

    ;

    Again my apologies for the inaccuracy of my previous postings.