Skip to main content

Understanding DateTime.ParseExact

When searching information about DateTime.ParseExact all documentations and blogs mention that the 'dateString' and the 'formatString' must be identical with the same date & time separators and format.
Although true, it's not the a precise description of what is going on under the hoods. Some times when needed this rule can be bypassed. I will explain later how. For now lets see the steel thread:
      string dateString = "2012-01-01 13:30"
      string formatString = "yyyy-MM-dd HH:mm";
      DateTime.ParseExact(dateString,formatString ,currentCultureFormatter) 

      string dateString = "2012 01~01 13|30"
      string formatString = "yyyy MM~dd HH|mm";
      DateTime.ParseExact(dateString,formatString ,currentCultureFormatter) 

      string dateString = "2012-01|01 13~30"
      string formatString = "yyyy-MM|dd HH~mm";
      DateTime.ParseExact(dateString,formatString ,currentCultureFormatter)

NOTE: currentCultureFormatter must be of type IFormatProvider usually a CultureInfo object is used, when null is used internally .NET makes use of the current Thread CultureInfo object.
For more information see my previous post String and DateTime Fotamatting with IFormatProvider & ICustomFormatter


All above examples will work because the separators and formats are equal.
Internally DateTime.ParseExact has two main methods DoStricParse and ParseByFormat. DoStricParse iterates through 'formaString' and calls ParseByFormat for each char.

ParseByFormat tries to validate the 'dateString' and the 'formatString' by evaluating each char. If the 'dateString' and the 'formatString' are equal the method returns true to DoStricParse that in turn continues to parse the date, otherwise at the end of the method there is a simple validation that return false and an exception if the format are not equal.

Code from .NET Reflactor:
else if (!str.Match(failureMessageFormatArgument))
{
    result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime" , null );
    return false;
}
Till now, nothing that you didn't know already. But the interesting thing is that a few lines above this last validation ParseByFormat search explicitly for the ":" char, if it finds it tries to make sure that in the 'dateString' the culture TimeSeparator is placed exactly in the same position.

Code from .NET Reflactor:
private static bool ParseByFormat(ref __DTString str, ref __DTString format, ref ParsingInfo parseInfo, DateTimeFormatInfo dtfi, ref DateTimeResult result)
{
    char failureMessageFormatArgument = format.GetChar();
    switch (failureMessageFormatArgument)
    {
       .
       .
       .
        case '.':
            if (!str.Match(failureMessageFormatArgument))
            {
                if (!format.GetNext() || !format.Match( 'F'))
                {
                    result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime" , null );
                    return false;
                }
                format.GetRepeatCount();
            }
            goto Label_0A5A;

        case ':':
            if (str.Match(dtfi.TimeSeparator))
            {
                goto Label_0A5A; 
            }
            result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
            return false;
      .
      .
      .
}
That means that the rule "The DateTime.ParseExact(String, String, IFormatProvider) method parses the string representation of a date, which must be in the format defined by the format parameter" MSDN
Is not accurate when talking about the 'Time' parsing (The 'Date' part must be indeed identical), because as explained above the next scenario will be valid as well although the 'formatString' and the 'dateString' are not equal:
    CultureInfo currentCultureFormatter = CultureInfo.CreateSpecificCulture("it-IT" );
    string dateString = "2012-01-01 13.30"
    string format = "yyyy-MM-dd HH:mm";
    DateTime.ParseExact(dateString,format,currentCultureFormatter) 
The Italian culture TimeSeparator is sometimes (depending on the specific OS) set to '.'



So when the method encounters the ':' char in 'formatString' it finds in 'dateString' in the same position the Italian TimeSeparator '.' and it returns true to DoStricParse.

A practical problem that you can came a cross without this Post insight is in the following scenario:

In your international application you have a piece of code that runs successfully for all the supported languages in production that retrieves a date string from DB and converts it to a DateTime object in a fixed format.
     //* From DB
      string dateString = "2012-01-01 13:30"
      string formatString = "yyyy-MM-dd HH:mm";
      DateTime.ParseExact(dateString,format,null)//* Null: Use current Thraed CultureInfo object
One day the Product Team decide to support a new language - Italian. Suddenly the application starts crashing although the 'dateString' and 'formatString' are equal. The code that worked nicely for all other languages throws an exception.

Now equipped with your new knowledge you know that the reason is that Italian is a rare case where the TimeSeparator is not ':' but '.' instead.

When working with WindowForm and or ConsoleApplication you can fix the problem by simply changing the relevant culture TimeSeparator (in this case Italian) with the standard ':' in Regional Settings.
But for some reasons that will be discussed in another post, a WebApp hosted in IIS is not influenced by the configuration in Regional Settings.

One simple fast solution for IIS Web Apps can be to dynamically concatenate the TimeSeparator based on the Current TimeSeparator:
    CultureInfo currentCultureFormatter = CultureInfo.CreateSpecificCulture("it-IT" );

    string dateTime = "2012-01-01 13" + new System.Globalization.CultureInfo ("it-IT" ).DateTimeFormat.TimeSeparator + "30";
    //* Instead of string = "2012-01-01 13:30";
    string format = "yyyy-MM-dd HH:mm";
    DateTime.ParseExact(dateString,format,currentCultureFormatter) 

Comments

YARDEN COCAHV said…
Hi,
I found this article very useful and helpful. Thanks for the deep dive into that issue

The Best

GetHashCode Hands-On Session

The following is a hands-on post meant to demonstrate how GetHashCode() and Equals() methods are used by .NET Framework under the hood. For the sake of simplicity I will refer to the popular hashed-base Dictionary type, although any other hash based structure will follow a similar behavior, if not the same one. After understanding this post you should be able to spot potential problematic behaviors and resolve them, prevent creation of unreachable items in dictionaries and improve CRUD actions performance on hash based structures. The TheoryGetHashCode() is used to create a unique integer identifier for objects/structs. The hashcode can be used for two purposes: Programmatically, by developers, to distinguish objects/structs form each other (NOTE: Not recommended when the default .NET implementation is used, as it's not guaranteed to preserve the same hash between .NET versions and platforms)Internally, by .NET Framework, when using the object/struct as a key in a hashed based l…

Closures in C# vs JavaScript -
Same But Different

Closure in a Nutshell Closures are a Software phenomenon which exist in several languages, in which methods declared inside other methods (nested methods), capture variables declared inside the outer methods. This behavior makes captured variables available even after the outer method's scope has vanished.

The following pseudo-code demonstrates the simplest sample:
Main() //* Program starts from here { Closures(); } AgeCalculator() { int myAge = 30; return() => { //* Returns the correct answer although AgeCalculator method Scope should have ordinarily disappear return myAge++; }; } Closures() { Func ageCalculator = AgeCalculator(); //* At this point AgeCalculator scopeid cleared, but the captured values keeps to live Log(ageCalculator()); //* Result: 30 Log(ageCalculator()); //* Result: 31 } JavaScript and C# are two languages that support…

SSL Distilled

A few days ago, my wife asked me: "why is it, sometimes when I access my web-mail account at work, I get a scary browser popup threatening and yelling at me that the site is dangerous and I should click 'OK'...?".
I am pretty sure this post will answer her question. SSL without Sweating Although SSL is very popular and well documented on many web articles, blogs, books & technical videos, when trying to really understand and assemble all the bits and bytes, you will probably find yourself breaking a sweat, because of the many misconceptions about this subjects. In this article I am going to give a brief top-to-bottom review of the must-know parts of SSL and try to re-explain the less familiar parts of this protocol. In order to make it easy for those of you that are familiar with SSL and only want to read the 'Hot' parts, I added an '*' as prefix to those points, so you guys can jump between them. …