Friday, May 22, 2009

A Beginner's Guide To DateTime and Formatting

One of the most frequently asked questions on the MSDN Forums has to do with the DateTime class, specifically formatting and persisting to a database.

Questions typically look like these:

"When I store the data in the database, it only has the date, but when I pull it out into my code, it has the time. How do I keep just the date and not the time?"

"I've tried setting the format of a date to a particular date string, but when I look at it in the debugger, nothing has changed!"

"I want to store the DateTime as 2009.05.06.01.13.13 but the database is always storing it as 06/05/2009 1:13:13 PM format. How do I change this?"

"I've used DateTime.Parse to get a DateTime object, but as soon as I do, the format of the DateTime seems to change. How can I keep it from changing?"

"I have a property that's a DateTime, and so my users have to enter their date in the format MM/dd/yyyy hh:mm:ss tt in order for the computer to recognize it, but I only want them to have to enter the hours and minutes. What should I do?"

This post aims at addressing these questions and clearing up some confusion about the DateTime class. While reading this, I want you to keep this simple fact in mind:

The DateTime instance is different from, and separated from, it's representation in text.

The DateTime Class

First, it's important to know the difference between the DateTime class and the string class. The DateTime class stores all the information necessary to represent a moment in time. It will always store the following information: the year, the month, the day, the hour, the minute, the second, the milliseconds, and the kind of date and time this is (UTC, Local or Unspecified). Let me repeat, it will always store this information, and there's no way of getting around this. In other words, if you're just trying to represent the date in a DateTime, you're going to have to recognize that the time is also carried with the instance of the DateTime class. You can ignore the time portion, and you can format the date so it doesn't appear, but it's still lurking there in the background, and it will always be there. There's no way to get rid of it. That being said, you can still display the DateTime to the user in a particular format so as to not show the portions of the date and time that you want to ignore. This is where formatting comes into play.

When it comes to formatting, it's important to recognize the difference between the DateTime and how it is represented. The DateTime instance will never store the date as text, it stores it as a series of numbers instead, and provides you methods to output the DateTime as text. The way that the date appears when you print it out is dependent upon either the culture's preferred display format, or a specific format which can be passed into the ToString method on the DateTime.

Now, remember, just because you pass a format string into the DateTime's ToString method, this doesn't mean that the DateTime is going to remember the format string you passed in, and use it again and again. It doesn't. You're going to have to tell the computer what format you want the date printed in each time you output the Date. There are two ways to do this:

1. Using the fields in the DateTimeFormatInfo class that is stored in the CultureInfo.CurrentCulture as the DateTimeFormat property.

The information at CultureInfo.CurrentCulture.DateTimeFormat specifies the default way that the DateTime will be represented when printed or shown in the debugger. Here are a few examples:

Culture: English (United States) Date: 5/22/2009 8:52:08 AM
Culture: English (United Kingdom) Date: 22/05/2009 08:52:08
Culture: French (France) Date: 22/05/2009 08:52:08
Culture: Spanish (Mexico) Date: 22/05/2009 08:52:08 a.m.
Culture: German (Germany) Date: 22.05.2009 08:52:08

Now, note that the DateTime object is the same for all of these, but what's changed is how I've formatted them. I used this code to get the above result.:


using System;
using System.Linq;
using System.Globalization;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] cultures = { "en-US", "en-GB", "fr-FR", "es-MX", "de-DE" };
DateTime dt = DateTime.Now;
cultures.ToList().ForEach(c => PrintDate(dt, c));
}

static void PrintDate(DateTime dt, string culture)
{
CultureInfo c = CultureInfo.GetCultureInfo(culture);
Console.WriteLine("Culture: {0} Date: {1}", c.DisplayName, dt.ToString(c));
}
}
}


Now, on my machine, if I put a breakpoint on the second line of the PrintDate method, and I hover over "dt", the tooltip that pops up looks like this:

{05/22/2009 08:52:08 AM}

This is because I live in the United States, and I have my computer set to use the culture en-US. How it shows up in the debugger is how I have my computer set up. If I set the culture to en-GB, it would show up differently. The way the DateTime is displayed by default is dependent upon a combination of the current thread's CultureInfo.DateTimeFormat's ShortDatePattern and LongTimePattern. I can set this explicitly using the following code. Notice I set the new ShortDatePattern to "MM.dd.yyyy" instead of "MM/dd/yyyy", which is the default for en-US.


using System;
using System.Globalization;
using System.Threading;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
CultureInfo info = (CultureInfo)CultureInfo.CurrentCulture.Clone();
info.DateTimeFormat.ShortDatePattern = "MM.dd.yyyy";
Thread.CurrentThread.CurrentCulture = info;
Console.WriteLine(DateTime.Now);
}
}
}


In my culture, this will print out "05.22.2009 08:52:08 AM", and if I add a breakpoint on the line where I'm writing to the console, and I hover over "Now", I'll see the following:

{05.22.2009 08:52:08 AM}

This is because I've changed the ShortDatePattern on the current thread's CultureInfo.DateTimeFormat.

The key to remember here is that the DateTime is still the same, it's just shown differently.

Explicit DateTime Formatting

Now, I can always explicitly set the format for the DateTime. I would go over this in detail here, but MSDN has two great pages on how to do this:

Standard Date and Time Format Strings
Custom Date and Time Format Strings

These two pages show pretty explicitly how to use formatting strings for the DateTime. I encourage you to review them and know them well.

Parsing Dates

What if you have a string, and you need to get a DateTime instead?

Well, which method you use depends on what culture you're in, and how the string is going to be represented. There are two methods (actually four, with "Try" equivalents for each) to parse a string to a DateTime. These are "Parse" and "ParseExact". The only difference between the two are that the former will attempt to create a DateTime from a string using the default culture of the process, while the latter will parse exactly as you specify. I'll focus on ParseExact here.

Let's say I have a string that looks like this: "10/21/2008", and I want to get a DateTime object that represents this date. In order to do this, I'd have to use ParseExact to parse the text out, and the code I would use would look like this:


string text = "10/21/2008";
DateTime date = DateTime.ParseExact(text, "MM/dd/yyyy", CultureInfo.InvariantCulture);


Now, if you've been following along, and you visited the link on Custom Format Strings, you'll find that the format string I passed in as the second parameter matches the date string that is passed in as the first parameter. The resulting DateTime will actually be October 21, 2008 at midnight. Now, why is it set at midnight? Remember, the DateTime object is always going to contain all of the time information down to the 10,000th of a millisecond, regardless of how it was constructed. There's no way I can eliminate that information from the DateTime object. In fact, even the Date property of the DateTime object simply does nothing but reset the time portion to midnight and return. The point is, the time is always there.

Also, you may notice that using DateTime.ParseExact doesn't make the DateTime object appear in the same format in Intellisense. This is because the DateTime object doesn't actually store the format information, as I had said earlier in the post. It stores only the information needed to represent the time, not the format info. The way it appears in Intellisense is dependent upon the settings in the thread's CultureInfo.DateTimeFormat property.

So when do you use Parse, and when do you use ParseExact?

Typically, if I know the format is always going to be in a specific format, regardless of culture, I'll use ParseExact, as it provides me with a more granular level of control. If I need to parse user input, however, I might use Parse, or TryParse to validate the user input, as the user is going to be most likely to enter the date in a format familiar to themselves. Also, if I'm reading data from a source that has dates in a textual format, and the source is culture-sensitive, I might use Parse instead of ParseExact, trusting that the cultural context under which my application runs is the same as the cultural context of the application I'm interfacing with.

Also, in some situations, I want my user to be able to enter only a portion of the DateTime in a specific user control, and I want them to be able to enter it in a specific format. In those situations, I ought to use ParseExact. For example, if I have a TextBox where a user is supposed to enter their time in the "HH:mm" format, I could validate their input, and retrieve a DateTime that would have the proper time in it like this (remember to always validate your input before running code such as the code below):


// textBox1.Text is set to "12:30"
DateTime dt = DateTime.ParseExact(textBox1.Text, "HH:mm", CultureInfo.InvariantCulture);


Now, because the above code doesn't specify the date, the ParseExact method is going to use today's date by default. This is just a small gotcha, but it's worth being aware of.

So, to sum it up, there's no way to convert a string to a DateTime. Instead, you have to create a new DateTime based on the information contained in the string, by using the DateTime.Parse, DateTime.ParseExact, DateTime.TryParse, or DateTime.TryParseExact methods.

Conclusion

The aim of this post is to simply give you a broad overview of the difference between a DateTime and it's textual representation, as well as giving you some general understanding of how to get a textual representation from a DateTime object, and how to get a DateTime object from a textual representation of a date. If there's one thing I want you to remember from this post, it's this... a DateTime instance is a different thing than the way looks in text.

It's my intention to make this post a growing document, and I plan on changing it several times to encompass more information. Feel free to add a comment or contact me through email if you have any suggestions.

Happy coding!