Reputation: 50728
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
Reputation: 38179
Adding benchmarks comparing different methods of parsing a date/time in the ISO 8601 format:
Bottom line:
// * 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
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
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
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
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
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