Contribute to IDE0044 (make field readonly)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Globalization / TimeSpanParse.cs
blob7dfac05aaddc4ef5483dabcac37c87eed166bbb2
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 ////////////////////////////////////////////////////////////////////////////
6 //
7 // Purpose: Used by TimeSpan to parse a time interval string.
8 //
9 // Standard Format:
10 // -=-=-=-=-=-=-=-
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
29 // 1 number => d
30 // 2 numbers => h:m
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
35 // Custom Format:
36 // -=-=-=-=-=-=-=
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;
52 using System.Text;
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;
65 [Flags]
66 private enum TimeSpanStandardStyles : byte
68 // Standard Format Styles
69 None = 0x00000000,
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
80 End = 1, // '\0'
81 Num = 2, // Number
82 Sep = 3, // literal
83 NumOverflow = 4, // Number that overflowed
86 private ref struct TimeSpanToken
88 internal TTT _ttt;
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)
101 _ttt = type;
102 _num = number;
103 _zeroes = leadingZeroes;
104 _sep = separator;
107 public bool NormalizeAndValidateFraction()
109 Debug.Assert(_ttt == TTT.Num);
110 Debug.Assert(_num > -1);
112 if (_num == 0)
113 return true;
115 if (_zeroes == 0 && _num > MaxFraction)
116 return false;
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
125 return true;
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);
137 return true;
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);
150 return true;
154 private ref struct TimeSpanTokenizer
156 private readonly ReadOnlySpan<char> _value;
157 private int _pos;
159 internal TimeSpanTokenizer(ReadOnlySpan<char> input) : this(input, 0) { }
161 internal TimeSpanTokenizer(ReadOnlySpan<char> input, int startPosition)
163 _value = input;
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.
173 int pos = _pos;
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';
182 if ((uint)num <= 9)
184 int zeroes = 0;
185 if (num == 0)
187 // Read all leading zeroes.
188 zeroes = 1;
189 while (true)
191 int digit;
192 if (++_pos >= _value.Length || (uint)(digit = _value[_pos] - '0') > 9)
194 return new TimeSpanToken(TTT.Num, 0, zeroes, default);
197 if (digit == 0)
199 zeroes++;
200 continue;
203 num = digit;
204 break;
208 // Continue to read as long as we're reading digits.
209 while (++_pos < _value.Length)
211 int digit = _value[_pos] - '0';
212 if ((uint)digit > 9)
214 break;
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.
229 int length = 1;
230 while (true)
232 if (++_pos >= _value.Length || (uint)(_value[_pos] - '0') <= 9)
234 break;
236 length++;
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
254 int pos = ++_pos;
255 return (uint)pos < (uint)_value.Length ?
256 _value[pos] :
257 (char)0;
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
272 if (!_posLocInit)
274 _posLoc = new TimeSpanFormat.FormatLiterals();
275 _posLoc.Init(_fullPosPattern, false);
276 _posLocInit = true;
278 return _posLoc;
282 internal TimeSpanFormat.FormatLiterals NegativeLocalized
286 if (!_negLocInit)
288 _negLoc = new TimeSpanFormat.FormatLiterals();
289 _negLoc.Init(_fullNegPattern, false);
290 _negLocInit = true;
292 return _negLoc;
296 internal bool FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
297 _sepCount == 5
298 && _numCount == 4
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) =>
306 _sepCount == 4
307 && _numCount == 3
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) =>
326 _sepCount == 2
327 && _numCount == 1
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) =>
333 _sepCount == 3
334 && _numCount == 2
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) =>
341 _sepCount == 4
342 && _numCount == 3
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) =>
350 _sepCount == 4
351 && _numCount == 3
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) =>
359 _sepCount == 5
360 && _numCount == 4
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) =>
369 _sepCount == 5
370 && _numCount == 4
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;
401 _tokenCount = 0;
402 _sepCount = 0;
403 _numCount = 0;
405 _fullPosPattern = dtfi.FullTimeSpanPositivePattern;
406 _fullNegPattern = dtfi.FullTimeSpanNegativePattern;
407 _posLocInit = false;
408 _negLocInit = false;
411 internal bool ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result)
413 switch (tok._ttt)
415 case TTT.Num:
416 if ((_tokenCount == 0 && !AddSep(default, ref result)) || !AddNum(tok, ref result))
418 return false;
420 break;
422 case TTT.Sep:
423 if (!AddSep(tok._sep, ref result))
425 return false;
427 break;
429 case TTT.NumOverflow:
430 return result.SetOverflowFailure();
432 default:
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)");
439 return true;
442 private bool AddSep(ReadOnlySpan<char> sep, ref TimeSpanResult result)
444 if (_sepCount >= MaxLiteralTokens || _tokenCount >= MaxTokens)
446 return result.SetBadTimeSpanFailure();
449 switch (_sepCount++)
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;
459 _tokenCount++;
460 return true;
462 private bool AddNum(TimeSpanToken num, ref TimeSpanResult result)
464 if (_numCount >= MaxNumericTokens || _tokenCount >= MaxTokens)
466 return result.SetBadTimeSpanFailure();
469 switch (_numCount++)
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;
478 _tokenCount++;
479 return true;
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)
501 return false;
504 throw new FormatException(SR.Format_NoFormatSpecifier);
507 internal bool SetBadQuoteFailure(char failingCharacter)
509 if (!_throwOnFailure)
511 return false;
514 throw new FormatException(SR.Format(SR.Format_BadQuote, failingCharacter));
517 internal bool SetInvalidStringFailure()
519 if (!_throwOnFailure)
521 return false;
524 throw new FormatException(SR.Format_InvalidString);
527 internal bool SetArgumentNullFailure(string argumentName)
529 if (!_throwOnFailure)
531 return false;
534 Debug.Assert(argumentName != null);
535 throw new ArgumentNullException(argumentName, SR.ArgumentNull_String);
538 internal bool SetOverflowFailure()
540 if (!_throwOnFailure)
542 return false;
545 throw new OverflowException(SR.Format(SR.Overflow_TimeSpanElementTooLarge, new string(_originalTimeSpanString)));
548 internal bool SetBadTimeSpanFailure()
550 if (!_throwOnFailure)
552 return false;
555 throw new FormatException(SR.Format(SR.Format_BadTimeSpan, new string(_originalTimeSpanString)));
558 internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null)
560 if (!_throwOnFailure)
562 return false;
565 throw new FormatException(SR.Format(SR.Format_BadFormatSpecifier, formatSpecifierCharacter));
569 internal static long Pow10(int pow)
571 switch (pow)
573 case 0: return 1;
574 case 1: return 10;
575 case 2: return 100;
576 case 3: return 1000;
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())
593 result = 0;
594 return false;
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)
600 result = 0;
601 return false;
604 result = ticks * TimeSpan.TicksPerMillisecond + fraction._num;
605 if (positive && result < 0)
607 result = 0;
608 return false;
611 return true;
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;
629 return true;
632 result = default;
633 return false;
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;
651 return true;
654 result = default;
655 return false;
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;
673 return true;
676 result = default;
677 return false;
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();
684 if (input.IsEmpty)
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();
713 return true;
716 /// <summary>
717 /// Validate the terminal state of a standard format parse.
718 /// Sets result.parsedTimeSpan on success.
719 /// Calculates the resultant TimeSpan from the TimeSpanRawInfo.
720 /// </summary>
721 /// <remarks>
722 /// try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
723 /// 1) Verify Start matches
724 /// 2) Verify End matches
725 /// 3) 1 number => d
726 /// 2 numbers => h:m
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
730 /// </remarks>
731 private static bool ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
733 if (raw._lastSeenTTT == TTT.Num)
735 TimeSpanToken tok = new TimeSpanToken();
736 tok._ttt = TTT.Sep;
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;
766 bool match = false;
768 if (inv)
770 if (raw.FullMatch(raw.PositiveInvariant))
772 match = true;
773 positive = true;
775 if (!match && raw.FullMatch(raw.NegativeInvariant))
777 match = true;
778 positive = false;
782 if (loc)
784 if (!match && raw.FullMatch(raw.PositiveLocalized))
786 match = true;
787 positive = true;
789 if (!match && raw.FullMatch(raw.NegativeLocalized))
791 match = true;
792 positive = false;
796 if (match)
798 long ticks;
800 if (!TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, raw._numbers4, out ticks))
802 return result.SetOverflowFailure();
805 if (!positive)
807 ticks = -ticks;
808 if (ticks > 0)
810 return result.SetOverflowFailure();
814 result.parsedTimeSpan = new TimeSpan(ticks);
815 return true;
818 return result.SetBadTimeSpanFailure();
822 /// <summary>
823 /// Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds",
824 /// or "Days.Hours:Minutes:.Fraction" terminal case.
825 /// </summary>
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);
837 long ticks = 0;
838 bool positive = false, match = false, overflow = false;
839 var zero = new TimeSpanToken(0);
841 if (inv)
843 if (raw.FullHMSFMatch(raw.PositiveInvariant))
845 positive = true;
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))
852 positive = true;
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))
859 positive = true;
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))
866 positive = false;
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))
873 positive = false;
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))
880 positive = false;
881 match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
882 overflow = overflow || !match;
886 if (loc)
888 if (!match && raw.FullHMSFMatch(raw.PositiveLocalized))
890 positive = true;
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))
897 positive = true;
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))
904 positive = true;
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))
911 positive = false;
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))
918 positive = false;
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))
925 positive = false;
926 match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
927 overflow = overflow || !match;
931 if (match)
933 if (!positive)
935 ticks = -ticks;
936 if (ticks > 0)
938 return result.SetOverflowFailure();
942 result.parsedTimeSpan = new TimeSpan(ticks);
943 return true;
946 return overflow ?
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);
965 long ticks = 0;
967 if (inv)
969 if (raw.FullHMSMatch(raw.PositiveInvariant))
971 positive = true;
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))
978 positive = true;
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))
985 positive = true;
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))
992 positive = false;
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))
999 positive = false;
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))
1006 positive = false;
1007 match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
1008 overflow = overflow || !match;
1012 if (loc)
1014 if (!match && raw.FullHMSMatch(raw.PositiveLocalized))
1016 positive = true;
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))
1023 positive = true;
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))
1030 positive = true;
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))
1037 positive = false;
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))
1044 positive = false;
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))
1051 positive = false;
1052 match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
1053 overflow = overflow || !match;
1057 if (match)
1059 if (!positive)
1061 ticks = -ticks;
1062 if (ticks > 0)
1064 return result.SetOverflowFailure();
1068 result.parsedTimeSpan = new TimeSpan(ticks);
1069 return true;
1072 return overflow ?
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;
1091 if (inv)
1093 if (raw.FullHMMatch(raw.PositiveInvariant))
1095 match = true;
1096 positive = true;
1099 if (!match && raw.FullHMMatch(raw.NegativeInvariant))
1101 match = true;
1102 positive = false;
1106 if (loc)
1108 if (!match && raw.FullHMMatch(raw.PositiveLocalized))
1110 match = true;
1111 positive = true;
1114 if (!match && raw.FullHMMatch(raw.NegativeLocalized))
1116 match = true;
1117 positive = false;
1121 if (match)
1123 long ticks = 0;
1124 var zero = new TimeSpanToken(0);
1126 if (!TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, zero, out ticks))
1128 return result.SetOverflowFailure();
1131 if (!positive)
1133 ticks = -ticks;
1134 if (ticks > 0)
1136 return result.SetOverflowFailure();
1140 result.parsedTimeSpan = new TimeSpan(ticks);
1141 return true;
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;
1161 if (inv)
1163 if (raw.FullDMatch(raw.PositiveInvariant))
1165 match = true;
1166 positive = true;
1169 if (!match && raw.FullDMatch(raw.NegativeInvariant))
1171 match = true;
1172 positive = false;
1176 if (loc)
1178 if (!match && raw.FullDMatch(raw.PositiveLocalized))
1180 match = true;
1181 positive = true;
1184 if (!match && raw.FullDMatch(raw.NegativeLocalized))
1186 match = true;
1187 positive = false;
1191 if (match)
1193 long ticks = 0;
1194 var zero = new TimeSpanToken(0);
1196 if (!TryTimeToTicks(positive, raw._numbers0, zero, zero, zero, zero, out ticks))
1198 return result.SetOverflowFailure();
1201 if (!positive)
1203 ticks = -ticks;
1204 if (ticks > 0)
1206 return result.SetOverflowFailure();
1210 result.parsedTimeSpan = new TimeSpan(ticks);
1211 return true;
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)
1227 switch (format[0])
1229 case 'c':
1230 case 't':
1231 case 'T':
1232 return TryParseTimeSpanConstant(input, ref result); // fast path for legacy style TimeSpan formats.
1234 case 'g':
1235 return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized, formatProvider, ref result);
1237 case 'G':
1238 return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull, formatProvider, ref result);
1240 default:
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];
1271 int nextFormatChar;
1272 switch (ch)
1274 case 'h':
1275 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1276 if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh))
1278 return result.SetInvalidStringFailure();
1280 seenHH = true;
1281 break;
1283 case 'm':
1284 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1285 if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm))
1287 return result.SetInvalidStringFailure();
1289 seenMM = true;
1290 break;
1292 case 's':
1293 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1294 if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss))
1296 return result.SetInvalidStringFailure();
1298 seenSS = true;
1299 break;
1301 case 'f':
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();
1307 seenFF = true;
1308 break;
1310 case 'F':
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);
1317 seenFF = true;
1318 break;
1320 case 'd':
1321 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1322 int tmp = 0;
1323 if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen < 2) ? 1 : tokenLen, (tokenLen < 2) ? 8 : tokenLen, out tmp, out dd))
1325 return result.SetInvalidStringFailure();
1327 seenDD = true;
1328 break;
1330 case '\'':
1331 case '\"':
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);
1344 break;
1346 case '%':
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
1357 break;
1359 else
1361 // This means that '%' is at the end of the format string or
1362 // "%%" appears in the format string.
1363 return result.SetInvalidStringFailure();
1366 case '\\':
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)
1373 tokenLen = 2;
1375 else
1377 // This means that '\' is at the end of the format string or the literal match failed.
1378 return result.SetInvalidStringFailure();
1380 break;
1382 default:
1383 return result.SetInvalidStringFailure();
1386 i += tokenLen;
1390 if (!tokenizer.EOL)
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),
1402 out long ticks))
1404 if (!positive)
1406 ticks = -ticks;
1409 result.parsedTimeSpan = new TimeSpan(ticks);
1410 return true;
1412 else
1414 return result.SetOverflowFailure();
1418 private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result)
1420 result = 0;
1421 int zeroes = 0;
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();
1437 break;
1440 tmpResult = tmpResult * 10 + (ch - '0');
1441 if (tmpResult == 0) tmpZeroes++;
1442 tokenLength++;
1445 zeroes = tmpZeroes;
1446 result = tmpResult;
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)
1456 return false;
1460 return true;
1463 /// <summary>
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.
1466 /// </summary>
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;
1473 private char _ch;
1474 private int _pos;
1475 private int _len;
1477 internal void NextChar()
1479 if (_pos < _len)
1481 _pos++;
1484 _ch = _pos < _len ?
1485 _str[_pos] :
1486 (char)0;
1489 internal char NextNonDigit()
1491 int i = _pos;
1492 while (i < _len)
1494 char ch = _str[i];
1495 if (ch < '0' || ch > '9') return ch;
1496 i++;
1499 return (char)0;
1502 internal bool TryParse(ReadOnlySpan<char> input, ref TimeSpanResult result)
1504 result.parsedTimeSpan = default;
1506 _str = input;
1507 _len = input.Length;
1508 _pos = -1;
1509 NextChar();
1510 SkipBlanks();
1512 bool negative = false;
1513 if (_ch == '-')
1515 negative = true;
1516 NextChar();
1519 long time;
1520 if (NextNonDigit() == ':')
1522 if (!ParseTime(out time, ref result))
1524 return false;
1527 else
1529 int days;
1530 if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result))
1532 return false;
1535 time = days * TimeSpan.TicksPerDay;
1537 if (_ch == '.')
1539 NextChar();
1540 long remainingTime;
1541 if (!ParseTime(out remainingTime, ref result))
1543 return false;
1545 time += remainingTime;
1549 if (negative)
1551 time = -time;
1552 // Allow -0 as well
1553 if (time > 0)
1555 return result.SetOverflowFailure();
1558 else
1560 if (time < 0)
1562 return result.SetOverflowFailure();
1566 SkipBlanks();
1568 if (_pos < _len)
1570 return result.SetBadTimeSpanFailure();
1573 result.parsedTimeSpan = new TimeSpan(time);
1574 return true;
1577 internal bool ParseInt(int max, out int i, ref TimeSpanResult result)
1579 i = 0;
1580 int p = _pos;
1581 while (_ch >= '0' && _ch <= '9')
1583 if ((i & 0xF0000000) != 0)
1585 return result.SetOverflowFailure();
1588 i = i * 10 + _ch - '0';
1589 if (i < 0)
1591 return result.SetOverflowFailure();
1594 NextChar();
1597 if (p == _pos)
1599 return result.SetBadTimeSpanFailure();
1602 if (i > max)
1604 return result.SetOverflowFailure();
1607 return true;
1610 internal bool ParseTime(out long time, ref TimeSpanResult result)
1612 time = 0;
1613 int unit;
1615 if (!ParseInt(23, out unit, ref result))
1617 return false;
1620 time = unit * TimeSpan.TicksPerHour;
1621 if (_ch != ':')
1623 return result.SetBadTimeSpanFailure();
1626 NextChar();
1628 if (!ParseInt(59, out unit, ref result))
1630 return false;
1633 time += unit * TimeSpan.TicksPerMinute;
1635 if (_ch == ':')
1637 NextChar();
1639 // allow seconds with the leading zero
1640 if (_ch != '.')
1642 if (!ParseInt(59, out unit, ref result))
1644 return false;
1646 time += unit * TimeSpan.TicksPerSecond;
1649 if (_ch == '.')
1651 NextChar();
1652 int f = (int)TimeSpan.TicksPerSecond;
1653 while (f > 1 && _ch >= '0' && _ch <= '9')
1655 f /= 10;
1656 time += (_ch - '0') * f;
1657 NextChar();
1662 return true;
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 if (formats[i] == null || formats[i].Length == 0)
1695 return result.SetBadFormatSpecifierFailure();
1698 // Create a new non-throwing result each time to ensure the runs are independent.
1699 TimeSpanResult innerResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
1701 if (TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult))
1703 result.parsedTimeSpan = innerResult.parsedTimeSpan;
1704 return true;
1708 return result.SetBadTimeSpanFailure();