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:
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:
Code from .NET Reflactor:
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:
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.
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:
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 objectOne 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
I found this article very useful and helpful. Thanks for the deep dive into that issue