Calendar Table and Date/Time Functions
I frequently see questions in the forums and newsgroups about how to best query date/time data and perform date manipulation. Let me first say that a permanent calendar table that materializes commonly used DATEPART values along with time periods you frequently use is invaluable. I’ve used such a table for over a decade with great success and strongly recommend you implement one on all of your database servers. I’ve included a sample calendar table (and numbers table) later in this post and you can find other variations of such a table via an internet search.
Removing the Time Portion
A common requirement I have is to remove the time portion from a date/time value. This is easy in SQL 2008 since you can simply “CAST(SomeDateTimeValue AS date)”. But the date data type is not available in older SQL Server versions so you need an alternate method. In SQL 2005 and earlier versions, I recommend the DATEADD…DATEDIFF method below with an arbitrary base date value specified in a format that is independent of the session DATAFORMAT setting:
SELECT CAST(GETDATE() AS date); --SQL 2008 and later SELECT DATEADD(day, DATEDIFF(day, '19000101', GETDATE()), '19000101'); --SQL 2005 and earlier |
I often see a variation of the DATEADD…DATEDIFF technique with the integer zero (no quotes) specified as the base date. Although this may provide the expected results (I’ve done it myself), I caution against it because it relies on implicit conversion from the internal SQL Server integer date/time storage format. If you want to be concise, a better approach is to specify an empty string for the base date value since the default value is ‘1900-01-01 00:00:00’. In my opinion, an explicit data value is more intuitive, though.
SELECT DATEADD(day, DATEDIFF(day, '', GETDATE()), ''); |
I also sometimes see code that extracts the year, month and day date parts and concatenates with separators. However, that method is dependent on session DATEFORMAT settings and slower than other methods. See Tibor Karaszi’s The ultimate guide to the datetime datatypes article for details.
First and Last Day of Period
Another common task is to determine the first or last day of a given period. The script below shows how to accomplish this of you don’t have a calendar table with the calculated values available.
DECLARE @Date date = GETDATE(); SELECT 'First day of year' [DateDescription], DATEADD(year, DATEDIFF(year,'19000101',@Date), '19000101') AS [CalendarDate] UNION ALL SELECT 'Last day of year', DATEADD(day,-1,DATEADD(year,0,DATEADD(year,DATEDIFF(year,'19000101',@Date)+1,'19000101'))) UNION ALL SELECT 'First day of month', DATEADD(month, DATEDIFF(month,'19000101',@Date), '19000101') UNION ALL SELECT 'Last day of month', DATEADD(day,-1,DATEADD(month,0,DATEADD(month,DATEDIFF(month,'19000101',@Date)+1,'19000101'))) UNION ALL SELECT 'First day week (based on DATEFIRST setting)', DATEADD(day,-(DATEPART(weekday ,@Date)-1),DATEDIFF(day,'19000101', @Date)) UNION ALL SELECT 'Last day of week (based on DATEFIRST setting)', DATEADD(day,-(DATEPART(weekday ,@Date)-1)+6,DATEDIFF(day,'19000101', @Date)); |
With a calendar table like the one later in this post:
DECLARE @Date date = GETDATE(); SELECT 'First day of year' [DateDescription], (SELECT FirstDateOfYear FROM dbo.Calendar WHERE CalendarDate = @Date) UNION ALL SELECT 'Last day of year', (SELECT LastDateOfYear FROM dbo.Calendar WHERE CalendarDate = @Date) UNION ALL SELECT 'First day of month', (SELECT FirstDateOfMonth FROM dbo.Calendar WHERE CalendarDate = @Date) UNION ALL SELECT 'Last day of month', (SELECT LastDateOfMonth FROM dbo.Calendar WHERE CalendarDate = @Date) UNION ALL SELECT 'First day week (based on DATEFIRST setting)', (SELECT FirstDateOfWeek FROM dbo.Calendar WHERE CalendarDate = @Date) UNION ALL SELECT 'Last day of week (based on DATEFIRST setting)', (SELECT LastDateOfWeek FROM dbo.Calendar WHERE CalendarDate = @Date); |
Calendar and Numbers Table
I think auxiliary calendar and number tables are a must-have on every database server. These objects allow you to easily perform set-based processing in a number of scenarios. In fact, the calendar table population script below uses a numbers table to populate the calendar table with several thousand rows in under a second. This is much more efficient that a WHILE loop.
This calendar table population script also updates the table with most US holidays and adjusts business/non-business days accordingly. In addition to customizing the script for holidays as observed by your organization, you might add fiscal period start/end dates to facilitate querying based on those cycles. Also consider creating user-defined functions or stored procedures to encapsulate frequently used code that uses the calendar table. For example, here is a function that returns the date that is a specified number of business days from the date provided:
CREATE FUNCTION dbo.udf_AddBusinessDays (@Date date, @BusinessDays int) RETURNS date AS BEGIN RETURN ( SELECT TOP (1) CalendarDate AS BusinessDate FROM (SELECT TOP (@BusinessDays) CalendarDate FROM dbo.Calendar WHERE CalendarDate > @Date AND BusinessDay = 1 ORDER BY CalendarDate) AS BusinessDays ORDER BY CalendarDate DESC ) END GO |
Script 1: Example calendar table utility function
--auxiliary number table CREATE TABLE dbo.Numbers( Number int NOT NULL CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED ); --load Numbers table with 1,000,000 numbers WITH t1 AS (SELECT 0 AS n UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0) ,t2 AS (SELECT 0 AS n FROM t1 t1a, t1 t1b, t1 t1c, t1 t1d) ,t3 AS (SELECT 0 AS n FROM t2 t2a, t2 t2b, t2 t2c) ,numbers AS (SELECT ROW_NUMBER() OVER(ORDER BY n) - 1 AS number FROM t3) INSERT INTO dbo.Numbers WITH (TABLOCKX) ( Number ) SELECT number FROM numbers WHERE number < 1000000; |
Script 2: Create and populate numbers table
CREATE TABLE dbo.Calendar( --load dates from 2000-01-01 through 2099-12-31 --New Year's Day --New Year's Day celebrated on Friday, December 31 when January 1 falls on Saturday --Martin Luther King Day - 3rd Monday in January --President's Day - 3rd Monday in February --Good Friday - 2 days before Easter Sunday --Memorial Day - last Monday in May --Independence Day - July 4th --Independence Day celebrated on Friday, July 3 when July 4 falls on a Saturday --Independence Day celebrated on Friday, July 3 when July 4 falls on a Saturday --Columbus Day - second Monday in October --Veteran's Day - November 11 --Thanksgiving - fourth Thursday in November AND (SELECT COUNT(*) FROM UPDATE dbo.Calendar --Christmas day celebrated on Friday, December 24 when December 25 falls on a Saturday --Christmas day celebrated on Monday, December 24 when December 25 falls on a Sunday |
Script 3: Create and populate calendar table and update with holidays
Legacy Comments
MrDee
2010-07-20 |
re: Calendar Table and Date/Time Functions Brilliant - Thank you! The 'Removing the Time Portion' solution is sheer elegance. |
Chirag Shah
2010-09-09 |
re: Calendar Table and Date/Time Functions Dan, this is an excellent post on the subject. If you could elaborate various use of numbers table to solve some T-SQL problems (may be on a next blog post) Thanks! |
Ramjet
2012-06-30 |
re: Calendar Table and Date/Time Functions Good Morning Mr. Guzman, I've reference your calendar script before (It's a favorite) but it used to have 5-10 (don't remember exactly) common functions listed as well. Am I remembering incorrectly? JB |
guzmanda
2012-06-30 |
re: Calendar Table and Date/Time Functions I included only a few sample queries in my post so you might be thinking of another site or article. If you need help developing other ones you need, let me know your requirements and I'll try to help. |
jamin
2012-08-24 |
re: Calendar Table and Date/Time Functions great stuff - very helpful! |