#BackToBasics : Dating Responsibly - SentryOne Team Blog

#BackToBasics : Dating Responsibly

No, this is not going to be a synopsis of the TV series, "8 Simple Rules… for Dating My Teenage Daughter." This is a continuation of my #BackToBasics series, which I pledged after a challenge from Tim Ford. This month, after having yet another conversation about safe date formats, I thought I would put together a TL;DR version of a few of my favorite "Bad Habits" posts about date/time handling.

 

Please don't use yyyy-mm-dd

My most recent discussion about date formats in SQL Server revolved around yyyy-mm-dd – which I contend is not a safe date format to use as a string passed in to any kind of date/time parameter, variable, or column. The reason is that it can fail when casting to datetime or smalldatetime, because SQL Server swaps the month and day under certain language settings. For example:

SET LANGUAGE Deutsch; -- German
DECLARE @d datetime = '2016-04-17';
Msg 242, Level 16, State 3, Line 2
Bei der Konvertierung eines varchar-Datentyps in einen datetime-Datentyp liegt der Wert außerhalb des gültigen Bereichs.

Translation:
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.

Why? It was interpreted as yyyy-dd-mm. This is a little less obvious than other completely unsafe date formats like mm/dd/yyyy and dd/mm/yyyy.

Of course, getting an error is a good thing, but it's only because there is no 17th month. If a user running under German language settings tried to store 2015-04-06, SQL Server would have happily stored June 4th, not April 6th, and who knows when anybody would have noticed the "error."

I urge you to always use yyyymmdd (without the dashes) for a date without time – it will never fail, regardless of regional, language, or dateformat settings, and across any of the date/time data types. (And absolutely do not store it as a string data type in SQL Server – always store it as a proper date or time data type.)

And it's not just Germansee how all of the other languages fared.

 

Please let the client format dates

I see a lot of effort being spent on using T-SQL to manipulate a date/time value into a specific format intended for a report viewer or other end user. Things like CONVERT(char(10), [column], 101) or, quite often much worse, FORMAT([column], 'MM/dd/yyyy').

Let's play guess the date!Was this updated Feb 1 or Jan 2?
I have no idea.

The main problem is that you can't always control your audience. You may only have American users this moment, but that could change. And then, like above, 4/6/2015 gets interpreted by users as June 4th instead of April 6th.

Also, if you dump the output into a grid of some kind, and the user expects to sort the date column by, well, date, it actually will lump all the January dates together (regardless of year), then all the February dates, and so on. This is because it's sorting by string (unless the app has been trained to actually keep track of the original date value somehow). Oh, and if you use the more logical dd/MM/yyyy, it just breaks in a different way – all of the 1sts of the month will sort together, then all of the 2nds, then the 3rds, etc.

While SQL Server blurs the lines a bit about whether a date is just a string, I urge you to not output them that way – convert to a string for display purposes as late as possible, and when it makes sense, obey the end user's preference. We have a presentation tier for a reason: to apply formatting to raw data coming out of the database. Leave it in its native format until you can't, instead of hammering it into a string as soon as you can.

 

Please don't use shorthand

I get a kick out of seeing code like this:

SELECT DATEPART(D, GETDATE());

Now, what sort of productivity or performance gains do you think you might have gained by typing D instead of spelling out DAY? Here's why you're going to care: some shorthand is not what you would expect, and could be very problematic if you're troubleshooting at 3 AM. This is based on a quiz I've given to hundreds of attendees of my Bad Habits sessions, and maybe three people have passed in all that time:

SELECT w = DATEPART(W, '20151223'), y = DATEPART(Y, '20151223');
 
/* results:          w        y
                     ------   ------
                     4        357                 */

Admit it, it's not what you expected either, is it? W stands for WEEKDAY, not WEEK. And Y stands for DAYOFYEAR, not YEAR. Please, just spell it out. You avoid these problems and make your code self-documenting, at the mere cost of an additional 32 milliseconds or so of typing. And I promise the extra characters won't slow down your queries.

Another quick one, this:

DECLARE @d datetime = GETDATE(); SELECT @d - 1;

Fails when you use the new types:

DECLARE @d date = GETDATE() - 1;
Msg 206, Level 16, State 2, Line 1
Operand type clash: date is incompatible with int

Always use DATEADD(), even though it's a bit more to type.

Both of these are explained in more detail in "Bad Habits to Kick : Using shorthand with date/time operations."

 

Please don't use BETWEEN

When I see date range queries that use BETWEEN, I shudder. While it's very easy to find the beginning of a range, how exactly do you find the end? Unless you're dealing exclusively with the date data type (or datetime with reliable constraints preventing time), there isn't a trustworthy way to find the end, due to the varying precision and rounding behaviors of the different date/time types, as well as the problem known as February. And no, EOMONTH() is not the solution, as it just gets the last day of the given month, at midnight, which is only useful if you don't store time.

It is always possible to use an open-ended range, even when using the date data type (again, don't use BETWEEN with date, as it just makes your technique inconsistent). This is because it's always easier to find the beginning of the next range than the end of the current one. Take a month, for example. I can always get the beginning of the current month like this:

DECLARE @start datetime, @end datetime;
 
SET @start = CONVERT(date, DATEADD(DAY, -DATEPART(DAY, GETDATE()), GETDATE()));
 
/* result:      2016-04-01        */

Finding the "end" of the month is harder, because I have to know what data type I'm dealing with. I could use this common technique:

SET @end = DATEADD(MILLISECOND, -3, DATEADD(MONTH, 1, @start));
 
/* result:      2016-04-30 23:59:59.997        */

That's great, as long as the data type of all of the variables, parameters, columns, and both implicit and explicit conversions along the way stay as datetime. If the parameter changes to smalldatetime, your "end" now rounds up to May 1st at midnight. And if the column changes to datetime2, there's a lottery-style chance you could lose data (since a row with 2016-04-30 23:59:59.9984132 could theoretically be stored).

This is much more reliable:

SET @end = DATEADD(MONTH, 1, @start);
 
-- now the query changes from:
 
... WHERE [column] BETWEEN @start AND @end;
 
-- to the slightly more verbose:
 
... WHERE [column] >= @start
      AND [column] <  @end;

There is no chance you can include too much data or leave some out when you say "greater than or equal to the beginning of this range, and less than the beginning of the next." More information in "What do BETWEEN and the devil have in common?"

 

Conclusion

There you have it, four quick tips on dating responsibly, in as few words as I know how. Hope you've found it useful!

Comments ( 4 )

    • Andriy M says:

      If you need a safe format that supports time and you think the "T" in "yyyy-MM-ddThh:nn:ss.xxx[xxxx]" hurts readability for you, you can use "yyyyMMdd hh:nn:ss.xxx[xxxx]" instead.

    • Charles Kincaid says:

      I am a big fan of using ISO 8601 for date and date/time representations. Even so I was unaware of this conversion issue. Thanks for this article.

      I agree with you on the BETWEEN issue. There is one factor that you did not mention. In some cultures the end points while others exclude the end points. This can lead to false positives on boundary condition testing. So X >= '20160401 000000' AND X <= '20160401 235959'

    • Mazhar says:

      Hi, are there any issues with using dates like so '07-apr-2016'?

    • Aaron Bertrand says:

      Yes, of course, the French translator inside SQL Server will also stumble on this:

      SET LANGUAGE French;
      SELECT CONVERT(datetime, '07-apr-2016');
      Msg 241, Level 16, State 1
      Échec de la conversion de la date et/ou de l'heure à partir d'une chaîne de caractères.

    Leave A Comment

    Your email address will not be published.

    This site uses Akismet to reduce spam. Learn how your comment data is processed.