0

In .NET, to determine whether a supplied string is a date, which one is quicker: using DateTime to cast it to a date, or using a regular expression to check the string for its validity as a date?

I just need to ensure the value supplied is a date, and I'm not doing anything with it afterward.

Thanks.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
  • 3
    Why dont you test it? Get a bunch of date strings with an approximate % of them invalid if you more or less know their frequency, throw them at both methods and pick the fastest. – InBetween Jun 22 '11 at 11:42
  • 2
    write a benchmark test (or tests). Seriously, it's the only way to be sure – Mitch Wheat Jun 22 '11 at 11:42
  • 1
    Which is Quicker: writing a 5 line benchmark, or posting on SO and waiting for someone else to do it. – Steven Robbins Jun 22 '11 at 11:46
  • 3
    This is quite a weird question from someone with `13.5K Rep` – V4Vendetta Jun 22 '11 at 11:51
  • When you say that you need to "ensure the value supplied is a date", do you mean a valid date or a value that is formatted like a date? It would have to be a pretty clever and complicated Regex that could determine whether a value is actually a valid date. – Daniel Pratt Jun 22 '11 at 11:57
  • How relevant can "quickest" be? This is usually done in the context of a UI, are you in a hurry to start waiting for the next Key? – H H Jun 22 '11 at 12:44
  • "and I'm not doing anything with it afterward" - then why bother? I would guess you will convert it to datetime at some point, and then the reliability of that is the only criterion. – H H Jun 22 '11 at 12:46
  • @Henk it's in a central validator component; the component to process it happens elsewhere, so I need to "scrub" the data here. – Brian Mains Jun 22 '11 at 13:48
  • @Brian: Then "quick" is hardly as relevant as "what format is accepted elsewhere" – H H Jun 22 '11 at 14:46
  • @Henk why wouldn't "quick" be relevant, I wouldn't say that I'm looking for the slowest process possible here... plus scalability would be a factor. – Brian Mains Jun 22 '11 at 14:55
  • @Daniel - yes I need to ensure a valid date structure. – Brian Mains Jun 22 '11 at 14:56
  • @Brian: where do the Date-strings come from? If it's file or Db I/O then this issue immediately shrinks to less than 1% of total. If it's a GUI, well .... – H H Jun 23 '11 at 06:41
  • @Henk DB or File IO yes. – Brian Mains Jun 23 '11 at 12:07
  • @Brian: then the speed of this test can hardly be relevant. Quicka&Easy to write might be. For quality, think about false positives/negatives. – H H Jun 23 '11 at 22:24
  • @Henk: I ended up going with DateTime.TryParse because of that; just wondering other's thoughts, and also because it may be used for validation of hundreds of thousands of dates in a batch process.... – Brian Mains Jun 24 '11 at 00:59

6 Answers6

5

My first question would be which is more expressive? Or Which is the most readable? In a case like this where performance gains would probably be negligible, I'd vote for the code that's the easiest to maintain/read.

EDIT

Found a decent, similar post. It's worth a read

Regex vs Tryparse what is the best in performance

Community
  • 1
  • 1
James Hill
  • 60,353
  • 20
  • 145
  • 161
4

A good regex should be much faster, and maybe consume less transient memory.

But here's the flipside of the coin:

You're pretty-much tied to only one time format, which means that internationalization will be painful, and that your users need to be educated to enter the date in the proper format.

Also, you will lose some date validation, say, how do you weed-out Feb 29th on non leap-years? April 31st?

Florian Doyon
  • 4,146
  • 1
  • 27
  • 37
2

The best thing to do would be to write a bit of test code for both and then run a loop to do it a million times. Without knowing the input, it would be hard to answer this (although my guess would be that TryParse would be quicker).

That said, the time difference on today's processors is probably irrelevant.

Tim Almond
  • 12,088
  • 10
  • 40
  • 50
2

UPDATE: When run in a fiddle TryParse is a lot quicker.

I ran a rudimentary test with 10000 items. It looks like Regexp is at least twice as fast as DateTime.Parse. See for yourself with the code below:

    private string[] arrDates = new string[10000];

    protected void Page_Load(object sender, EventArgs e)
    {
        initialise();

        RunRegexDemo();
        RunDateTimeParseDemo();

    }

    private void initialise()
    {
        Random ryear, rmonth, rdate;
        ryear = new Random();
        rmonth = new Random();
        rdate = new Random();
        int y, m, d;


        DateTime dt;

        for (int i = 0; i < arrDates.Length; i++)
        {
            y = 0;
            m = 0;
            d = 0;

            while (y < 1850)
            {
                y = ryear.Next(2050);
            }
            while (m < 1 || m > 12)
            {
                m = rmonth.Next(12);
            }
            while (d < 1 || d > 28)
            {
                d = rdate.Next(28);
            }

            dt = new DateTime(y, m, d);

            arrDates[i] = dt.ToString("yyyy-MM-dd");

            //lbl1.Text += "<br />" + arrDates[i];
        }

    }

    private void RunRegexDemo()
    {
        System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
        lbl1.Text+= "<h4>Starting Regex demo</h4>";
        string f;

        st.Start();

        foreach(string x in arrDates){
            f= "<br/>" + x + " is a valid date? = " + System.Text.RegularExpressions.Regex.IsMatch(x, @"^(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$");
        }

        st.Stop();
        lbl1.Text+= "<p>Ended RegEx demo. Elapsed time: " + st.ElapsedMilliseconds;
    }


    protected void RunDateTimeParseDemo(){
        System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
        lbl1.Text += "<h4>Starting DateTime.Parse demo</h4>";
        st.Start();
        DateTime dt;
        string f;
        foreach (string x in arrDates)
        {
            f = "<br/>" + x + " is a valid date? = " + DateTime.TryParse(x, out dt);
        }

        st.Stop();
        lbl1.Text += "<p>Ended TryParse demo. Elapsed time: " + st.ElapsedMilliseconds;
    }
immutabl
  • 6,857
  • 13
  • 45
  • 76
  • 1
    This example does recreate a new regex each time, which is quite innefective. Please check the fixed fiddle: https://dotnetfiddle.net/Apdhhe where cached regex wins everytime (jitted and unjitted) – Florian Doyon Jun 15 '16 at 09:26
  • 1
    Thanks! There was an error in your final string.format argument list which I've fixed. https://dotnetfiddle.net/cHcOR8 – immutabl Jun 15 '16 at 10:51
1

Regex seems to be faster in this case as Regex will only look for patterns where as DateTime parse will need to find the pattern as well as get values out of that pattern to create DateTime object

Ankur
  • 33,367
  • 2
  • 46
  • 72
0

Adding benchmarks comparing different methods of parsing a date/time in the ISO 8601 format:

  • ParseExact with a custom format
  • ParseExact with a standard format
  • Pre-compiled regex with date/time creation
  • Pre-compiled regex just validating the input string's format

Bottom line:

  • Regex is much faster to check the format without creating a date/time and does not allocate
  • Regex is slower to parse and build a date/time, allocates significantly more (and requires more code...)
  • Standard formats seem to allocate less
    // * Summary *
    
    BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19044.2965/21H2/November2021Update)
    12th Gen Intel Core i9-12900H, 1 CPU, 20 logical and 14 physical cores
    .NET SDK=7.0.302
      [Host]   : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
      .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
    
    Job=.NET 7.0  Runtime=.NET 7.0
Method Loops Mean Error StdDev Ratio RatioSD Gen0 Allocated
ParseExactCustomFormat 10 277.65 ns 3.148 ns 2.791 ns 1.00 0.00 0.0114 144 B
ParseExactStandardFormat 10 254.07 ns 0.490 ns 0.435 ns 0.92 0.01 0.0095 120 B
Regex 10 423.21 ns 4.026 ns 3.766 ns 1.52 0.02 0.0858 1080 B
RegexIsMatch 10 62.19 ns 0.322 ns 0.302 ns 0.22 0.00 - -
ParseExactCustomFormat 100 255.08 ns 1.176 ns 1.042 ns 1.00 0.00 0.0114 144 B
ParseExactStandardFormat 100 255.18 ns 1.358 ns 1.271 ns 1.00 0.01 0.0095 120 B
Regex 100 427.15 ns 8.149 ns 7.224 ns 1.67 0.03 0.0858 1080 B
RegexIsMatch 100 88.23 ns 1.920 ns 5.508 ns 0.32 0.05 - -
ParseExactCustomFormat 1000 255.65 ns 1.409 ns 1.249 ns 1.00 0.00 0.0114 144 B
ParseExactStandardFormat 1000 255.34 ns 0.689 ns 0.611 ns 1.00 0.00 0.0095 120 B
Regex 1000 437.88 ns 4.192 ns 3.273 ns 1.71 0.02 0.0858 1080 B
RegexIsMatch 1000 62.69 ns 0.744 ns 0.696 ns 0.25 0.00 - -
/// <summary>
/// Compares different methods of parsing a date/time in the ISO 8601 format.
/// </summary>
[SimpleJob(RuntimeMoniker.Net70)]
[MemoryDiagnoser]
public partial class DateTimeParseExactVsRegex
{
    [Params(10, 100, 1_000)]
    public int Loops { get; set; }

    /// <summary>
    /// Parses the input string by using <see cref="DateTime.ParseExact"/> 
    /// with a custom format.
    /// </summary>
    [Benchmark(Baseline = true)]
    public void ParseExactCustomFormat()
    {
        DateTime.ParseExact(_serializedDateTime, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK",
            CultureInfo.InvariantCulture,
            DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }

    private const string _serializedDateTime = "2002-08-11T10:11:12.0000000Z";

    /// <summary>
    /// Parses the input string by using <see cref="DateTime.ParseExact"/> 
    /// with a standard format.
    /// </summary>
    [Benchmark]
    public void ParseExactStandardFormat()
    {
        DateTime.ParseExact(_serializedDateTime, "O",
            CultureInfo.InvariantCulture,
            DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }

    /// <summary>
    /// Parses the input string by using a <see cref="Regex"/>.
    /// </summary>
    [Benchmark]
    public void Regex()
    {
        Match match = GetRegex().Match(_serializedDateTime);
        if (!match.Success)
        {
            throw new NotImplementedException();
        }

        int GetInt(string groupName)
        {
            ReadOnlySpan<char> yearAsString = match.Groups[groupName].ValueSpan;
            return int.Parse(yearAsString);
        }

        int year = GetInt("year"), month = GetInt("month"), day = GetInt("day");
        int hour = GetInt("hour"), minute = GetInt("minute"), second = GetInt("second");
        int subSecond = GetInt("subsecond");

        DateTime _ = new DateTime(year, month, day, hour, minute, second).AddTicks(subSecond);
    }

    [GeneratedRegex(@"^(?<year>\d{4})\-(?<month>\d{2})\-(?<day>\d{2})T(?<hour>\d{2})\:(?<minute>\d{2})\:(?<second>\d{2})\.(?<subsecond>\d{7})Z$")]
    private static partial System.Text.RegularExpressions.Regex GetRegex();

    /// <summary>
    /// Detects whether the input string matches the date/time format by using a <see cref="Regex"/>.
    /// </summary>
    [Benchmark]
    public void RegexIsMatch() => GetRegex().IsMatch(_serializedDateTime);
}
vc 74
  • 37,131
  • 7
  • 73
  • 89