Re: Check DateTime format

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance

From: Jon Skeet [C# MVP] (skeet_at_pobox.com)
Date: 07/22/04


Date: Thu, 22 Jul 2004 08:26:44 +0100

Dan S <DanS@discussions.microsoft.com> wrote:
> My application asks the user to enter in a date - in the mm/dd/yyyy
> format. Is there any quick and easy way to verify the date the user
> enters is formatted correctly? Right now I'm calling DateTime.Parse()
> and catching the FormatException but it seems this is a bit
> inefficient - catching the exception that is. There is some pretty
> obvious delay while it traces back up the call stack. Is there a
> better way? Something that returns a bool possibly?

After the first time, which may involve a delay due to loading
resources, throwing an exception is likely to be very fast - far faster
than a user can actually notice. On my laptop I can throw a hundred
thousand exceptions in a second - I think that's rather more than a
user is likely to enter.

I'm not saying that exceptions are always a nice way to go, but I
wouldn't dismiss them for performance reasons - at least not in this
case.

You can use a regular expression to check the format, of course - but
you may well find that doing so is more expensive than just trying to
parse the date and catching the exception.

You could probably do slightly better with a hard-coded test, something
like:

static readonly char[] LowerBounds = "00/00/1000".ToCharArray();
static readonly char[] UpperBounds = "19/39/2999".ToCharArray();
static bool IsProbablyValidDate(string date)
{
    if (date.Length != 10)
    {
        return false;
    }
    for (int i=0; i < date.Length; i++)
    {
        char c=date[i];
        if (c < LowerBounds[i] || c > UpperBounds[i])
        {
            return false;
        }
    }
    return true;
}

That gets rid of *many* invalid dates, but not all - you'll still need
to call DateTime.Parse (or preferrably DateTime.ParseExact) and catch
the potential exception, unless you want to do all the parsing
correctly.

Note that it also requires the leading zeroes for months and days - if
you don't want that, it becomes slightly trickier.

(That only deals with dates in years 1000-2999; if you need to deal
with earlier or later years, change the 7th character in
LowerBounds/UpperBounds.)

Here's a benchmark to compare the three approaches mentioned:

using System;
using System.Windows.Forms; // For MethodInvoker
using System.Text.RegularExpressions;
using System.Globalization;

delegate void DoSomething();

class Test
{
    static string[] invalid = {"123123", "wibble", "32/12/3223",
            "14/23/1999", "04/35/1992", "02/29/2003"};
    
    static string[] valid = {"12/02/2321", "02/12/2312", "02/29/2004",
            "01/30/2000"};
    
    const int Iterations = 100000;
    
    static void Main()
    {
        Time (new MethodInvoker(TestRegex));
        Time (new MethodInvoker(TestHardCoded));
        Time (new MethodInvoker(TestNoPreCheck));
    }
    
    static void Time(MethodInvoker test)
    {
        DateTime start = DateTime.Now;
        test();
        DateTime end = DateTime.Now;
        
        Console.WriteLine ("{0}: {1}", test.Method.Name, end-start);
    }
    
    static readonly Regex Expression = new Regex
       (@"\d{1,2}\/\d{1,2}\/\d{4}", RegexOptions.Compiled);
    static void TestRegex()
    {
        for (int i=0; i < Iterations; i++)
        {
            foreach (string x in invalid)
            {
                if (Expression.IsMatch(x))
                {
                    try
                    {
                        DateTime.ParseExact(x, "dd/mm/yyyy",
                              CultureInfo.InvariantCulture);
                        throw new Exception("Invalid date passed");
                    }
                    catch
                    {
                    }
                }
            }
            foreach (string x in valid)
            {
                if (Expression.IsMatch(x))
                {
                    try
                    {
                        DateTime.ParseExact(x, "dd/mm/yyyy",
                              CultureInfo.InvariantCulture);

                    }
                    catch
                    {
                        throw new Exception("Valid date failed");
                    }
                }
                else
                    throw new Exception("Valid date failed");
            }
        }
    }
    
    static void TestHardCoded()
    {
        for (int i=0; i < Iterations; i++)
        {
            foreach (string x in invalid)
            {
                if (IsProbablyValidDate(x))
                {
                    try
                    {
                        DateTime.ParseExact(x, "dd/mm/yyyy",
                              CultureInfo.InvariantCulture);

                        throw new Exception("Invalid date passed");
                    }
                    catch
                    {
                    }
                }
            }
            foreach (string x in valid)
            {
                if (IsProbablyValidDate(x))
                {
                    try
                    {
                        DateTime.ParseExact(x, "dd/mm/yyyy",
                              CultureInfo.InvariantCulture);

                    }
                    catch
                    {
                        throw new Exception("Valid date failed");
                    }
                }
                else
                    throw new Exception("Valid date failed");
            }
        }
    }
    
    static void TestNoPreCheck()
    {
        for (int i=0; i < Iterations; i++)
        {
            foreach (string x in invalid)
            {
                try
                {
                    DateTime.ParseExact(x, "dd/mm/yyyy",
                                        CultureInfo.InvariantCulture);
                    throw new Exception("Invalid date passed");
                }
                catch
                {
                }
            }
            foreach (string x in valid)
            {
                try
                {
                    DateTime.ParseExact(x, "dd/mm/yyyy",
                                        CultureInfo.InvariantCulture);
                }
                catch
                {
                    throw new Exception("Valid date failed");
                }
            }
        }
    }

    static readonly char[] LowerBounds = "00/00/1000".ToCharArray();
    static readonly char[] UpperBounds = "19/39/2999".ToCharArray();
    static bool IsProbablyValidDate(string date)
    {
        if (date.Length != 10)
        {
            return false;
        }
        for (int i=0; i < date.Length; i++)
        {
            char c=date[i];
            if (c < LowerBounds[i] || c > UpperBounds[i])
            {
                return false;
            }
        }
        return true;
    }
}

The results on my laptop were:
TestRegex: 00:00:09.3437500
TestHardCoded: 00:00:04.3437500
TestNoPreCheck: 00:00:12.5156250

Changing the regex to require exactly two digits instead of 1 or 2 for
the month and day sped it up very slightly, but not really
significantly.

-- 
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too


Relevant Pages

  • Re: Check DateTime format
    ... "Jon Skeet " wrote: ... >> format. ... >> inefficient - catching the exception that is. ... static void Time ...
    (microsoft.public.dotnet.general)
  • Re: Fastest way to split a file by columns?
    ... ByteBuffer buf = new ByteBuffer; ... // process exception. ... static void initializeStream() throws Exception { ... if (pos>= buf.length) { ...
    (comp.lang.java.programmer)
  • Re: Threading
    ... now I see why I was sceptic about the thread abort exception. ... tried a similar scenario in the default domain. ... static void ThreadProc() ...
    (microsoft.public.dotnet.languages.csharp)
  • US vs UK datetime/string format failing on certain machines. Ideas
    ... I have a webform with a text box and a calendar control. ... or 6 lines of code just to format something from a text box ... String was not recognized as a valid DateTime. ... An unhandled exception occurred during the execution of the ...
    (microsoft.public.dotnet.framework.aspnet)
  • Re: Out of Memory Exception
    ... I catch the exception if it ... happens on the load and report an invalid file format.. ... > throws OutOfMemoryException if it does not recognize the image format. ...
    (microsoft.public.dotnet.languages.csharp)