Calculating Date Differences in SQL Server: A Comprehensive Guide to DATEDIFF

In SQL Server, accurately calculating the difference between two dates is a common and crucial task. Whether you’re analyzing trends over time, calculating durations, or generating reports, understanding how to effectively determine date differences is essential. SQL Server provides the DATEDIFF function specifically for this purpose. This article delves into the intricacies of DATEDIFF, offering a comprehensive guide for anyone needing to master date difference calculations in SQL Server.

Understanding the DATEDIFF Function

The DATEDIFF function in SQL Server is designed to calculate the count of specified datepart boundaries crossed between a startdate and an enddate. It returns this count as a signed integer value, making it versatile for various date and time calculations.

For scenarios involving exceptionally large date differences that might exceed the range of the int data type, SQL Server offers the DATEDIFF_BIG function. However, for the vast majority of applications, DATEDIFF provides ample range and performance.

You can find DATEDIFF and other related date and time functionalities within the broader context of Date and time data types and functions in Transact-SQL.

Syntax of DATEDIFF

The syntax for the DATEDIFF function is straightforward:

DATEDIFF ( datepart , startdate , enddate )

Let’s break down each component:

datepart

The datepart argument is crucial as it defines the units in which DATEDIFF will calculate and report the difference. Think of it as specifying whether you want the difference in years, months, days, hours, seconds, or even smaller units like milliseconds. Common datepart units include month and second, but SQL Server supports a wide array of options.

It’s important to note that datepart must be specified directly and cannot be a variable or a quoted string like 'month'.

Here’s a table detailing the valid datepart values, including both the full name and accepted abbreviations. Using either the full name or its abbreviation will yield the same result.

datepart name datepart abbreviation
year yy, yyyy
quarter qq, q
month mm, m
dayofyear dy, y
day dd, d
week wk, ww
weekday dw, w
hour hh
minute mi, n
second ss, s
millisecond ms
microsecond mcs
nanosecond ns

startdate and enddate

Both startdate and enddate arguments represent the two dates you want to compare. They can be expressions that resolve to any of the following data types:

  • date
  • datetime
  • datetimeoffset
  • datetime2
  • smalldatetime
  • time

To avoid any ambiguity, especially when dealing with year values, it’s highly recommended to use four-digit years. For more information on handling two-digit year values, refer to Server configuration: two digit year cutoff.

Return Types and Values

DATEDIFF returns an int value, representing the integer difference between the startdate and enddate based on the specified datepart.

The function calculates the difference by counting the boundaries of the datepart crossed between the two dates. For instance, SELECT DATEDIFF(day, '2036-03-01', '2036-02-28'); returns -2. This result highlights an important aspect of DATEDIFF: it considers the direction of the date difference. In this case, moving from March 1st, 2036 back by two day boundaries leads to February 28th, 2036, correctly accounting for 2036 being a leap year.

It’s crucial to be aware of the potential for overflow. Since DATEDIFF returns an int, there are limits to the magnitude of the difference it can handle (-2,147,483,648 to +2,147,483,647). If the calculated difference exceeds this range, DATEDIFF will raise an error. For example, with millisecond as the datepart, the maximum permissible difference is approximately 24 days, 20 hours, 31 minutes, and 23.647 seconds. For second, the limit extends to roughly 68 years, 19 days, 3 hours, 14 minutes, and 7 seconds.

If both startdate and enddate only contain time values and a non-time-related datepart is used (like day or month), DATEDIFF will return 0.

DATEDIFF is time zone aware. It utilizes the time zone offset component of startdate and enddate during the calculation.

Due to the smalldatetime data type’s minute-level accuracy, when either startdate or enddate is of type smalldatetime, seconds and milliseconds are always treated as 0 in the DATEDIFF result.

When only a time value is assigned to a date data type variable, DATEDIFF automatically sets the missing date part to the default date: 1900-01-01. Conversely, if only a date value is assigned to a time or date data type variable, the missing time part defaults to 00:00:00. If startdate and enddate have mismatched date and time parts, the missing parts are filled with these default values before the difference is calculated.

In scenarios where startdate and enddate have different date data types, and one has greater precision (more time parts or fractional seconds), DATEDIFF effectively truncates the more precise value to match the less precise one by setting the missing parts to 0 for the less precise date.

Datepart Boundaries: How DATEDIFF Counts

Understanding how DATEDIFF handles datepart boundaries is key to interpreting its results correctly. Consider the following examples:

SELECT DATEDIFF(year, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(quarter, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(month, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(dayofyear, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(day, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(week, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(weekday, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(hour, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(minute, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(second, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(millisecond, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1
SELECT DATEDIFF(microsecond, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000'); -- Returns 1

In each of these examples, even though the dates are only separated by a fraction of a second (hundred nanoseconds), DATEDIFF returns 1 for every datepart. This is because the function counts the boundaries crossed. Moving from the last moment of 2005 to the first moment of 2006 crosses a year boundary, a quarter boundary, a month boundary, and so on for each specified datepart.

If startdate and enddate fall within the same calendar week but in different years, DATEDIFF with week as the datepart will return 0. This is because no week boundary is crossed in such a scenario when considering the ISO week date system which can span across calendar years.

Important Remarks for Using DATEDIFF

  • Usage Context: You can use DATEDIFF within various clauses of your SQL queries, including SELECT, WHERE, HAVING, GROUP BY, and ORDER BY.

  • String Literals and Data Types: DATEDIFF implicitly converts string literals to datetime2 data type. It’s important to note that DATEDIFF does not support the YDM format when dates are provided as strings. To use YDM, you must explicitly cast the string to either datetime or smalldatetime.

  • SET DATEFIRST Setting: The SET DATEFIRST setting, which defines the first day of the week, has no bearing on DATEDIFF. DATEDIFF consistently uses Sunday as the first day of the week to ensure predictable and deterministic behavior across different configurations.

  • Potential Overflow: As mentioned earlier, be mindful of potential overflow issues if the date difference, especially with high-precision dateparts like minute or finer, results in a value outside the valid range for the int data type.

Practical Examples of DATEDIFF in Action

The following examples demonstrate the versatility of DATEDIFF using different types of inputs for startdate and enddate. These examples are based on the AdventureWorks2022 or AdventureWorksDW2022 sample databases.

A. Using Columns as Start and End Dates

This example calculates the difference in days between dates stored in two columns of a table.

CREATE TABLE dbo.Duration ( startDate DATETIME2, endDate DATETIME2 );
INSERT INTO dbo.Duration (startDate, endDate) VALUES ('2007-05-06 12:10:09', '2007-05-07 12:10:09');
SELECT DATEDIFF(day, startDate, endDate) AS [Duration] FROM dbo.Duration;

Result:

Duration
--------
1

B. Using User-Defined Variables

In this example, variables are used to represent the startdate and enddate.

DECLARE @startdate AS DATETIME2 = '2007-05-05 12:10:09.3312722';
DECLARE @enddate AS DATETIME2 = '2007-05-04 12:10:09.3312722';
SELECT DATEDIFF(day, @startdate, @enddate);

C. Using Scalar System Functions

Scalar system functions like GETDATE() (current date and time) and SYSDATETIME() (system date and time with higher precision) can be used as arguments.

SELECT DATEDIFF(millisecond, GETDATE(), SYSDATETIME());

This example calculates the difference in milliseconds between the time obtained by GETDATE() and SYSDATETIME().

D. Utilizing Scalar Subqueries and Functions

DATEDIFF can also work with results from subqueries or other scalar functions.

USE AdventureWorks2022;
GO
SELECT DATEDIFF(day, (SELECT MIN(OrderDate) FROM Sales.SalesOrderHeader), (SELECT MAX(OrderDate) FROM Sales.SalesOrderHeader));

This query calculates the number of days between the earliest and latest order dates in the SalesOrderHeader table.

E. Specifying Constants for Dates

You can directly use date constants as strings.

SELECT DATEDIFF(day, '2007-05-07 09:53:01.0376635', '2007-05-08 09:53:01.0376635');

F. Combining Numeric Expressions and System Functions

This example showcases using a numeric expression (GETDATE() + 1) and system functions (GETDATE(), SYSDATETIME()) for enddate.

USE AdventureWorks2022;
GO
SELECT DATEDIFF(day, '2007-05-07 09:53:01.0376635', GETDATE() + 1) AS NumberOfDays
FROM Sales.SalesOrderHeader;
GO
USE AdventureWorks2022;
GO
SELECT DATEDIFF(day, '2007-05-07 09:53:01.0376635', DATEADD(day, 1, SYSDATETIME())) AS NumberOfDays
FROM Sales.SalesOrderHeader;
GO

These queries calculate the difference in days between a fixed date and the date one day after the current date.

G. Using Ranking Functions for Start Date

Ranking functions like ROW_NUMBER() can be incorporated as startdate.

USE AdventureWorks2022;
GO
SELECT p.FirstName, p.LastName,
       DATEDIFF(day, ROW_NUMBER() OVER (ORDER BY a.PostalCode), SYSDATETIME()) AS 'Row Number'
FROM Sales.SalesPerson AS s
INNER JOIN Person.Person AS p
    ON s.BusinessEntityID = p.BusinessEntityID
INNER JOIN Person.Address AS a
    ON a.AddressID = p.BusinessEntityID
WHERE TerritoryID IS NOT NULL AND SalesYTD > 0;

This example, while perhaps less practically useful in this specific form, illustrates the flexibility of using ranking function results within DATEDIFF.

H. Aggregate Window Functions for Start Date

Aggregate window functions, such as MIN() OVER (PARTITION BY ...), can also be used.

USE AdventureWorks2022;
GO
SELECT soh.SalesOrderID, sod.ProductID, sod.OrderQty, soh.OrderDate,
       DATEDIFF(day, MIN(soh.OrderDate) OVER (PARTITION BY soh.SalesOrderID), SYSDATETIME()) AS 'Total'
FROM Sales.SalesOrderDetail AS sod
INNER JOIN Sales.SalesOrderHeader AS soh
    ON sod.SalesOrderID = soh.SalesOrderID
WHERE soh.SalesOrderID IN (43659, 58918);
GO

This query calculates the difference in days between the earliest order date for each SalesOrderID and the current system date.

I. Displaying Date Difference as a String

This example demonstrates a more complex scenario: calculating the difference between two dates and presenting it as a human-readable string, breaking down the difference into years, months, days, hours, minutes, seconds, and milliseconds.

-- DOES NOT ACCOUNT FOR LEAP YEARS
DECLARE @date1 AS DATETIME, @date2 AS DATETIME, @result AS VARCHAR (100);
DECLARE @years AS INT, @months AS INT, @days AS INT, @hours AS INT, @minutes AS INT, @seconds AS INT, @milliseconds AS INT;

SET @date1 = '1900-01-01 00:00:00.000';
SET @date2 = '2018-12-12 07:08:01.123';

SELECT @years = DATEDIFF(yy, @date1, @date2);
IF DATEADD(yy, -@years, @date2) < @date1
    SELECT @years = @years - 1;
SELECT @date2 = DATEADD(yy, -@years, @date2);
SELECT @months = DATEDIFF(mm, @date1, @date2);
IF DATEADD(mm, -@months, @date2) < @date1
    SELECT @months = @months - 1;
SELECT @date2 = DATEADD(mm, -@months, @date2);
SELECT @days = DATEDIFF(dd, @date1, @date2);
IF DATEADD(dd, -@days, @date2) < @date1
    SELECT @days = @days - 1;
SELECT @date2 = DATEADD(dd, -@days, @date2);
SELECT @hours = DATEDIFF(hh, @date1, @date2);
IF DATEADD(hh, -@hours, @date2) < @date1
    SELECT @hours = @hours - 1;
SELECT @date2 = DATEADD(hh, -@hours, @date2);
SELECT @minutes = DATEDIFF(mi, @date1, @date2);
IF DATEADD(mi, -@minutes, @date2) < @date1
    SELECT @minutes = @minutes - 1;
SELECT @date2 = DATEADD(mi, -@minutes, @date2);
SELECT @seconds = DATEDIFF(ss, @date1, @date2);
IF DATEADD(ss, -@seconds, @date2) < @date1
    SELECT @seconds = @seconds - 1;
SELECT @date2 = DATEADD(ms, -@seconds, @date2);
SELECT @milliseconds = DATEDIFF(ms, @date1, @date2);


SELECT @result = CAST (@years AS VARCHAR (10)) + ' years, ' + CAST (@months AS VARCHAR (10)) + ' months, ' + CAST (@days AS VARCHAR (10)) + ' days, ' + CAST (@hours AS VARCHAR (10)) + ' hours, ' + CAST (@minutes AS VARCHAR (10)) + ' minutes and ' + CAST (@seconds AS VARCHAR (10)) +
    CASE
        WHEN @milliseconds > 0 THEN '.' + CAST (@milliseconds AS VARCHAR (10))
        ELSE ''
    END + ' seconds';

SELECT @result;

Result:

118 years, 11 months, 11 days, 7 hours, 8 minutes and 1.123 seconds

Note: This example provides a detailed breakdown but does not fully account for leap years in its logic for simplicity. For production scenarios requiring absolute accuracy, especially across long durations, consider more robust leap year handling.

Examples for Azure Synapse Analytics and Analytics Platform System (PDW)

The DATEDIFF function behaves similarly in Azure Synapse Analytics and Analytics Platform System (PDW). Here are a few examples demonstrating its use within these environments, drawing upon the AdventureWorks sample data.

J. Columns as Start and End Dates in Synapse/PDW

CREATE TABLE dbo.Duration ( startDate DATETIME2, endDate DATETIME2 );
INSERT INTO dbo.Duration (startDate, endDate) VALUES ('2007-05-06 12:10:09', '2007-05-07 12:10:09');
SELECT TOP (1) DATEDIFF(day, startDate, endDate) AS [Duration] FROM dbo.Duration;

K. Scalar Subqueries and Functions in Synapse/PDW

-- Uses AdventureWorks
SELECT TOP (1) DATEDIFF(day, (SELECT MIN(HireDate) FROM dbo.DimEmployee), (SELECT MAX(HireDate) FROM dbo.DimEmployee)) FROM dbo.DimEmployee;

L. Constants for Dates in Synapse/PDW

-- Uses AdventureWorks
SELECT TOP (1) DATEDIFF(day, '2007-05-07 09:53:01.0376635', '2007-05-08 09:53:01.0376635') FROM DimCustomer;

M. Ranking Functions in Synapse/PDW

-- Uses AdventureWorks
SELECT FirstName, LastName, DATEDIFF(day, ROW_NUMBER() OVER (ORDER BY DepartmentName), SYSDATETIME()) AS RowNumber
FROM dbo.DimEmployee;

N. Aggregate Window Functions in Synapse/PDW

-- Uses AdventureWorks
SELECT FirstName, LastName, DepartmentName,
       DATEDIFF(year, MAX(HireDate) OVER (PARTITION BY DepartmentName), SYSDATETIME()) AS SomeValue
FROM dbo.DimEmployee;

Related Content

To further expand your knowledge and capabilities with date and time manipulation in SQL Server, explore these related resources:

  • DATEDIFF_BIG (Transact-SQL): For handling very large date differences that might exceed the int range.
  • Date and Time Data Types and Functions (Transact-SQL): An overview of all date and time functionalities in Transact-SQL.
  • Transact-SQL Syntax Conventions: Understand the syntax conventions used in Transact-SQL, including date and time functions.

By mastering the DATEDIFF function and understanding its nuances, you gain a powerful tool for effectively working with dates and times in SQL Server, enabling you to perform complex calculations and analysis with ease.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *