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…

Method Breakpoints are Evil

Some IDEs expose an option to set "Method Breakpoints", it turns out that "Method Breakpoints" might tremendously decrease debugger's performance. In this article, I explain what are "Method Breakpoints" and the reasons they impact performance so badly. To better understand this subject I will cover how Debuggers works under the hoods and how Breakpoints and Method Breakpoints are implemented internally. Java Platform Debugger Architecture JDPA is an architecture designed for enabling communication between debuggers and debugees. The architecture consists of three APIs: JVM Tool Interface (JVM TI) - A native interface which defines the services a VM must provide for debugging purposes Java Debug Wire Protocol (JWDP) - A textual API which defines the communication's protocol between debugee and debuggerJava Debug Interface (JDI) - Defines a high-level Java API designed to facilitate the interaction between debugge and debuggers. Internally J…