This is because the DateTime parser reads from left to right without returning.
As he tries to read the month, he begins to take the first two digits and uses it to analyze the month. And then he tries to make out the year, but only one figure remains, so he fails. There is simply no way to solve this problem without introducing a separator character:
DateTime.ParseExact("1 15", "M yy", CultureInfo.InvariantCulture)
If you cannot do this, first read on the right and separate the year separately (using string manipulations). Or just add zero to the beginning and MMyy it as MMyy :
string s = "115"; if (s.Length < 4) s = "0" + s; Console.WriteLine(DateTime.ParseExact(s, "MMyy", CultureInfo.InvariantCulture));
Research!
Since ispiro is requested for sources: parsing is done with the DateTimeParse type. For us, this is the ParseDigits method:
internal static bool ParseDigits(ref __DTString str, int digitLen, out int result) { if (digitLen == 1) { // 1 really means 1 or 2 for this call return ParseDigits(ref str, 1, 2, out result); } else { return ParseDigits(ref str, digitLen, digitLen, out result); } }
Note that the comment is there when digitLen is 1 . Be aware that the first number in this other ParseDigits overload is minDigitLen and the other is maxDigitLen . Thus, basically for the last digitLen of 1 function will also take a maximum length of 2 (which allows you to use one M to correspond to two-digit months).
Now another overload that actually does the job contains this loop:
while (tokenLength < maxDigitLen) { if (!str.GetNextDigit()) { str.Index--; break; } result = result * 10 + str.GetDigit(); tokenLength++; }
As you can see, the method continues to take more digits from the string until it exceeds the maximum length. The rest of the method is just error checking and stuff.
Finally, let's take a look at the actual parsing in DoStrictParse . There we have the following cycle :
// Scan every character in format and match the pattern in str. while (format.GetNext()) { // We trim inner spaces here, so that we will not eat trailing spaces when // AllowTrailingWhite is not used. if (parseInfo.fAllowInnerWhite) { str.SkipWhiteSpaces(); } if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) { return (false); } }
So basically, this happens over the characters in the format string, and then tries to match the string from left to right using this format. ParseByFormat performs additional logic that captures repeating formats (for example, yy instead of just y )) and uses this information to fork into different formats. For our months, this is the corresponding part :
if (tokenLen <= 2) { if (!ParseDigits(ref str, tokenLen, out tempMonth)) { if (!parseInfo.fCustomNumberParser || !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth)) { result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null); return (false); } } }
So, we close the circle to ParseDigits , which is transmitted with a token length of 1 for one M But, as we saw above, it will still correspond to two numbers, if possible; and all that without checking whether the number of two digits that it matches matches for a month. Therefore, 130 will not match for January 2030. It will correspond to the 13th month and will not pass later.