I was working on a project the other day and I needed to migrate/convert some excellent .NET code into a T-SQL stored procedure. Not a terribly daunting task, but a couple of speedbumps along the way. One such speedbump came when converting some code that used DateTime.Date and DateTime.DaysInMonth methods. I was also creating new DateTimes based on the month and year of a given DateTime to get a “first of the month” datetime.
After a litle time spent on a un-named search engine, I found some code that makes clever use of T-SQL datetime math to achieve the results I was looking for. After some analysis, I tweaked what I found a little and ended up with what you’ll see below.
DateTime.Date
To strip out the time from a datetime, leaving it in the form ‘2009-07-07 00:00:00′, you can do something like this:
SELECT CONVERT(DATETIME, DATEDIFF(dd, 0, @date))
Now, at this point you might be asking “why does this work”? Well, let’s take a closer look at this one from the inside out, starting with the DATEDIFF(dd, 0, @date) part. The purpose of the DATEDIFF(datepart, startdate, enddate) function is to determine “the count (signed integer) of the specified datepart boundaries crossed between the specified startdate and enddate.” (Yeah, that’s straight from MSDN). So, looking at our code, what we are doing is determining the number of dateparts of type ‘dd’ (day) that have occured between ‘0′ and our date. It helps to know that ‘0′ is the same as ‘1900-01-01 00:00:00′. If our sample date is ‘2009-07-10 12:31:00′, then that number of days is 40002. From there, we simply CONVERT this number back to a datetime, resulting in the desired outcome.
First of the Month
Next, we’ll take a look at a ‘first of the month’ in T-SQL. There isn’t a .NET ‘method’, per se to do this, but by passing appropriate parameters into the DateTime constructor, you can easily get what you need. In T-SQL we could create a new datetime by concatenating strings to achieve the same effect as a DateTime constructor, but that is messy at best. It would be better to use something cleaner, that looks more like what we did in the previous example. The approach on this one is actually very similar to the previous example, in that we start by using DATEDIFF to compare the date in question to the ‘0′ date.
SELECT DATEADD(mm, DATEDIFF(mm, 0, @date), 0)
Looking at this close, we see that DATEDIFF is used to determine the number of months since ‘0′. Then DATEADD is applied to add this number of months to the ‘0′ date, which results in the first of the month. Good Stuff.
DateTime.DaysInMonth
Moving on, let’s look at what it takes to achieve the equivalent of DateTime.DaysInMonth in T-SQL. This one is considerably more nasty-looking, and I’ll leave it as an exercise for the reader to break it down and find out what makes it tick. Hint: look for the previous example embedded within this one.
SELECT DATEPART(dd, DATEADD(dd, -1, DATEADD(mm, DATEDIFF(mm, 0, @date) + 1, 0)))
Wrapping it up, nice and tidy
The obvious next step, once I had these awesome snippets of T-SQL was to wrap them up into functions so that I could re-use them to my heart’s content. Here is a chunk of T-SQL code that you can use to create the same functions, followed by a short sample of how to apply it.
create function [dbo].[dateOnly] (@datetime datetime) returns datetime
as
begin
return convert(datetime, datediff(dd, 0, @datetime))
end
go
create function [dbo].[firstOfMonth] (@datetime datetime) returns datetime
as
begin
return dateadd(m, datediff(mm, 0, @dateTime), 0)
end
go
create function [dbo].[daysInMonth] (@dateTime datetime) returns int
as
begin
return datepart(dd, dateadd(dd, -1, dateadd(m, datediff(mm, 0, @dateTime) + 1, 0)))
end
go
declare @myDate datetime
set @myDate = ‘2009-07-04 15:34:59′
select dbo.daysInMonth(@myDate)