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