Brian Mains
Brian Mains

Reputation: 50728

Which is Quicker: DateTime.TryParse or Regex

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.

Upvotes: 0

Views: 1725

Answers (6)

vc 74
vc 74

Reputation: 38179

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);
}

Upvotes: 0

immutabl
immutabl

Reputation: 6903

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;
    }

Upvotes: 2

Florian Doyon
Florian Doyon

Reputation: 4186

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?

Upvotes: 4

James Hill
James Hill

Reputation: 61872

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

Upvotes: 5

Ankur
Ankur

Reputation: 33657

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

Upvotes: 1

Tim Almond
Tim Almond

Reputation: 12726

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.

Upvotes: 2

Related Questions