• subscribe
January 25, 2006 12:00 AM

Correctly Calculating Datetime Differences

Transcend DATEDIFF()’s limitations
SQL Server Pro
InstantDoc ID #48764
Downloads
48764.zip

T -SQL programmers share a basic and surprisingly challenging problem: correctly calculating the difference between two datetime values. SQL Server doesn't provide a built-in solution for this task, so you have to create your own.

Although SQL Server's DATEDIFF() function lets you calculate the difference between two datetime values in terms of a specified unit (e.g., year, month, day), DATE DIFF() considers only the specified unit and those higher in the temporal hierarchy— not lower units. For example, if you specify a unit of day, SQL Server will consider only the year, month, and day and ignore lower units (hour, minute, second, millisecond). Notice the following query, which asks for the difference in years between two values that are just 3ms apart:

SELECT DATEDIFF(year, '20051231 23:59:59.997', 
'20060101 00:00:00.000')

SQL Server returns an incorrect value of 1 (instead of 0) for the number of years between these values because the function doesn't check units lower than year but rather simply subtracts 2005 from 2006.

Let's look at how to get the correct datetime difference, taking all units into consideration. In other words, given two timestamps—from_ts and to_ts—let's see how to return the difference in terms of years, months, days, hours, minutes, seconds, and milliseconds.

Test Your Solution First
Set up the datetime-differences problem by running the code in Listing 1 to create the TimeStamps table and populate it with sample data. Table 1 shows the Time-Stamps table's content. Each row contains an integer key (keycol) and a pair of timestamps (from_ts and to_ts). The task is to correctly calculate the differences between the timestamp pairs, considering all possible datetime units, and produce the desired result that Table 2 shows.

Before looking at my solution, take some time to try to solve the problem yourself. Your solution should handle cases in which from_ts is later than to_ts, in which case your code should generate a negative result. The sgn result column should identify whether the result is positive (1) or negative (-1). (Note that the outputs I show omit the from_ts and to_ts values to save space; you can correlate these values back to the TimeStamps table based on keycol.)

Step-by-Step Calculations
Now, let's walk through my solution, which Listing 2 shows. First, consider the innermost query at callout F in Listing 2, which generates the derived table D1, shown in Table 3. This query uses simple CASE expressions to achieve its purpose: to place the larger value of the timestamp pair (from_ts, to_ts) in the result column to_ts and to place the smaller value in from_ts. This operation allows for simpler calculations later, guaranteeing that from_ts is smaller than or equal to to_ts. The sgn result column will hold a value of 1 if from_ts is less than or equal to to_ts and -1 if from_ts is greater than to_ts.

Callout E shows the query that accepts D1 as its input, then generates derived table D2, which Table 4 shows. This query uses the DATEDIFF() function to calculate the date-unit differences (i.e., year, month, day). Remember that the unit difference that DATEDIFF() calculates might have an offset of 1 from the correct value because the function doesn't consider units lower than the unit specified in the temporal hierarchy; the next step will take care of that offset.

The query at callout D accepts D2 as its input and generates derived table D3, which Table 5 shows. This query uses a CASE expression for each date unit, adding the corresponding unit difference you received from the previous query to the from_ts value. If the result is greater than to_ts, the difference that DATEDIFF() calculated was greater than the correct value by 1, so the code subtracts 1 from the difference. This logic, which fixes the inaccuracy in DATEDIFF()'s calculation, is a key element of the solution.

Why not use the same logic to calculate the difference for the time units in the timestamp values? The reason is that when you get down to the time units, the difference between two timestamps might be higher than an integer can hold. In such cases, the calculation would overflow, so you need to handle the time units separately.

Calculating Time Elements
Now that you've calculated the correct differences for the date units, you need to return from each unit only the portion that the higher-level unit doesn't cover. For example, say you're given the timestamps 20030321 14:27:12.233 and 20060115 11:45:22.263. The calculations in the query at callout D in Listing 2 will yield 2 years, 33 months, and 1030 days, each correct independently. But to show the result in combined units, you need to return only the portion of months after subtracting the higher-level unit, which in this case is 9 months (33 months ? 2 years). Similarly, you want to return only the portion of days after subtracting the years and months, which is 24 days in our example (1030 days ? (2 years and 9 months)). Eventually, you'll return a difference of 2 years, 9 months, and 24 days (plus lower units). The next steps return the date elements and prepare the inputs you need to calculate the time elements.

The query at callout C accepts D3 as its input and generates the derived table D4, which Table 6 shows. This query simply shifts from_ts forward by each of the three date-unit differences (y, m, and d), generating the values y_ts, m_ts and d_ts, respectively. The solution will use each of these timestamps as an anchor to return only the relevant portion of a date unit.

The query at callout B accepts D4 as its input and generates the derived table D5, which Table 7, page 26, shows. This query returns only the relevant portion of each date unit, subtracting the difference between to_ts and the higher-level anchor from the independent date unit. The query also calculates the difference in seconds (s) between the day anchor and to_ts. The listing will use s in the next step to calculate all time elements except the milliseconds element.



ARTICLE TOOLS

Comments
  • Karen
    3 years ago
    Jan 27, 2009

    Callouts are used to call attention to one or more lines of code in a listing. In the print version of this article, the callouts will appear as highlighted code with a letter next to it. In the online version, callouts are indicated by lines such as BEGIN CALLOUT A and END CALLOUT A.

    Unfortunately, the wrong code had been placed in the 48764.zip file. This code contained callout lines that were not commented, so the code would fail. I've replaced that code with the correct code. The Datetime_Difference.sql file contains all the code you see in Listings 1, 2, and 3 but without callout lines. Plus, the listing titles are treated as comments. You can obtain Datetime_Difference.sql by clicking the 48764.zip link under the "Download the Code" heading at the top of the page.

    If you have any questions about the new code, please let me know. And thanks for reading SQL Server Magazine!

    Karen Bemowski, senior editor
    SQL Server Magazine, Windows IT Pro

  • Bhaskar
    3 years ago
    Jan 27, 2009

    Possibly I am missing something obvious but what exactly is the Callout element?I am unable to compile the listing code because 'callout' is not recognised?

  • Tim
    6 years ago
    Jan 27, 2006

    Itzik, Enjoyed your solution as always. I do believe there is a small bug concerning your treatment of seconds as revealed by the example of the difference of 3ms to midnight vs. midnight. Your solution shows 1 sec + 3 ms where correctly it should just be 3 ms. To keep the same structure and correct the bug, I added an extra derived table to use a case statement to subtract 1 from the seconds total when adding the number of seconds gives a time greater than the to_ts time.

    (
    SELECT keycol, from_ts, to_ts, sgn, y, m, d,
    s - CASE WHEN DATEADD(second, s, from_ts) > to_ts
    THEN 1 ELSE 0 END AS s
    FROM

    Thanks again.

You must log on before posting a comment.

Are you a new visitor? Register Here
  • SP1?
    I know there is a SP1 for SQL 2008 R2 available....and there is a "feature pack" as well... ...
  • SQL database mirroring
    I have SQL Server 2008 R2 Enterprise 64bit on Windows 2008 R2 Enterprise 64bit.  Each SQL Server has...
  • Dell Compellent Disk Drive
    Does anybody has experience with Dell Compellent Disk Drive? Basically, this system manages all disk...
  • Sql server performance tuning
    I need to find a tool that help me to optimize sql server,queries,improve the performance and solve ...