1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 ////////////////////////////////////////////////////////////////////////////
7 // Purpose: Used by TimeSpan to parse a time interval string.
11 // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
12 // Not culture sensitive. Default format (and null/empty format string) map to this format.
14 // "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
15 // Only print what's needed. Localized (if you want Invariant, pass in Invariant).
16 // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
18 // "G": General format, long: [-]d':'hh':'mm':'ss'.'fffffff
19 // Always print days and 7 fractional digits. Localized (if you want Invariant, pass in Invariant).
20 // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
22 // * "TryParseTimeSpan" is the main method for Parse/TryParse
24 // - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
25 // - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
26 // - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
27 // The terminal states are attempted as follows:
28 // foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
31 // 3 numbers => h:m:s | d.h:m | h:m:.f
32 // 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
33 // 5 numbers => d.h:m:s.f
38 // * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
39 // * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
40 // methods that take a string[] of formats
42 // - For single-letter formats "TryParseTimeSpan" is called (see above)
43 // - For multi-letter formats "TryParseByFormat" is called
44 // - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
45 // which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
46 // operates on whole-tokens, ParseExact operates at the character-level. As such,
47 // TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
49 ////////////////////////////////////////////////////////////////////////////
51 using System
.Diagnostics
;
54 namespace System
.Globalization
56 internal static class TimeSpanParse
58 private const int MaxFractionDigits
= 7;
59 private const int MaxDays
= 10675199;
60 private const int MaxHours
= 23;
61 private const int MaxMinutes
= 59;
62 private const int MaxSeconds
= 59;
63 private const int MaxFraction
= 9999999;
66 private enum TimeSpanStandardStyles
: byte
68 // Standard Format Styles
70 Invariant
= 0x00000001, //Allow Invariant Culture
71 Localized
= 0x00000002, //Allow Localized Culture
72 RequireFull
= 0x00000004, //Require the input to be in DHMSF format
73 Any
= Invariant
| Localized
,
76 // TimeSpan Token Types
77 private enum TTT
: byte
79 None
= 0, // None of the TimeSpanToken fields are set
83 NumOverflow
= 4, // Number that overflowed
86 private ref struct TimeSpanToken
89 internal int _num
; // Store the number that we are parsing (if any)
90 internal int _zeroes
; // Store the number of leading zeroes (if any)
91 internal ReadOnlySpan
<char> _sep
; // Store the literal that we are parsing (if any)
93 public TimeSpanToken(TTT type
) : this(type
, 0, 0, default) { }
95 public TimeSpanToken(int number
) : this(TTT
.Num
, number
, 0, default) { }
97 public TimeSpanToken(int number
, int leadingZeroes
) : this(TTT
.Num
, number
, leadingZeroes
, default) { }
99 public TimeSpanToken(TTT type
, int number
, int leadingZeroes
, ReadOnlySpan
<char> separator
)
103 _zeroes
= leadingZeroes
;
107 public bool NormalizeAndValidateFraction()
109 Debug
.Assert(_ttt
== TTT
.Num
);
110 Debug
.Assert(_num
> -1);
115 if (_zeroes
== 0 && _num
> MaxFraction
)
118 int totalDigitsCount
= ((int) Math
.Floor(Math
.Log10(_num
))) + 1 + _zeroes
;
120 if (totalDigitsCount
== MaxFractionDigits
)
122 // Already normalized. no more action needed
123 // .9999999 normalize to 9,999,999 ticks
124 // .0000001 normalize to 1 ticks
128 if (totalDigitsCount
< MaxFractionDigits
)
130 // normalize the fraction to the 7-digits
131 // .999999 normalize to 9,999,990 ticks
132 // .99999 normalize to 9,999,900 ticks
133 // .000001 normalize to 10 ticks
134 // .1 normalize to 1,000,000 ticks
136 _num
*= (int) Pow10(MaxFractionDigits
- totalDigitsCount
);
140 // totalDigitsCount is greater then MaxFractionDigits, we'll need to do the rounding to 7-digits length
141 // .00000001 normalized to 0 ticks
142 // .00000005 normalized to 1 ticks
143 // .09999999 normalize to 1,000,000 ticks
144 // .099999999 normalize to 1,000,000 ticks
146 Debug
.Assert(_zeroes
> 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction
147 _num
= (int) Math
.Round((double)_num
/ Pow10(totalDigitsCount
- MaxFractionDigits
), MidpointRounding
.AwayFromZero
);
148 Debug
.Assert(_num
< MaxFraction
);
154 private ref struct TimeSpanTokenizer
156 private ReadOnlySpan
<char> _value
;
159 internal TimeSpanTokenizer(ReadOnlySpan
<char> input
) : this(input
, 0) { }
161 internal TimeSpanTokenizer(ReadOnlySpan
<char> input
, int startPosition
)
164 _pos
= startPosition
;
167 /// <summary>Returns the next token in the input string</summary>
168 /// <remarks>Used by the parsing routines that operate on standard-formats.</remarks>
169 internal TimeSpanToken
GetNextToken()
171 // Get the position of the next character to be processed. If there is no
172 // next character, we're at the end.
174 Debug
.Assert(pos
> -1);
175 if (pos
>= _value
.Length
)
177 return new TimeSpanToken(TTT
.End
);
180 // Now retrieve that character. If it's a digit, we're processing a number.
181 int num
= _value
[pos
] - '0';
187 // Read all leading zeroes.
192 if (++_pos
>= _value
.Length
|| (uint)(digit
= _value
[_pos
] - '0') > 9)
194 return new TimeSpanToken(TTT
.Num
, 0, zeroes
, default);
208 // Continue to read as long as we're reading digits.
209 while (++_pos
< _value
.Length
)
211 int digit
= _value
[_pos
] - '0';
217 num
= num
* 10 + digit
;
218 if ((num
& 0xF0000000) != 0) // Max limit we can support 268435455 which is FFFFFFF
220 return new TimeSpanToken(TTT
.NumOverflow
);
224 return new TimeSpanToken(TTT
.Num
, num
, zeroes
, default);
227 // Otherwise, we're processing a separator, and we've already processed the first
228 // character of it. Continue processing characters as long as they're not digits.
232 if (++_pos
>= _value
.Length
|| (uint)(_value
[_pos
] - '0') <= 9)
239 // Return the separator.
240 return new TimeSpanToken(TTT
.Sep
, 0, 0, _value
.Slice(pos
, length
));
243 internal bool EOL
=> _pos
>= (_value
.Length
- 1);
245 internal void BackOne()
247 if (_pos
> 0) --_pos
;
250 internal char NextChar
255 return (uint)pos
< (uint)_value
.Length
?
262 /// <summary>Stores intermediary parsing state for the standard formats.</summary>
263 private ref struct TimeSpanRawInfo
265 internal TimeSpanFormat
.FormatLiterals PositiveInvariant
=> TimeSpanFormat
.PositiveInvariantFormatLiterals
;
266 internal TimeSpanFormat
.FormatLiterals NegativeInvariant
=> TimeSpanFormat
.NegativeInvariantFormatLiterals
;
268 internal TimeSpanFormat
.FormatLiterals PositiveLocalized
274 _posLoc
= new TimeSpanFormat
.FormatLiterals();
275 _posLoc
.Init(_fullPosPattern
, false);
282 internal TimeSpanFormat
.FormatLiterals NegativeLocalized
288 _negLoc
= new TimeSpanFormat
.FormatLiterals();
289 _negLoc
.Init(_fullNegPattern
, false);
296 internal bool FullAppCompatMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
299 && _literals0
.EqualsOrdinal(pattern
.Start
)
300 && _literals1
.EqualsOrdinal(pattern
.DayHourSep
)
301 && _literals2
.EqualsOrdinal(pattern
.HourMinuteSep
)
302 && _literals3
.EqualsOrdinal(pattern
.AppCompatLiteral
)
303 && _literals4
.EqualsOrdinal(pattern
.End
);
305 internal bool PartialAppCompatMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
308 && _literals0
.EqualsOrdinal(pattern
.Start
)
309 && _literals1
.EqualsOrdinal(pattern
.HourMinuteSep
)
310 && _literals2
.EqualsOrdinal(pattern
.AppCompatLiteral
)
311 && _literals3
.EqualsOrdinal(pattern
.End
);
313 /// <summary>DHMSF (all values matched)</summary>
314 internal bool FullMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
315 _sepCount
== MaxLiteralTokens
316 && _numCount
== MaxNumericTokens
317 && _literals0
.EqualsOrdinal(pattern
.Start
)
318 && _literals1
.EqualsOrdinal(pattern
.DayHourSep
)
319 && _literals2
.EqualsOrdinal(pattern
.HourMinuteSep
)
320 && _literals3
.EqualsOrdinal(pattern
.MinuteSecondSep
)
321 && _literals4
.EqualsOrdinal(pattern
.SecondFractionSep
)
322 && _literals5
.EqualsOrdinal(pattern
.End
);
324 /// <summary>D (no hours, minutes, seconds, or fractions)</summary>
325 internal bool FullDMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
328 && _literals0
.EqualsOrdinal(pattern
.Start
)
329 && _literals1
.EqualsOrdinal(pattern
.End
);
331 /// <summary>HM (no days, seconds, or fractions)</summary>
332 internal bool FullHMMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
335 && _literals0
.EqualsOrdinal(pattern
.Start
)
336 && _literals1
.EqualsOrdinal(pattern
.HourMinuteSep
)
337 && _literals2
.EqualsOrdinal(pattern
.End
);
339 /// <summary>DHM (no seconds or fraction)</summary>
340 internal bool FullDHMMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
343 && _literals0
.EqualsOrdinal(pattern
.Start
)
344 && _literals1
.EqualsOrdinal(pattern
.DayHourSep
)
345 && _literals2
.EqualsOrdinal(pattern
.HourMinuteSep
)
346 && _literals3
.EqualsOrdinal(pattern
.End
);
348 /// <summary>HMS (no days or fraction)</summary>
349 internal bool FullHMSMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
352 && _literals0
.EqualsOrdinal(pattern
.Start
)
353 && _literals1
.EqualsOrdinal(pattern
.HourMinuteSep
)
354 && _literals2
.EqualsOrdinal(pattern
.MinuteSecondSep
)
355 && _literals3
.EqualsOrdinal(pattern
.End
);
357 /// <summary>DHMS (no fraction)</summary>
358 internal bool FullDHMSMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
361 && _literals0
.EqualsOrdinal(pattern
.Start
)
362 && _literals1
.EqualsOrdinal(pattern
.DayHourSep
)
363 && _literals2
.EqualsOrdinal(pattern
.HourMinuteSep
)
364 && _literals3
.EqualsOrdinal(pattern
.MinuteSecondSep
)
365 && _literals4
.EqualsOrdinal(pattern
.End
);
367 /// <summary>HMSF (no days)</summary>
368 internal bool FullHMSFMatch(TimeSpanFormat
.FormatLiterals pattern
) =>
371 && _literals0
.EqualsOrdinal(pattern
.Start
)
372 && _literals1
.EqualsOrdinal(pattern
.HourMinuteSep
)
373 && _literals2
.EqualsOrdinal(pattern
.MinuteSecondSep
)
374 && _literals3
.EqualsOrdinal(pattern
.SecondFractionSep
)
375 && _literals4
.EqualsOrdinal(pattern
.End
);
377 internal TTT _lastSeenTTT
;
378 internal int _tokenCount
;
379 internal int _sepCount
;
380 internal int _numCount
;
382 private TimeSpanFormat
.FormatLiterals _posLoc
;
383 private TimeSpanFormat
.FormatLiterals _negLoc
;
384 private bool _posLocInit
;
385 private bool _negLocInit
;
386 private string _fullPosPattern
;
387 private string _fullNegPattern
;
389 private const int MaxTokens
= 11;
390 private const int MaxLiteralTokens
= 6;
391 private const int MaxNumericTokens
= 5;
393 internal TimeSpanToken _numbers0
, _numbers1
, _numbers2
, _numbers3
, _numbers4
; // MaxNumbericTokens = 5
394 internal ReadOnlySpan
<char> _literals0
, _literals1
, _literals2
, _literals3
, _literals4
, _literals5
; // MaxLiteralTokens=6
396 internal void Init(DateTimeFormatInfo dtfi
)
398 Debug
.Assert(dtfi
!= null);
400 _lastSeenTTT
= TTT
.None
;
405 _fullPosPattern
= dtfi
.FullTimeSpanPositivePattern
;
406 _fullNegPattern
= dtfi
.FullTimeSpanNegativePattern
;
411 internal bool ProcessToken(ref TimeSpanToken tok
, ref TimeSpanResult result
)
416 if ((_tokenCount
== 0 && !AddSep(default, ref result
)) || !AddNum(tok
, ref result
))
423 if (!AddSep(tok
._sep
, ref result
))
429 case TTT
.NumOverflow
:
430 return result
.SetOverflowFailure();
433 // Some unknown token or a repeat token type in the input
434 return result
.SetBadTimeSpanFailure();
437 _lastSeenTTT
= tok
._ttt
;
438 Debug
.Assert(_tokenCount
== (_sepCount
+ _numCount
), "tokenCount == (SepCount + NumCount)");
442 private bool AddSep(ReadOnlySpan
<char> sep
, ref TimeSpanResult result
)
444 if (_sepCount
>= MaxLiteralTokens
|| _tokenCount
>= MaxTokens
)
446 return result
.SetBadTimeSpanFailure();
451 case 0: _literals0
= sep
; break;
452 case 1: _literals1
= sep
; break;
453 case 2: _literals2
= sep
; break;
454 case 3: _literals3
= sep
; break;
455 case 4: _literals4
= sep
; break;
456 default: _literals5
= sep
; break;
462 private bool AddNum(TimeSpanToken num
, ref TimeSpanResult result
)
464 if (_numCount
>= MaxNumericTokens
|| _tokenCount
>= MaxTokens
)
466 return result
.SetBadTimeSpanFailure();
471 case 0: _numbers0
= num
; break;
472 case 1: _numbers1
= num
; break;
473 case 2: _numbers2
= num
; break;
474 case 3: _numbers3
= num
; break;
475 default: _numbers4
= num
; break;
483 /// <summary>Store the result of the parsing.</summary>
484 private ref struct TimeSpanResult
486 internal TimeSpan parsedTimeSpan
;
487 private readonly bool _throwOnFailure
;
488 private readonly ReadOnlySpan
<char> _originalTimeSpanString
;
490 internal TimeSpanResult(bool throwOnFailure
, ReadOnlySpan
<char> originalTimeSpanString
)
492 parsedTimeSpan
= default;
493 _throwOnFailure
= throwOnFailure
;
494 _originalTimeSpanString
= originalTimeSpanString
;
497 internal bool SetNoFormatSpecifierFailure()
499 if (!_throwOnFailure
)
504 throw new FormatException(SR
.Format_NoFormatSpecifier
);
507 internal bool SetBadQuoteFailure(char failingCharacter
)
509 if (!_throwOnFailure
)
514 throw new FormatException(SR
.Format(SR
.Format_BadQuote
, failingCharacter
));
517 internal bool SetInvalidStringFailure()
519 if (!_throwOnFailure
)
524 throw new FormatException(SR
.Format_InvalidString
);
527 internal bool SetArgumentNullFailure(string argumentName
)
529 if (!_throwOnFailure
)
534 Debug
.Assert(argumentName
!= null);
535 throw new ArgumentNullException(argumentName
, SR
.ArgumentNull_String
);
538 internal bool SetOverflowFailure()
540 if (!_throwOnFailure
)
545 throw new OverflowException(SR
.Format(SR
.Overflow_TimeSpanElementTooLarge
, new string(_originalTimeSpanString
)));
548 internal bool SetBadTimeSpanFailure()
550 if (!_throwOnFailure
)
555 throw new FormatException(SR
.Format(SR
.Format_BadTimeSpan
, new string(_originalTimeSpanString
)));
558 internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter
= null)
560 if (!_throwOnFailure
)
565 throw new FormatException(SR
.Format(SR
.Format_BadFormatSpecifier
, formatSpecifierCharacter
));
569 internal static long Pow10(int pow
)
577 case 4: return 10000;
578 case 5: return 100000;
579 case 6: return 1000000;
580 case 7: return 10000000;
581 default: return (long)Math
.Pow(10, pow
);
585 private static bool TryTimeToTicks(bool positive
, TimeSpanToken days
, TimeSpanToken hours
, TimeSpanToken minutes
, TimeSpanToken seconds
, TimeSpanToken fraction
, out long result
)
587 if (days
._num
> MaxDays
||
588 hours
._num
> MaxHours
||
589 minutes
._num
> MaxMinutes
||
590 seconds
._num
> MaxSeconds
||
591 !fraction
.NormalizeAndValidateFraction())
597 long ticks
= ((long)days
._num
* 3600 * 24 + (long)hours
._num
* 3600 + (long)minutes
._num
* 60 + seconds
._num
) * 1000;
598 if (ticks
> InternalGlobalizationHelper
.MaxMilliSeconds
|| ticks
< InternalGlobalizationHelper
.MinMilliSeconds
)
604 result
= ticks
* TimeSpan
.TicksPerMillisecond
+ fraction
._num
;
605 if (positive
&& result
< 0)
614 internal static TimeSpan
Parse(ReadOnlySpan
<char> input
, IFormatProvider
? formatProvider
)
616 var parseResult
= new TimeSpanResult(throwOnFailure
: true, originalTimeSpanString
: input
);
617 bool success
= TryParseTimeSpan(input
, TimeSpanStandardStyles
.Any
, formatProvider
, ref parseResult
);
618 Debug
.Assert(success
, "Should have thrown on failure");
619 return parseResult
.parsedTimeSpan
;
622 internal static bool TryParse(ReadOnlySpan
<char> input
, IFormatProvider
? formatProvider
, out TimeSpan result
)
624 var parseResult
= new TimeSpanResult(throwOnFailure
: false, originalTimeSpanString
: input
);
626 if (TryParseTimeSpan(input
, TimeSpanStandardStyles
.Any
, formatProvider
, ref parseResult
))
628 result
= parseResult
.parsedTimeSpan
;
636 internal static TimeSpan
ParseExact(ReadOnlySpan
<char> input
, ReadOnlySpan
<char> format
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
)
638 var parseResult
= new TimeSpanResult(throwOnFailure
: true, originalTimeSpanString
: input
);
639 bool success
= TryParseExactTimeSpan(input
, format
, formatProvider
, styles
, ref parseResult
);
640 Debug
.Assert(success
, "Should have thrown on failure");
641 return parseResult
.parsedTimeSpan
;
644 internal static bool TryParseExact(ReadOnlySpan
<char> input
, ReadOnlySpan
<char> format
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
, out TimeSpan result
)
646 var parseResult
= new TimeSpanResult(throwOnFailure
: false, originalTimeSpanString
: input
);
648 if (TryParseExactTimeSpan(input
, format
, formatProvider
, styles
, ref parseResult
))
650 result
= parseResult
.parsedTimeSpan
;
658 internal static TimeSpan
ParseExactMultiple(ReadOnlySpan
<char> input
, string?[] formats
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
)
660 var parseResult
= new TimeSpanResult(throwOnFailure
: true, originalTimeSpanString
: input
);
661 bool success
= TryParseExactMultipleTimeSpan(input
, formats
, formatProvider
, styles
, ref parseResult
);
662 Debug
.Assert(success
, "Should have thrown on failure");
663 return parseResult
.parsedTimeSpan
;
666 internal static bool TryParseExactMultiple(ReadOnlySpan
<char> input
, string?[]? formats
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
, out TimeSpan result
)
668 var parseResult
= new TimeSpanResult(throwOnFailure
: false, originalTimeSpanString
: input
);
670 if (TryParseExactMultipleTimeSpan(input
, formats
, formatProvider
, styles
, ref parseResult
))
672 result
= parseResult
.parsedTimeSpan
;
680 /// <summary>Common private Parse method called by both Parse and TryParse.</summary>
681 private static bool TryParseTimeSpan(ReadOnlySpan
<char> input
, TimeSpanStandardStyles style
, IFormatProvider
? formatProvider
, ref TimeSpanResult result
)
683 input
= input
.Trim();
686 return result
.SetBadTimeSpanFailure();
689 var tokenizer
= new TimeSpanTokenizer(input
);
691 var raw
= new TimeSpanRawInfo();
692 raw
.Init(DateTimeFormatInfo
.GetInstance(formatProvider
));
694 TimeSpanToken tok
= tokenizer
.GetNextToken();
696 // The following loop will break out when we reach the end of the str or
697 // when we can determine that the input is invalid.
698 while (tok
._ttt
!= TTT
.End
)
700 if (!raw
.ProcessToken(ref tok
, ref result
))
702 return result
.SetBadTimeSpanFailure();
704 tok
= tokenizer
.GetNextToken();
706 Debug
.Assert(tokenizer
.EOL
);
708 if (!ProcessTerminalState(ref raw
, style
, ref result
))
710 return result
.SetBadTimeSpanFailure();
717 /// Validate the terminal state of a standard format parse.
718 /// Sets result.parsedTimeSpan on success.
719 /// Calculates the resultant TimeSpan from the TimeSpanRawInfo.
722 /// try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
723 /// 1) Verify Start matches
724 /// 2) Verify End matches
727 /// 3 numbers => h:m:s | d.h:m | h:m:.f
728 /// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
729 /// 5 numbers => d.h:m:s.f
731 private static bool ProcessTerminalState(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
733 if (raw
._lastSeenTTT
== TTT
.Num
)
735 TimeSpanToken tok
= new TimeSpanToken();
737 if (!raw
.ProcessToken(ref tok
, ref result
))
739 return result
.SetBadTimeSpanFailure();
743 switch (raw
._numCount
)
745 case 1: return ProcessTerminal_D(ref raw
, style
, ref result
);
746 case 2: return ProcessTerminal_HM(ref raw
, style
, ref result
);
747 case 3: return ProcessTerminal_HM_S_D(ref raw
, style
, ref result
);
748 case 4: return ProcessTerminal_HMS_F_D(ref raw
, style
, ref result
);
749 case 5: return ProcessTerminal_DHMSF(ref raw
, style
, ref result
);
750 default: return result
.SetBadTimeSpanFailure();
754 /// <summary>Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.</summary>
755 private static bool ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
757 if (raw
._sepCount
!= 6)
759 return result
.SetBadTimeSpanFailure();
761 Debug
.Assert(raw
._numCount
== 5);
763 bool inv
= (style
& TimeSpanStandardStyles
.Invariant
) != 0;
764 bool loc
= (style
& TimeSpanStandardStyles
.Localized
) != 0;
765 bool positive
= false;
770 if (raw
.FullMatch(raw
.PositiveInvariant
))
775 if (!match
&& raw
.FullMatch(raw
.NegativeInvariant
))
784 if (!match
&& raw
.FullMatch(raw
.PositiveLocalized
))
789 if (!match
&& raw
.FullMatch(raw
.NegativeLocalized
))
800 if (!TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, raw
._numbers4
, out ticks
))
802 return result
.SetOverflowFailure();
810 return result
.SetOverflowFailure();
814 result
.parsedTimeSpan
= new TimeSpan(ticks
);
818 return result
.SetBadTimeSpanFailure();
823 /// Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds",
824 /// or "Days.Hours:Minutes:.Fraction" terminal case.
826 private static bool ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
828 if (raw
._sepCount
!= 5 || (style
& TimeSpanStandardStyles
.RequireFull
) != 0)
830 return result
.SetBadTimeSpanFailure();
832 Debug
.Assert(raw
._numCount
== 4);
834 bool inv
= ((style
& TimeSpanStandardStyles
.Invariant
) != 0);
835 bool loc
= ((style
& TimeSpanStandardStyles
.Localized
) != 0);
838 bool positive
= false, match
= false, overflow
= false;
839 var zero
= new TimeSpanToken(0);
843 if (raw
.FullHMSFMatch(raw
.PositiveInvariant
))
846 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, out ticks
);
847 overflow
= overflow
|| !match
;
850 if (!match
&& raw
.FullDHMSMatch(raw
.PositiveInvariant
))
853 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, zero
, out ticks
);
854 overflow
= overflow
|| !match
;
857 if (!match
&& raw
.FullAppCompatMatch(raw
.PositiveInvariant
))
860 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, raw
._numbers3
, out ticks
);
861 overflow
= overflow
|| !match
;
864 if (!match
&& raw
.FullHMSFMatch(raw
.NegativeInvariant
))
867 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, out ticks
);
868 overflow
= overflow
|| !match
;
871 if (!match
&& raw
.FullDHMSMatch(raw
.NegativeInvariant
))
874 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, zero
, out ticks
);
875 overflow
= overflow
|| !match
;
878 if (!match
&& raw
.FullAppCompatMatch(raw
.NegativeInvariant
))
881 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, raw
._numbers3
, out ticks
);
882 overflow
= overflow
|| !match
;
888 if (!match
&& raw
.FullHMSFMatch(raw
.PositiveLocalized
))
891 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, out ticks
);
892 overflow
= overflow
|| !match
;
895 if (!match
&& raw
.FullDHMSMatch(raw
.PositiveLocalized
))
898 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, zero
, out ticks
);
899 overflow
= overflow
|| !match
;
902 if (!match
&& raw
.FullAppCompatMatch(raw
.PositiveLocalized
))
905 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, raw
._numbers3
, out ticks
);
906 overflow
= overflow
|| !match
;
909 if (!match
&& raw
.FullHMSFMatch(raw
.NegativeLocalized
))
912 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, out ticks
);
913 overflow
= overflow
|| !match
;
916 if (!match
&& raw
.FullDHMSMatch(raw
.NegativeLocalized
))
919 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, raw
._numbers3
, zero
, out ticks
);
920 overflow
= overflow
|| !match
;
923 if (!match
&& raw
.FullAppCompatMatch(raw
.NegativeLocalized
))
926 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, raw
._numbers3
, out ticks
);
927 overflow
= overflow
|| !match
;
938 return result
.SetOverflowFailure();
942 result
.parsedTimeSpan
= new TimeSpan(ticks
);
947 result
.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
948 result
.SetBadTimeSpanFailure(); // we couldn't find a thing
951 /// <summary>Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case.</summary>
952 private static bool ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
954 if (raw
._sepCount
!= 4 || (style
& TimeSpanStandardStyles
.RequireFull
) != 0)
956 return result
.SetBadTimeSpanFailure();
958 Debug
.Assert(raw
._numCount
== 3);
960 bool inv
= ((style
& TimeSpanStandardStyles
.Invariant
) != 0);
961 bool loc
= ((style
& TimeSpanStandardStyles
.Localized
) != 0);
963 bool positive
= false, match
= false, overflow
= false;
964 var zero
= new TimeSpanToken(0);
969 if (raw
.FullHMSMatch(raw
.PositiveInvariant
))
972 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, out ticks
);
973 overflow
= overflow
|| !match
;
976 if (!match
&& raw
.FullDHMMatch(raw
.PositiveInvariant
))
979 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, zero
, out ticks
);
980 overflow
= overflow
|| !match
;
983 if (!match
&& raw
.PartialAppCompatMatch(raw
.PositiveInvariant
))
986 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, zero
, raw
._numbers2
, out ticks
);
987 overflow
= overflow
|| !match
;
990 if (!match
&& raw
.FullHMSMatch(raw
.NegativeInvariant
))
993 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, out ticks
);
994 overflow
= overflow
|| !match
;
997 if (!match
&& raw
.FullDHMMatch(raw
.NegativeInvariant
))
1000 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, zero
, out ticks
);
1001 overflow
= overflow
|| !match
;
1004 if (!match
&& raw
.PartialAppCompatMatch(raw
.NegativeInvariant
))
1007 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, zero
, raw
._numbers2
, out ticks
);
1008 overflow
= overflow
|| !match
;
1014 if (!match
&& raw
.FullHMSMatch(raw
.PositiveLocalized
))
1017 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, out ticks
);
1018 overflow
= overflow
|| !match
;
1021 if (!match
&& raw
.FullDHMMatch(raw
.PositiveLocalized
))
1024 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, zero
, out ticks
);
1025 overflow
= overflow
|| !match
;
1028 if (!match
&& raw
.PartialAppCompatMatch(raw
.PositiveLocalized
))
1031 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, zero
, raw
._numbers2
, out ticks
);
1032 overflow
= overflow
|| !match
;
1035 if (!match
&& raw
.FullHMSMatch(raw
.NegativeLocalized
))
1038 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, out ticks
);
1039 overflow
= overflow
|| !match
;
1042 if (!match
&& raw
.FullDHMMatch(raw
.NegativeLocalized
))
1045 match
= TryTimeToTicks(positive
, raw
._numbers0
, raw
._numbers1
, raw
._numbers2
, zero
, zero
, out ticks
);
1046 overflow
= overflow
|| !match
;
1049 if (!match
&& raw
.PartialAppCompatMatch(raw
.NegativeLocalized
))
1052 match
= TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, zero
, raw
._numbers2
, out ticks
);
1053 overflow
= overflow
|| !match
;
1064 return result
.SetOverflowFailure();
1068 result
.parsedTimeSpan
= new TimeSpan(ticks
);
1073 result
.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
1074 result
.SetBadTimeSpanFailure(); // we couldn't find a thing
1077 /// <summary>Validate the 2-number "Hours:Minutes" terminal case.</summary>
1078 private static bool ProcessTerminal_HM(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
1080 if (raw
._sepCount
!= 3 || (style
& TimeSpanStandardStyles
.RequireFull
) != 0)
1082 return result
.SetBadTimeSpanFailure();
1084 Debug
.Assert(raw
._numCount
== 2);
1086 bool inv
= ((style
& TimeSpanStandardStyles
.Invariant
) != 0);
1087 bool loc
= ((style
& TimeSpanStandardStyles
.Localized
) != 0);
1089 bool positive
= false, match
= false;
1093 if (raw
.FullHMMatch(raw
.PositiveInvariant
))
1099 if (!match
&& raw
.FullHMMatch(raw
.NegativeInvariant
))
1108 if (!match
&& raw
.FullHMMatch(raw
.PositiveLocalized
))
1114 if (!match
&& raw
.FullHMMatch(raw
.NegativeLocalized
))
1124 var zero
= new TimeSpanToken(0);
1126 if (!TryTimeToTicks(positive
, zero
, raw
._numbers0
, raw
._numbers1
, zero
, zero
, out ticks
))
1128 return result
.SetOverflowFailure();
1136 return result
.SetOverflowFailure();
1140 result
.parsedTimeSpan
= new TimeSpan(ticks
);
1144 return result
.SetBadTimeSpanFailure();
1147 /// <summary>Validate the 1-number "Days" terminal case.</summary>
1148 private static bool ProcessTerminal_D(ref TimeSpanRawInfo raw
, TimeSpanStandardStyles style
, ref TimeSpanResult result
)
1150 if (raw
._sepCount
!= 2 || (style
& TimeSpanStandardStyles
.RequireFull
) != 0)
1152 return result
.SetBadTimeSpanFailure();
1154 Debug
.Assert(raw
._numCount
== 1);
1156 bool inv
= ((style
& TimeSpanStandardStyles
.Invariant
) != 0);
1157 bool loc
= ((style
& TimeSpanStandardStyles
.Localized
) != 0);
1159 bool positive
= false, match
= false;
1163 if (raw
.FullDMatch(raw
.PositiveInvariant
))
1169 if (!match
&& raw
.FullDMatch(raw
.NegativeInvariant
))
1178 if (!match
&& raw
.FullDMatch(raw
.PositiveLocalized
))
1184 if (!match
&& raw
.FullDMatch(raw
.NegativeLocalized
))
1194 var zero
= new TimeSpanToken(0);
1196 if (!TryTimeToTicks(positive
, raw
._numbers0
, zero
, zero
, zero
, zero
, out ticks
))
1198 return result
.SetOverflowFailure();
1206 return result
.SetOverflowFailure();
1210 result
.parsedTimeSpan
= new TimeSpan(ticks
);
1214 return result
.SetBadTimeSpanFailure();
1217 /// <summary>Common private ParseExact method called by both ParseExact and TryParseExact.</summary>
1218 private static bool TryParseExactTimeSpan(ReadOnlySpan
<char> input
, ReadOnlySpan
<char> format
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
, ref TimeSpanResult result
)
1220 if (format
.Length
== 0)
1222 return result
.SetBadFormatSpecifierFailure();
1225 if (format
.Length
== 1)
1232 return TryParseTimeSpanConstant(input
, ref result
); // fast path for legacy style TimeSpan formats.
1235 return TryParseTimeSpan(input
, TimeSpanStandardStyles
.Localized
, formatProvider
, ref result
);
1238 return TryParseTimeSpan(input
, TimeSpanStandardStyles
.Localized
| TimeSpanStandardStyles
.RequireFull
, formatProvider
, ref result
);
1241 return result
.SetBadFormatSpecifierFailure(format
[0]);
1245 return TryParseByFormat(input
, format
, styles
, ref result
);
1248 /// <summary>Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.</summary>
1249 private static bool TryParseByFormat(ReadOnlySpan
<char> input
, ReadOnlySpan
<char> format
, TimeSpanStyles styles
, ref TimeSpanResult result
)
1251 bool seenDD
= false; // already processed days?
1252 bool seenHH
= false; // already processed hours?
1253 bool seenMM
= false; // already processed minutes?
1254 bool seenSS
= false; // already processed seconds?
1255 bool seenFF
= false; // already processed fraction?
1257 int dd
= 0; // parsed days
1258 int hh
= 0; // parsed hours
1259 int mm
= 0; // parsed minutes
1260 int ss
= 0; // parsed seconds
1261 int leadingZeroes
= 0; // number of leading zeroes in the parsed fraction
1262 int ff
= 0; // parsed fraction
1263 int i
= 0; // format string position
1264 int tokenLen
= 0; // length of current format token, used to update index 'i'
1266 var tokenizer
= new TimeSpanTokenizer(input
, -1);
1268 while (i
< format
.Length
)
1270 char ch
= format
[i
];
1275 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1276 if (tokenLen
> 2 || seenHH
|| !ParseExactDigits(ref tokenizer
, tokenLen
, out hh
))
1278 return result
.SetInvalidStringFailure();
1284 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1285 if (tokenLen
> 2 || seenMM
|| !ParseExactDigits(ref tokenizer
, tokenLen
, out mm
))
1287 return result
.SetInvalidStringFailure();
1293 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1294 if (tokenLen
> 2 || seenSS
|| !ParseExactDigits(ref tokenizer
, tokenLen
, out ss
))
1296 return result
.SetInvalidStringFailure();
1302 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1303 if (tokenLen
> DateTimeFormat
.MaxSecondsFractionDigits
|| seenFF
|| !ParseExactDigits(ref tokenizer
, tokenLen
, tokenLen
, out leadingZeroes
, out ff
))
1305 return result
.SetInvalidStringFailure();
1311 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1312 if (tokenLen
> DateTimeFormat
.MaxSecondsFractionDigits
|| seenFF
)
1314 return result
.SetInvalidStringFailure();
1316 ParseExactDigits(ref tokenizer
, tokenLen
, tokenLen
, out leadingZeroes
, out ff
);
1321 tokenLen
= DateTimeFormat
.ParseRepeatPattern(format
, i
, ch
);
1323 if (tokenLen
> 8 || seenDD
|| !ParseExactDigits(ref tokenizer
, (tokenLen
< 2) ? 1 : tokenLen
, (tokenLen
< 2) ? 8 : tokenLen
, out tmp
, out dd
))
1325 return result
.SetInvalidStringFailure();
1332 StringBuilder enquotedString
= StringBuilderCache
.Acquire();
1333 if (!DateTimeParse
.TryParseQuoteString(format
, i
, enquotedString
, out tokenLen
))
1335 StringBuilderCache
.Release(enquotedString
);
1336 return result
.SetBadQuoteFailure(ch
);
1338 if (!ParseExactLiteral(ref tokenizer
, enquotedString
))
1340 StringBuilderCache
.Release(enquotedString
);
1341 return result
.SetInvalidStringFailure();
1343 StringBuilderCache
.Release(enquotedString
);
1347 // Optional format character.
1348 // For example, format string "%d" will print day
1349 // Most of the cases, "%" can be ignored.
1350 nextFormatChar
= DateTimeFormat
.ParseNextChar(format
, i
);
1352 // nextFormatChar will be -1 if we already reach the end of the format string.
1353 // Besides, we will not allow "%%" appear in the pattern.
1354 if (nextFormatChar
>= 0 && nextFormatChar
!= '%')
1356 tokenLen
= 1; // skip the '%' and process the format character
1361 // This means that '%' is at the end of the format string or
1362 // "%%" appears in the format string.
1363 return result
.SetInvalidStringFailure();
1367 // Escaped character. Can be used to insert character into the format string.
1368 // For example, "\d" will insert the character 'd' into the string.
1370 nextFormatChar
= DateTimeFormat
.ParseNextChar(format
, i
);
1371 if (nextFormatChar
>= 0 && tokenizer
.NextChar
== (char)nextFormatChar
)
1377 // This means that '\' is at the end of the format string or the literal match failed.
1378 return result
.SetInvalidStringFailure();
1383 return result
.SetInvalidStringFailure();
1392 // the custom format didn't consume the entire input
1393 return result
.SetBadTimeSpanFailure();
1396 bool positive
= (styles
& TimeSpanStyles
.AssumeNegative
) == 0;
1397 if (TryTimeToTicks(positive
, new TimeSpanToken(dd
),
1398 new TimeSpanToken(hh
),
1399 new TimeSpanToken(mm
),
1400 new TimeSpanToken(ss
),
1401 new TimeSpanToken(ff
, leadingZeroes
),
1409 result
.parsedTimeSpan
= new TimeSpan(ticks
);
1414 return result
.SetOverflowFailure();
1418 private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer
, int minDigitLength
, out int result
)
1422 int maxDigitLength
= (minDigitLength
== 1) ? 2 : minDigitLength
;
1423 return ParseExactDigits(ref tokenizer
, minDigitLength
, maxDigitLength
, out zeroes
, out result
);
1426 private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer
, int minDigitLength
, int maxDigitLength
, out int zeroes
, out int result
)
1428 int tmpResult
= 0, tmpZeroes
= 0;
1430 int tokenLength
= 0;
1431 while (tokenLength
< maxDigitLength
)
1433 char ch
= tokenizer
.NextChar
;
1434 if (ch
< '0' || ch
> '9')
1436 tokenizer
.BackOne();
1440 tmpResult
= tmpResult
* 10 + (ch
- '0');
1441 if (tmpResult
== 0) tmpZeroes
++;
1447 return tokenLength
>= minDigitLength
;
1450 private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer
, StringBuilder enquotedString
)
1452 for (int i
= 0; i
< enquotedString
.Length
; i
++)
1454 if (enquotedString
[i
] != tokenizer
.NextChar
)
1464 /// Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
1465 /// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
1467 private static bool TryParseTimeSpanConstant(ReadOnlySpan
<char> input
, ref TimeSpanResult result
) =>
1468 new StringParser().TryParse(input
, ref result
);
1470 private ref struct StringParser
1472 private ReadOnlySpan
<char> _str
;
1477 internal void NextChar()
1489 internal char NextNonDigit()
1495 if (ch
< '0' || ch
> '9') return ch
;
1502 internal bool TryParse(ReadOnlySpan
<char> input
, ref TimeSpanResult result
)
1504 result
.parsedTimeSpan
= default;
1507 _len
= input
.Length
;
1512 bool negative
= false;
1520 if (NextNonDigit() == ':')
1522 if (!ParseTime(out time
, ref result
))
1530 if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL
/ TimeSpan
.TicksPerDay
), out days
, ref result
))
1535 time
= days
* TimeSpan
.TicksPerDay
;
1541 if (!ParseTime(out remainingTime
, ref result
))
1545 time
+= remainingTime
;
1555 return result
.SetOverflowFailure();
1562 return result
.SetOverflowFailure();
1570 return result
.SetBadTimeSpanFailure();
1573 result
.parsedTimeSpan
= new TimeSpan(time
);
1577 internal bool ParseInt(int max
, out int i
, ref TimeSpanResult result
)
1581 while (_ch
>= '0' && _ch
<= '9')
1583 if ((i
& 0xF0000000) != 0)
1585 return result
.SetOverflowFailure();
1588 i
= i
* 10 + _ch
- '0';
1591 return result
.SetOverflowFailure();
1599 return result
.SetBadTimeSpanFailure();
1604 return result
.SetOverflowFailure();
1610 internal bool ParseTime(out long time
, ref TimeSpanResult result
)
1615 if (!ParseInt(23, out unit
, ref result
))
1620 time
= unit
* TimeSpan
.TicksPerHour
;
1623 return result
.SetBadTimeSpanFailure();
1628 if (!ParseInt(59, out unit
, ref result
))
1633 time
+= unit
* TimeSpan
.TicksPerMinute
;
1639 // allow seconds with the leading zero
1642 if (!ParseInt(59, out unit
, ref result
))
1646 time
+= unit
* TimeSpan
.TicksPerSecond
;
1652 int f
= (int)TimeSpan
.TicksPerSecond
;
1653 while (f
> 1 && _ch
>= '0' && _ch
<= '9')
1656 time
+= (_ch
- '0') * f
;
1665 internal void SkipBlanks()
1667 while (_ch
== ' ' || _ch
== '\t') NextChar();
1671 /// <summary>Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple.</summary>
1672 private static bool TryParseExactMultipleTimeSpan(ReadOnlySpan
<char> input
, string?[]? formats
, IFormatProvider
? formatProvider
, TimeSpanStyles styles
, ref TimeSpanResult result
)
1674 if (formats
== null)
1676 return result
.SetArgumentNullFailure(nameof(formats
));
1679 if (input
.Length
== 0)
1681 return result
.SetBadTimeSpanFailure();
1684 if (formats
.Length
== 0)
1686 return result
.SetNoFormatSpecifierFailure();
1689 // Do a loop through the provided formats and see if we can parse succesfully in
1690 // one of the formats.
1691 for (int i
= 0; i
< formats
.Length
; i
++)
1693 // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644)
1694 if (formats
[i
] == null || formats
[i
]!.Length
== 0)
1696 return result
.SetBadFormatSpecifierFailure();
1699 // Create a new non-throwing result each time to ensure the runs are independent.
1700 TimeSpanResult innerResult
= new TimeSpanResult(throwOnFailure
: false, originalTimeSpanString
: input
);
1702 if (TryParseExactTimeSpan(input
, formats
[i
], formatProvider
, styles
, ref innerResult
))
1704 result
.parsedTimeSpan
= innerResult
.parsedTimeSpan
;
1709 return result
.SetBadTimeSpanFailure();