Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DateTime.ParseExact with 7 digits / one or two digit month

Until now i thought that i would understand how DateTime.ParseExact works, but this is confusing. Why does following line returns false?

DateTime.TryParseExact("2013122", "yyyyMdd", CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out lastUpdate)

The month can also have two digits. In my opinion it should be able to understand that it means 22 January 2013. Why i'm on the wrong track? Did I miss something or is there an easy workaround?


Meanwhile i'm using this workaround which is not very elegant but works:

public static DateTime? ParseDate_yyyyMdd(String date)
{
    if (date == null)
        return null;
    date = date.Trim();
    if (date.Length < 7)
        return null;
    if (date.Length == 7)
        date = date.Insert(4, "0");
    DateTime dt;
    if (DateTime.TryParseExact(date, "yyyyMMdd", CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt))
        return dt;
    return null;
}

Gives my desired result:

DateTime? date = ParseDate_yyyyMdd("2013122");
Console.Write(date.ToString()); // 01/22/2013

However, i'm still interested in the reason for this limitation. Maybe someone also has a better approach.

like image 845
Tim Schmelter Avatar asked Feb 20 '14 08:02

Tim Schmelter


2 Answers

I did track down that in source code. Which confirms the answers of flipchart and Mark Sturgill.

Somewhere an internal ParseByFormat is called which counts (in your case) the 'M':

    // System.DateTimeParse
    private static bool ParseByFormat(ref __DTString str, ref __DTString format, ref ParsingInfo parseInfo, DateTimeFormatInfo dtfi, ref DateTimeResult result)
    {   
        ...
        case 'M':
            num = format.GetRepeatCount();
            if (num <= 2)
            {
                if (!DateTimeParse.ParseDigits(ref str, num, out newValue2) && (!parseInfo.fCustomNumberParser || !parseInfo.parseNumberDelegate(ref str, num, out newValue2)))
                {
                    result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
                    return false;
                }
            }

The next call is not very interesting, except for the 2 little numbers in ParseDigits call:

    // System.DateTimeParse
    internal static bool ParseDigits(ref __DTString str, int digitLen, out int result)
    {
        if (digitLen == 1)
        {
            return DateTimeParse.ParseDigits(ref str, 1, 2, out result);
        }
        return DateTimeParse.ParseDigits(ref str, digitLen, digitLen, out result);
    }

But now we get to the fun part:

    // System.DateTimeParse
    internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDigitLen, out int result)
    {
        result = 0;
        int index = str.Index;
        int i;
        for (i = 0; i < maxDigitLen; i++)
        {
            if (!str.GetNextDigit())
            {
                str.Index--;
                break;
            }
            result = result * 10 + str.GetDigit();
        }
        if (i < minDigitLen)
        {
            str.Index = index;
            return false;
        }
        return true;
    }

So that means (as already answered):

If you do not use the maximum number of digits AND the next character is also a digit, the format is not valid. Which is the reason why the following returns true:

DateTime.TryParseExact("20131-22", "yyyyM-dd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out lastUpdate)

Don't ask me about the reasons for that limitation - it is just there in source code.

like image 128
toATwork Avatar answered Oct 13 '22 00:10

toATwork


From MSDN documentation:

If you do not use date or time separators in a custom format pattern, use the invariant culture for the provider parameter and the widest form of each custom format specifier. For example, if you want to specify hours in the pattern, specify the wider form, "HH", instead of the narrower form, "H".

I think that the reason is that it tries to parse left to right (without backtracking). Because there are no delimiters, it can't determine the boundaries of the date parts.

like image 33
flipchart Avatar answered Oct 13 '22 01:10

flipchart