| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- ////////////////////////////////////////////////////////////////////////////
- //
- // Purpose: Used by TimeSpan to parse a time interval string.
- //
- // Standard Format:
- // -=-=-=-=-=-=-=-
- // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
- // Not culture sensitive. Default format (and null/empty format string) map to this format.
- //
- // "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
- // Only print what's needed. Localized (if you want Invariant, pass in Invariant).
- // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
- //
- // "G": General format, long: [-]d':'hh':'mm':'ss'.'fffffff
- // Always print days and 7 fractional digits. Localized (if you want Invariant, pass in Invariant).
- // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
- //
- // * "TryParseTimeSpan" is the main method for Parse/TryParse
- //
- // - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
- // - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
- // - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
- // The terminal states are attempted as follows:
- // foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
- // 1 number => d
- // 2 numbers => h:m
- // 3 numbers => h:m:s | d.h:m | h:m:.f
- // 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
- // 5 numbers => d.h:m:s.f
- //
- // Custom Format:
- // -=-=-=-=-=-=-=
- //
- // * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
- // * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
- // methods that take a string[] of formats
- //
- // - For single-letter formats "TryParseTimeSpan" is called (see above)
- // - For multi-letter formats "TryParseByFormat" is called
- // - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
- // which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
- // operates on whole-tokens, ParseExact operates at the character-level. As such,
- // TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
- //
- ////////////////////////////////////////////////////////////////////////////
- using System.Diagnostics;
- using System.Text;
- namespace System.Globalization
- {
- internal static class TimeSpanParse
- {
- private const int MaxFractionDigits = 7;
- private const int MaxDays = 10675199;
- private const int MaxHours = 23;
- private const int MaxMinutes = 59;
- private const int MaxSeconds = 59;
- private const int MaxFraction = 9999999;
- [Flags]
- private enum TimeSpanStandardStyles : byte
- {
- // Standard Format Styles
- None = 0x00000000,
- Invariant = 0x00000001, //Allow Invariant Culture
- Localized = 0x00000002, //Allow Localized Culture
- RequireFull = 0x00000004, //Require the input to be in DHMSF format
- Any = Invariant | Localized,
- }
- // TimeSpan Token Types
- private enum TTT : byte
- {
- None = 0, // None of the TimeSpanToken fields are set
- End = 1, // '\0'
- Num = 2, // Number
- Sep = 3, // literal
- NumOverflow = 4, // Number that overflowed
- }
- private ref struct TimeSpanToken
- {
- internal TTT _ttt;
- internal int _num; // Store the number that we are parsing (if any)
- internal int _zeroes; // Store the number of leading zeroes (if any)
- internal ReadOnlySpan<char> _sep; // Store the literal that we are parsing (if any)
- public TimeSpanToken(TTT type) : this(type, 0, 0, default) { }
- public TimeSpanToken(int number) : this(TTT.Num, number, 0, default) { }
- public TimeSpanToken(int number, int leadingZeroes) : this(TTT.Num, number, leadingZeroes, default) { }
- public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char> separator)
- {
- _ttt = type;
- _num = number;
- _zeroes = leadingZeroes;
- _sep = separator;
- }
- public bool NormalizeAndValidateFraction()
- {
- Debug.Assert(_ttt == TTT.Num);
- Debug.Assert(_num > -1);
- if (_num == 0)
- return true;
- if (_zeroes == 0 && _num > MaxFraction)
- return false;
- int totalDigitsCount = ((int) Math.Floor(Math.Log10(_num))) + 1 + _zeroes;
- if (totalDigitsCount == MaxFractionDigits)
- {
- // Already normalized. no more action needed
- // .9999999 normalize to 9,999,999 ticks
- // .0000001 normalize to 1 ticks
- return true;
- }
- if (totalDigitsCount < MaxFractionDigits)
- {
- // normalize the fraction to the 7-digits
- // .999999 normalize to 9,999,990 ticks
- // .99999 normalize to 9,999,900 ticks
- // .000001 normalize to 10 ticks
- // .1 normalize to 1,000,000 ticks
- _num *= (int) Pow10(MaxFractionDigits - totalDigitsCount);
- return true;
- }
- // totalDigitsCount is greater then MaxFractionDigits, we'll need to do the rounding to 7-digits length
- // .00000001 normalized to 0 ticks
- // .00000005 normalized to 1 ticks
- // .09999999 normalize to 1,000,000 ticks
- // .099999999 normalize to 1,000,000 ticks
- Debug.Assert(_zeroes > 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction
- _num = (int) Math.Round((double)_num / Pow10(totalDigitsCount - MaxFractionDigits), MidpointRounding.AwayFromZero);
- Debug.Assert(_num < MaxFraction);
- return true;
- }
- }
- private ref struct TimeSpanTokenizer
- {
- private ReadOnlySpan<char> _value;
- private int _pos;
- internal TimeSpanTokenizer(ReadOnlySpan<char> input) : this(input, 0) { }
- internal TimeSpanTokenizer(ReadOnlySpan<char> input, int startPosition)
- {
- _value = input;
- _pos = startPosition;
- }
- /// <summary>Returns the next token in the input string</summary>
- /// <remarks>Used by the parsing routines that operate on standard-formats.</remarks>
- internal TimeSpanToken GetNextToken()
- {
- // Get the position of the next character to be processed. If there is no
- // next character, we're at the end.
- int pos = _pos;
- Debug.Assert(pos > -1);
- if (pos >= _value.Length)
- {
- return new TimeSpanToken(TTT.End);
- }
- // Now retrieve that character. If it's a digit, we're processing a number.
- int num = _value[pos] - '0';
- if ((uint)num <= 9)
- {
- int zeroes = 0;
- if (num == 0)
- {
- // Read all leading zeroes.
- zeroes = 1;
- while (true)
- {
- int digit;
- if (++_pos >= _value.Length || (uint)(digit = _value[_pos] - '0') > 9)
- {
- return new TimeSpanToken(TTT.Num, 0, zeroes, default);
- }
- if (digit == 0)
- {
- zeroes++;
- continue;
- }
- num = digit;
- break;
- }
- }
- // Continue to read as long as we're reading digits.
- while (++_pos < _value.Length)
- {
- int digit = _value[_pos] - '0';
- if ((uint)digit > 9)
- {
- break;
- }
- num = num * 10 + digit;
- if ((num & 0xF0000000) != 0) // Max limit we can support 268435455 which is FFFFFFF
- {
- return new TimeSpanToken(TTT.NumOverflow);
- }
- }
- return new TimeSpanToken(TTT.Num, num, zeroes, default);
- }
- // Otherwise, we're processing a separator, and we've already processed the first
- // character of it. Continue processing characters as long as they're not digits.
- int length = 1;
- while (true)
- {
- if (++_pos >= _value.Length || (uint)(_value[_pos] - '0') <= 9)
- {
- break;
- }
- length++;
- }
- // Return the separator.
- return new TimeSpanToken(TTT.Sep, 0, 0, _value.Slice(pos, length));
- }
- internal bool EOL => _pos >= (_value.Length - 1);
- internal void BackOne()
- {
- if (_pos > 0) --_pos;
- }
- internal char NextChar
- {
- get
- {
- int pos = ++_pos;
- return (uint)pos < (uint)_value.Length ?
- _value[pos] :
- (char)0;
- }
- }
- }
- /// <summary>Stores intermediary parsing state for the standard formats.</summary>
- private ref struct TimeSpanRawInfo
- {
- internal TimeSpanFormat.FormatLiterals PositiveInvariant => TimeSpanFormat.PositiveInvariantFormatLiterals;
- internal TimeSpanFormat.FormatLiterals NegativeInvariant => TimeSpanFormat.NegativeInvariantFormatLiterals;
- internal TimeSpanFormat.FormatLiterals PositiveLocalized
- {
- get
- {
- if (!_posLocInit)
- {
- _posLoc = new TimeSpanFormat.FormatLiterals();
- _posLoc.Init(_fullPosPattern, false);
- _posLocInit = true;
- }
- return _posLoc;
- }
- }
- internal TimeSpanFormat.FormatLiterals NegativeLocalized
- {
- get
- {
- if (!_negLocInit)
- {
- _negLoc = new TimeSpanFormat.FormatLiterals();
- _negLoc.Init(_fullNegPattern, false);
- _negLocInit = true;
- }
- return _negLoc;
- }
- }
- internal bool FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 5
- && _numCount == 4
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.DayHourSep)
- && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals3.EqualsOrdinal(pattern.AppCompatLiteral)
- && _literals4.EqualsOrdinal(pattern.End);
- internal bool PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 4
- && _numCount == 3
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals2.EqualsOrdinal(pattern.AppCompatLiteral)
- && _literals3.EqualsOrdinal(pattern.End);
- /// <summary>DHMSF (all values matched)</summary>
- internal bool FullMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == MaxLiteralTokens
- && _numCount == MaxNumericTokens
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.DayHourSep)
- && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
- && _literals4.EqualsOrdinal(pattern.SecondFractionSep)
- && _literals5.EqualsOrdinal(pattern.End);
- /// <summary>D (no hours, minutes, seconds, or fractions)</summary>
- internal bool FullDMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 2
- && _numCount == 1
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.End);
- /// <summary>HM (no days, seconds, or fractions)</summary>
- internal bool FullHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 3
- && _numCount == 2
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals2.EqualsOrdinal(pattern.End);
- /// <summary>DHM (no seconds or fraction)</summary>
- internal bool FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 4
- && _numCount == 3
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.DayHourSep)
- && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals3.EqualsOrdinal(pattern.End);
- /// <summary>HMS (no days or fraction)</summary>
- internal bool FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 4
- && _numCount == 3
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
- && _literals3.EqualsOrdinal(pattern.End);
- /// <summary>DHMS (no fraction)</summary>
- internal bool FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 5
- && _numCount == 4
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.DayHourSep)
- && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
- && _literals4.EqualsOrdinal(pattern.End);
- /// <summary>HMSF (no days)</summary>
- internal bool FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) =>
- _sepCount == 5
- && _numCount == 4
- && _literals0.EqualsOrdinal(pattern.Start)
- && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
- && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
- && _literals3.EqualsOrdinal(pattern.SecondFractionSep)
- && _literals4.EqualsOrdinal(pattern.End);
- internal TTT _lastSeenTTT;
- internal int _tokenCount;
- internal int _sepCount;
- internal int _numCount;
- private TimeSpanFormat.FormatLiterals _posLoc;
- private TimeSpanFormat.FormatLiterals _negLoc;
- private bool _posLocInit;
- private bool _negLocInit;
- private string _fullPosPattern;
- private string _fullNegPattern;
- private const int MaxTokens = 11;
- private const int MaxLiteralTokens = 6;
- private const int MaxNumericTokens = 5;
- internal TimeSpanToken _numbers0, _numbers1, _numbers2, _numbers3, _numbers4; // MaxNumbericTokens = 5
- internal ReadOnlySpan<char> _literals0, _literals1, _literals2, _literals3, _literals4, _literals5; // MaxLiteralTokens=6
- internal void Init(DateTimeFormatInfo dtfi)
- {
- Debug.Assert(dtfi != null);
- _lastSeenTTT = TTT.None;
- _tokenCount = 0;
- _sepCount = 0;
- _numCount = 0;
- _fullPosPattern = dtfi.FullTimeSpanPositivePattern;
- _fullNegPattern = dtfi.FullTimeSpanNegativePattern;
- _posLocInit = false;
- _negLocInit = false;
- }
- internal bool ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result)
- {
- switch (tok._ttt)
- {
- case TTT.Num:
- if ((_tokenCount == 0 && !AddSep(default, ref result)) || !AddNum(tok, ref result))
- {
- return false;
- }
- break;
- case TTT.Sep:
- if (!AddSep(tok._sep, ref result))
- {
- return false;
- }
- break;
- case TTT.NumOverflow:
- return result.SetOverflowFailure();
- default:
- // Some unknown token or a repeat token type in the input
- return result.SetBadTimeSpanFailure();
- }
- _lastSeenTTT = tok._ttt;
- Debug.Assert(_tokenCount == (_sepCount + _numCount), "tokenCount == (SepCount + NumCount)");
- return true;
- }
- private bool AddSep(ReadOnlySpan<char> sep, ref TimeSpanResult result)
- {
- if (_sepCount >= MaxLiteralTokens || _tokenCount >= MaxTokens)
- {
- return result.SetBadTimeSpanFailure();
- }
- switch (_sepCount++)
- {
- case 0: _literals0 = sep; break;
- case 1: _literals1 = sep; break;
- case 2: _literals2 = sep; break;
- case 3: _literals3 = sep; break;
- case 4: _literals4 = sep; break;
- default: _literals5 = sep; break;
- }
- _tokenCount++;
- return true;
- }
- private bool AddNum(TimeSpanToken num, ref TimeSpanResult result)
- {
- if (_numCount >= MaxNumericTokens || _tokenCount >= MaxTokens)
- {
- return result.SetBadTimeSpanFailure();
- }
- switch (_numCount++)
- {
- case 0: _numbers0 = num; break;
- case 1: _numbers1 = num; break;
- case 2: _numbers2 = num; break;
- case 3: _numbers3 = num; break;
- default: _numbers4 = num; break;
- }
- _tokenCount++;
- return true;
- }
- }
- /// <summary>Store the result of the parsing.</summary>
- private ref struct TimeSpanResult
- {
- internal TimeSpan parsedTimeSpan;
- private readonly bool _throwOnFailure;
- private readonly ReadOnlySpan<char> _originalTimeSpanString;
- internal TimeSpanResult(bool throwOnFailure, ReadOnlySpan<char> originalTimeSpanString)
- {
- parsedTimeSpan = default;
- _throwOnFailure = throwOnFailure;
- _originalTimeSpanString = originalTimeSpanString;
- }
- internal bool SetNoFormatSpecifierFailure()
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new FormatException(SR.Format_NoFormatSpecifier);
- }
- internal bool SetBadQuoteFailure(char failingCharacter)
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new FormatException(SR.Format(SR.Format_BadQuote, failingCharacter));
- }
- internal bool SetInvalidStringFailure()
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new FormatException(SR.Format_InvalidString);
- }
- internal bool SetArgumentNullFailure(string argumentName)
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- Debug.Assert(argumentName != null);
- throw new ArgumentNullException(argumentName, SR.ArgumentNull_String);
- }
- internal bool SetOverflowFailure()
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new OverflowException(SR.Format(SR.Overflow_TimeSpanElementTooLarge, new string(_originalTimeSpanString)));
- }
- internal bool SetBadTimeSpanFailure()
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new FormatException(SR.Format(SR.Format_BadTimeSpan, new string(_originalTimeSpanString)));
- }
- internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null)
- {
- if (!_throwOnFailure)
- {
- return false;
- }
- throw new FormatException(SR.Format(SR.Format_BadFormatSpecifier, formatSpecifierCharacter));
- }
- }
- internal static long Pow10(int pow)
- {
- switch (pow)
- {
- case 0: return 1;
- case 1: return 10;
- case 2: return 100;
- case 3: return 1000;
- case 4: return 10000;
- case 5: return 100000;
- case 6: return 1000000;
- case 7: return 10000000;
- default: return (long)Math.Pow(10, pow);
- }
- }
- private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)
- {
- if (days._num > MaxDays ||
- hours._num > MaxHours ||
- minutes._num > MaxMinutes ||
- seconds._num > MaxSeconds ||
- !fraction.NormalizeAndValidateFraction())
- {
- result = 0;
- return false;
- }
- long ticks = ((long)days._num * 3600 * 24 + (long)hours._num * 3600 + (long)minutes._num * 60 + seconds._num) * 1000;
- if (ticks > InternalGlobalizationHelper.MaxMilliSeconds || ticks < InternalGlobalizationHelper.MinMilliSeconds)
- {
- result = 0;
- return false;
- }
- result = ticks * TimeSpan.TicksPerMillisecond + fraction._num;
- if (positive && result < 0)
- {
- result = 0;
- return false;
- }
- return true;
- }
- internal static TimeSpan Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
- bool success = TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult);
- Debug.Assert(success, "Should have thrown on failure");
- return parseResult.parsedTimeSpan;
- }
- internal static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, out TimeSpan result)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
- if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult))
- {
- result = parseResult.parsedTimeSpan;
- return true;
- }
- result = default;
- return false;
- }
- internal static TimeSpan ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
- bool success = TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult);
- Debug.Assert(success, "Should have thrown on failure");
- return parseResult.parsedTimeSpan;
- }
- internal static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
- if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult))
- {
- result = parseResult.parsedTimeSpan;
- return true;
- }
- result = default;
- return false;
- }
- internal static TimeSpan ParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
- bool success = TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult);
- Debug.Assert(success, "Should have thrown on failure");
- return parseResult.parsedTimeSpan;
- }
- internal static bool TryParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
- {
- var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
- if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult))
- {
- result = parseResult.parsedTimeSpan;
- return true;
- }
- result = default;
- return false;
- }
- /// <summary>Common private Parse method called by both Parse and TryParse.</summary>
- private static bool TryParseTimeSpan(ReadOnlySpan<char> input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result)
- {
- input = input.Trim();
- if (input.IsEmpty)
- {
- return result.SetBadTimeSpanFailure();
- }
- var tokenizer = new TimeSpanTokenizer(input);
- var raw = new TimeSpanRawInfo();
- raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
- TimeSpanToken tok = tokenizer.GetNextToken();
- // The following loop will break out when we reach the end of the str or
- // when we can determine that the input is invalid.
- while (tok._ttt != TTT.End)
- {
- if (!raw.ProcessToken(ref tok, ref result))
- {
- return result.SetBadTimeSpanFailure();
- }
- tok = tokenizer.GetNextToken();
- }
- Debug.Assert(tokenizer.EOL);
- if (!ProcessTerminalState(ref raw, style, ref result))
- {
- return result.SetBadTimeSpanFailure();
- }
- return true;
- }
- /// <summary>
- /// Validate the terminal state of a standard format parse.
- /// Sets result.parsedTimeSpan on success.
- /// Calculates the resultant TimeSpan from the TimeSpanRawInfo.
- /// </summary>
- /// <remarks>
- /// try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
- /// 1) Verify Start matches
- /// 2) Verify End matches
- /// 3) 1 number => d
- /// 2 numbers => h:m
- /// 3 numbers => h:m:s | d.h:m | h:m:.f
- /// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
- /// 5 numbers => d.h:m:s.f
- /// </remarks>
- private static bool ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._lastSeenTTT == TTT.Num)
- {
- TimeSpanToken tok = new TimeSpanToken();
- tok._ttt = TTT.Sep;
- if (!raw.ProcessToken(ref tok, ref result))
- {
- return result.SetBadTimeSpanFailure();
- }
- }
- switch (raw._numCount)
- {
- case 1: return ProcessTerminal_D(ref raw, style, ref result);
- case 2: return ProcessTerminal_HM(ref raw, style, ref result);
- case 3: return ProcessTerminal_HM_S_D(ref raw, style, ref result);
- case 4: return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
- case 5: return ProcessTerminal_DHMSF(ref raw, style, ref result);
- default: return result.SetBadTimeSpanFailure();
- }
- }
- /// <summary>Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.</summary>
- private static bool ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._sepCount != 6)
- {
- return result.SetBadTimeSpanFailure();
- }
- Debug.Assert(raw._numCount == 5);
- bool inv = (style & TimeSpanStandardStyles.Invariant) != 0;
- bool loc = (style & TimeSpanStandardStyles.Localized) != 0;
- bool positive = false;
- bool match = false;
- if (inv)
- {
- if (raw.FullMatch(raw.PositiveInvariant))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullMatch(raw.NegativeInvariant))
- {
- match = true;
- positive = false;
- }
- }
- if (loc)
- {
- if (!match && raw.FullMatch(raw.PositiveLocalized))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullMatch(raw.NegativeLocalized))
- {
- match = true;
- positive = false;
- }
- }
- if (match)
- {
- long ticks;
- if (!TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, raw._numbers4, out ticks))
- {
- return result.SetOverflowFailure();
- }
- if (!positive)
- {
- ticks = -ticks;
- if (ticks > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- return result.SetBadTimeSpanFailure();
- }
- /// <summary>
- /// Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds",
- /// or "Days.Hours:Minutes:.Fraction" terminal case.
- /// </summary>
- private static bool ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._sepCount != 5 || (style & TimeSpanStandardStyles.RequireFull) != 0)
- {
- return result.SetBadTimeSpanFailure();
- }
- Debug.Assert(raw._numCount == 4);
- bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
- bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
- long ticks = 0;
- bool positive = false, match = false, overflow = false;
- var zero = new TimeSpanToken(0);
- if (inv)
- {
- if (raw.FullHMSFMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMSMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullHMSFMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMSMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- }
- if (loc)
- {
- if (!match && raw.FullHMSFMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMSMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullHMSFMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMSMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
- overflow = overflow || !match;
- }
- }
- if (match)
- {
- if (!positive)
- {
- ticks = -ticks;
- if (ticks > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- return overflow ?
- result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
- result.SetBadTimeSpanFailure(); // we couldn't find a thing
- }
- /// <summary>Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case.</summary>
- private static bool ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._sepCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0)
- {
- return result.SetBadTimeSpanFailure();
- }
- Debug.Assert(raw._numCount == 3);
- bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
- bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
- bool positive = false, match = false, overflow = false;
- var zero = new TimeSpanToken(0);
- long ticks = 0;
- if (inv)
- {
- if (raw.FullHMSMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullHMSMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
- overflow = overflow || !match;
- }
- }
- if (loc)
- {
- if (!match && raw.FullHMSMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized))
- {
- positive = true;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullHMSMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.FullDHMMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
- overflow = overflow || !match;
- }
- if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized))
- {
- positive = false;
- match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
- overflow = overflow || !match;
- }
- }
- if (match)
- {
- if (!positive)
- {
- ticks = -ticks;
- if (ticks > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- return overflow ?
- result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
- result.SetBadTimeSpanFailure(); // we couldn't find a thing
- }
- /// <summary>Validate the 2-number "Hours:Minutes" terminal case.</summary>
- private static bool ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._sepCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0)
- {
- return result.SetBadTimeSpanFailure();
- }
- Debug.Assert(raw._numCount == 2);
- bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
- bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
- bool positive = false, match = false;
- if (inv)
- {
- if (raw.FullHMMatch(raw.PositiveInvariant))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullHMMatch(raw.NegativeInvariant))
- {
- match = true;
- positive = false;
- }
- }
- if (loc)
- {
- if (!match && raw.FullHMMatch(raw.PositiveLocalized))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullHMMatch(raw.NegativeLocalized))
- {
- match = true;
- positive = false;
- }
- }
- if (match)
- {
- long ticks = 0;
- var zero = new TimeSpanToken(0);
- if (!TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, zero, out ticks))
- {
- return result.SetOverflowFailure();
- }
- if (!positive)
- {
- ticks = -ticks;
- if (ticks > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- return result.SetBadTimeSpanFailure();
- }
- /// <summary>Validate the 1-number "Days" terminal case.</summary>
- private static bool ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
- {
- if (raw._sepCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0)
- {
- return result.SetBadTimeSpanFailure();
- }
- Debug.Assert(raw._numCount == 1);
- bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
- bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
- bool positive = false, match = false;
- if (inv)
- {
- if (raw.FullDMatch(raw.PositiveInvariant))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullDMatch(raw.NegativeInvariant))
- {
- match = true;
- positive = false;
- }
- }
- if (loc)
- {
- if (!match && raw.FullDMatch(raw.PositiveLocalized))
- {
- match = true;
- positive = true;
- }
- if (!match && raw.FullDMatch(raw.NegativeLocalized))
- {
- match = true;
- positive = false;
- }
- }
- if (match)
- {
- long ticks = 0;
- var zero = new TimeSpanToken(0);
- if (!TryTimeToTicks(positive, raw._numbers0, zero, zero, zero, zero, out ticks))
- {
- return result.SetOverflowFailure();
- }
- if (!positive)
- {
- ticks = -ticks;
- if (ticks > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- return result.SetBadTimeSpanFailure();
- }
- /// <summary>Common private ParseExact method called by both ParseExact and TryParseExact.</summary>
- private static bool TryParseExactTimeSpan(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
- {
- if (format.Length == 0)
- {
- return result.SetBadFormatSpecifierFailure();
- }
- if (format.Length == 1)
- {
- switch (format[0])
- {
- case 'c':
- case 't':
- case 'T':
- return TryParseTimeSpanConstant(input, ref result); // fast path for legacy style TimeSpan formats.
- case 'g':
- return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized, formatProvider, ref result);
- case 'G':
- return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull, formatProvider, ref result);
- default:
- return result.SetBadFormatSpecifierFailure(format[0]);
- }
- }
- return TryParseByFormat(input, format, styles, ref result);
- }
- /// <summary>Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.</summary>
- private static bool TryParseByFormat(ReadOnlySpan<char> input, ReadOnlySpan<char> format, TimeSpanStyles styles, ref TimeSpanResult result)
- {
- bool seenDD = false; // already processed days?
- bool seenHH = false; // already processed hours?
- bool seenMM = false; // already processed minutes?
- bool seenSS = false; // already processed seconds?
- bool seenFF = false; // already processed fraction?
- int dd = 0; // parsed days
- int hh = 0; // parsed hours
- int mm = 0; // parsed minutes
- int ss = 0; // parsed seconds
- int leadingZeroes = 0; // number of leading zeroes in the parsed fraction
- int ff = 0; // parsed fraction
- int i = 0; // format string position
- int tokenLen = 0; // length of current format token, used to update index 'i'
- var tokenizer = new TimeSpanTokenizer(input, -1);
- while (i < format.Length)
- {
- char ch = format[i];
- int nextFormatChar;
- switch (ch)
- {
- case 'h':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh))
- {
- return result.SetInvalidStringFailure();
- }
- seenHH = true;
- break;
- case 'm':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm))
- {
- return result.SetInvalidStringFailure();
- }
- seenMM = true;
- break;
- case 's':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss))
- {
- return result.SetInvalidStringFailure();
- }
- seenSS = true;
- break;
- case 'f':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF || !ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff))
- {
- return result.SetInvalidStringFailure();
- }
- seenFF = true;
- break;
- case 'F':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF)
- {
- return result.SetInvalidStringFailure();
- }
- ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
- seenFF = true;
- break;
- case 'd':
- tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
- int tmp = 0;
- if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen < 2) ? 1 : tokenLen, (tokenLen < 2) ? 8 : tokenLen, out tmp, out dd))
- {
- return result.SetInvalidStringFailure();
- }
- seenDD = true;
- break;
- case '\'':
- case '\"':
- StringBuilder enquotedString = StringBuilderCache.Acquire();
- if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen))
- {
- StringBuilderCache.Release(enquotedString);
- return result.SetBadQuoteFailure(ch);
- }
- if (!ParseExactLiteral(ref tokenizer, enquotedString))
- {
- StringBuilderCache.Release(enquotedString);
- return result.SetInvalidStringFailure();
- }
- StringBuilderCache.Release(enquotedString);
- break;
- case '%':
- // Optional format character.
- // For example, format string "%d" will print day
- // Most of the cases, "%" can be ignored.
- nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
- // nextFormatChar will be -1 if we already reach the end of the format string.
- // Besides, we will not allow "%%" appear in the pattern.
- if (nextFormatChar >= 0 && nextFormatChar != '%')
- {
- tokenLen = 1; // skip the '%' and process the format character
- break;
- }
- else
- {
- // This means that '%' is at the end of the format string or
- // "%%" appears in the format string.
- return result.SetInvalidStringFailure();
- }
- case '\\':
- // Escaped character. Can be used to insert character into the format string.
- // For example, "\d" will insert the character 'd' into the string.
- //
- nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
- if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar)
- {
- tokenLen = 2;
- }
- else
- {
- // This means that '\' is at the end of the format string or the literal match failed.
- return result.SetInvalidStringFailure();
- }
- break;
- default:
- return result.SetInvalidStringFailure();
- }
- i += tokenLen;
- }
- if (!tokenizer.EOL)
- {
- // the custom format didn't consume the entire input
- return result.SetBadTimeSpanFailure();
- }
- bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0;
- if (TryTimeToTicks(positive, new TimeSpanToken(dd),
- new TimeSpanToken(hh),
- new TimeSpanToken(mm),
- new TimeSpanToken(ss),
- new TimeSpanToken(ff, leadingZeroes),
- out long ticks))
- {
- if (!positive)
- {
- ticks = -ticks;
- }
- result.parsedTimeSpan = new TimeSpan(ticks);
- return true;
- }
- else
- {
- return result.SetOverflowFailure();
- }
- }
- private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result)
- {
- result = 0;
- int zeroes = 0;
- int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
- return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
- }
- private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result)
- {
- int tmpResult = 0, tmpZeroes = 0;
- int tokenLength = 0;
- while (tokenLength < maxDigitLength)
- {
- char ch = tokenizer.NextChar;
- if (ch < '0' || ch > '9')
- {
- tokenizer.BackOne();
- break;
- }
- tmpResult = tmpResult * 10 + (ch - '0');
- if (tmpResult == 0) tmpZeroes++;
- tokenLength++;
- }
- zeroes = tmpZeroes;
- result = tmpResult;
- return tokenLength >= minDigitLength;
- }
- private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString)
- {
- for (int i = 0; i < enquotedString.Length; i++)
- {
- if (enquotedString[i] != tokenizer.NextChar)
- {
- return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
- /// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
- /// </summary>
- private static bool TryParseTimeSpanConstant(ReadOnlySpan<char> input, ref TimeSpanResult result) =>
- new StringParser().TryParse(input, ref result);
- private ref struct StringParser
- {
- private ReadOnlySpan<char> _str;
- private char _ch;
- private int _pos;
- private int _len;
- internal void NextChar()
- {
- if (_pos < _len)
- {
- _pos++;
- }
- _ch = _pos < _len ?
- _str[_pos] :
- (char)0;
- }
- internal char NextNonDigit()
- {
- int i = _pos;
- while (i < _len)
- {
- char ch = _str[i];
- if (ch < '0' || ch > '9') return ch;
- i++;
- }
- return (char)0;
- }
- internal bool TryParse(ReadOnlySpan<char> input, ref TimeSpanResult result)
- {
- result.parsedTimeSpan = default;
- _str = input;
- _len = input.Length;
- _pos = -1;
- NextChar();
- SkipBlanks();
- bool negative = false;
- if (_ch == '-')
- {
- negative = true;
- NextChar();
- }
- long time;
- if (NextNonDigit() == ':')
- {
- if (!ParseTime(out time, ref result))
- {
- return false;
- };
- }
- else
- {
- int days;
- if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result))
- {
- return false;
- }
- time = days * TimeSpan.TicksPerDay;
- if (_ch == '.')
- {
- NextChar();
- long remainingTime;
- if (!ParseTime(out remainingTime, ref result))
- {
- return false;
- };
- time += remainingTime;
- }
- }
- if (negative)
- {
- time = -time;
- // Allow -0 as well
- if (time > 0)
- {
- return result.SetOverflowFailure();
- }
- }
- else
- {
- if (time < 0)
- {
- return result.SetOverflowFailure();
- }
- }
- SkipBlanks();
- if (_pos < _len)
- {
- return result.SetBadTimeSpanFailure();
- }
- result.parsedTimeSpan = new TimeSpan(time);
- return true;
- }
- internal bool ParseInt(int max, out int i, ref TimeSpanResult result)
- {
- i = 0;
- int p = _pos;
- while (_ch >= '0' && _ch <= '9')
- {
- if ((i & 0xF0000000) != 0)
- {
- return result.SetOverflowFailure();
- }
- i = i * 10 + _ch - '0';
- if (i < 0)
- {
- return result.SetOverflowFailure();
- }
- NextChar();
- }
- if (p == _pos)
- {
- return result.SetBadTimeSpanFailure();
- }
- if (i > max)
- {
- return result.SetOverflowFailure();
- }
- return true;
- }
- internal bool ParseTime(out long time, ref TimeSpanResult result)
- {
- time = 0;
- int unit;
- if (!ParseInt(23, out unit, ref result))
- {
- return false;
- }
- time = unit * TimeSpan.TicksPerHour;
- if (_ch != ':')
- {
- return result.SetBadTimeSpanFailure();
- }
- NextChar();
- if (!ParseInt(59, out unit, ref result))
- {
- return false;
- }
- time += unit * TimeSpan.TicksPerMinute;
- if (_ch == ':')
- {
- NextChar();
- // allow seconds with the leading zero
- if (_ch != '.')
- {
- if (!ParseInt(59, out unit, ref result))
- {
- return false;
- }
- time += unit * TimeSpan.TicksPerSecond;
- }
- if (_ch == '.')
- {
- NextChar();
- int f = (int)TimeSpan.TicksPerSecond;
- while (f > 1 && _ch >= '0' && _ch <= '9')
- {
- f /= 10;
- time += (_ch - '0') * f;
- NextChar();
- }
- }
- }
- return true;
- }
- internal void SkipBlanks()
- {
- while (_ch == ' ' || _ch == '\t') NextChar();
- }
- }
- /// <summary>Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple.</summary>
- private static bool TryParseExactMultipleTimeSpan(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
- {
- if (formats == null)
- {
- return result.SetArgumentNullFailure(nameof(formats));
- }
- if (input.Length == 0)
- {
- return result.SetBadTimeSpanFailure();
- }
- if (formats.Length == 0)
- {
- return result.SetNoFormatSpecifierFailure();
- }
- // Do a loop through the provided formats and see if we can parse succesfully in
- // one of the formats.
- for (int i = 0; i < formats.Length; i++)
- {
- if (formats[i] == null || formats[i].Length == 0)
- {
- return result.SetBadFormatSpecifierFailure();
- }
- // Create a new non-throwing result each time to ensure the runs are independent.
- TimeSpanResult innerResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
- if (TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult))
- {
- result.parsedTimeSpan = innerResult.parsedTimeSpan;
- return true;
- }
- }
- return result.SetBadTimeSpanFailure();
- }
- }
- }
|