Welcome to my blog! I'm a Sr. Software Development Engineer in the Seattle area, who has been performing C++/C#/Java development for over 20 years, but have definitely learned that there is always more to learn!
All thoughts and opinions expressed in my blog and my comments are my own and do not represent the thoughts of my employer.
Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.
A while back I talked about some goodies in DateTime, mostly the properties that let you split out just the date or time. This week, I wanted to look at a couple more methods of the DateTime struct that give you additional control over parsing an input string into a DateTime.
Most of us have dealt with using DateTime.Parse() for these tasks, but sometimes you are wanting to parse something that may not be a valid DateTime, or may be in a non-standard format. So let’s look at the TryParse() and ParseExact() methods that can be used to deal with these two situations.
So, as you probably already know, if you attempt to Parse() an input string that is not in a valid format, or contains invalid values, it will throw a FormatException. For example:
1: // throws FormatException, not a valid format
2: var dt1 = DateTime.Parse("");
4: // throws FormatException, February doesn’t have a 30th day
5: var dt2 = DateTime.Parse("02/30/2010 12:35");
7: // succeeds
8: var dt3 = DateTime.Parse("01/11/1984 13:00");
This is pretty much what you’d expect for many situations, but what if you are processing a file or user input that has a fairly high chance of having an invalid value, what would we do?
Well, obviously, we could just handle the exception and use that to decide how to proceed. For example, if anytime we can’t parse a date we want to assume the current date and time, we could do:
1: string input = "02/30/2010 12:35";
2: DateTime recordDate;
4: // let's say we want to parse the date, but if we can't, then we'll assume now...
7: recordDate = DateTime.Parse(input);
9: catch (Exception)
11: recordDate = DateTime.Now;
This works, obviously, but the try/catch block eats up a lot of vertical space, and it incurs the cost of throwing an exception if an error is encountered. When there is a chance that the input is invalid, and you want to handle that invalid situation instead of just bubbling up an exception, consider TryParse() instead.
The TryParse() methods have much the same signature as Parse(), except that they take a final out parameter of DateTime where the value is placed (if successful), and return a bool that indicates if the parse was successful or not. Thus, the code above could be rewritten as:
5: if(!DateTime.TryParse(input, out recordDate))
7: recordDate = DateTime.Now;
Notice that the code is much more concise without the try/catch, this way, we can attempt the parse, and if all is well the result will be in our recordDate variable, and if not we go into the body of the if (since TryParse() returns false on an error, and we negate the result) and we can then assign a “default” value for recordDate.
It should be noted that Parse() actually calls TryParse() and just throws the exception in the event TryParse() returns false. That is, Parse() is roughly equivalent to:
1: // rough psuedo-code of Parse()
2: public DateTime Parse(string inputString)
4: DateTime result;
6: if (!DateTime.TryParse(inputString, out result))
8: throw new FormatException(...);
11: return result;
So calling TryParse() directly is more efficient because it avoids the wrapper call, and it doesn’t allocate and throw an unneeded exception in the case of an error.
So let’s time the two methods above on bad data and see what we get over 1,000,000 iterations:
1: TryParse() took: 610 ms, 0.00061 ms/item.
2: Parse() took: 26645 ms, 0.026645 ms/item.
To be fair, these are time differences for 1 million bad items, when you parse good items the times of the two methods perform identically, but if you have a good chance of receiving a badly formatted string and want to directly handle it, then using TryParse() is more efficient.
What if you were reading data from a file, and the DateTime contained in it was a non-standard format. For example, let’s say we’re parsing a log file that begins with a timestamp that is a date in the format yyyyMMdd HHmmss (like 20111231 031505 for 12/31/2011 03:15:05 AM).
If we attempt to do a DateTime Parse() or TryParse() on this, we will get a failure because it is not one of the standard formats that DateTime’s parsing mechanisms understand.
1: string logString = "20111231 031505";
2: DateTime logEntryTime;
6: logEntryTime = DateTime.Parse(logString);
8: catch (Exception ex)
10: // the above will throw
11: Console.WriteLine("Didn't understand that DateTime.");
What we can do in this situation is to call ParseExact() and tell it the exact format we are expecting. We do this by specifying a standard format string or custom format string (much the same as you’d pass to DateTime.ToString() to modify it’s output if you don’t like the default output format).
1: // Note: MM is months, mm is minutes, see MSDN for details
2: logEntryTime = DateTime.ParseExact(logString, "yyyyMMdd HHmmss", null);
4: // outputs: 12/31/2011 3:15:05 AM
Note that when using the custom format strings, the case and quantity of the format specifiers can matter. For example, “MM” is months and “mm” is minutes, “HH” is 24-hour format and “hh” is 12-hour format, “mm” is zero-padded where “m” is not, etc. For further details see the MSDN.
Also notice that in the snippet above we passed a null for the IFormatProvider. Doing this uses the current culture’s DateTime format provider. If you want to use the invariant culture’s instead you can specify it manually:
1: // these two are identical (current culture)
2: logEntryTime = DateTime.ParseExact(logString, "yyyyMMdd hhmmss", null);
3: logEntryTime = DateTime.ParseExact(logString, "yyyyMMdd hhmmss", DateTimeFormatInfo.CurrentInfo);
5: // this one is invariant
6: logEntryTime = DateTime.ParseExact(logString, "yyyyMMdd hhmmss", DateTimeFormatInfo.InvariantInfo);
So this will help you parse non-standard formats, but in addition to handling invalid formats, ParseExact() is also useful if you want to only accept one format as valid (even if it’s a standard format). This is because you are telling it the explicit format you want to accept, and it doesn’t need to try several formats to see which one works – it only tries the single format specified.
For example, let’s compare doing 1,000,000 iterations of the two pieces of code below:
1: string logString = "12/31/2011";
4: // Both work, but ParseExact() wants an explicit format
5: logEntryTime = DateTime.Parse(logString);
6: logEntryTime = DateTime.ParseExact(logString, "MM/dd/yyyy", null);
If we test both of these, we see that ParseExact() is more efficient:
1: Parse() took: 700 ms, 0.0007 ms/item.
2: ParseExact() took: 494 ms, 0.000494 ms/item.
So, if you know the exact format that the date time representation should be, ParseExact() is more efficient. In addition, it will only accept that format, so you can use ParseExact() to narrow the parse behavior to only accept a single format.
Finally, it should be noted that just like Parse(), ParseExact() has a TryParseExact() that returns a bool instead of throwing if the input string is not in the expected format.
The DateTime struct has a lot of methods for parsing a string into a DateTime. Most everyone has used the Parse() method, but the cost of the exception throws on an error can become a performance bottleneck if improperly formatted input is possible.
Thus, use TryParse() when you want to be able to attempt a parse and handle invalid data immediately (instead of bubbling up the exception), and ParseExact() when the format you are expecting is not a standard format, or when you want to limit to one particular standard format for efficiency.
Print | posted on Thursday, January 5, 2012 6:34 PM |
Filed Under [
Copyright © James Michael Hare