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, includingSELECT
,WHERE
,HAVING
,GROUP BY
, andORDER BY
. -
String Literals and Data Types:
DATEDIFF
implicitly converts string literals to datetime2 data type. It’s important to note thatDATEDIFF
does not support theYDM
format when dates are provided as strings. To useYDM
, 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 onDATEDIFF
.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.