diff options
Diffstat (limited to 'src/mscorlib/src/System/Globalization/TimeSpanParse.cs')
-rw-r--r-- | src/mscorlib/src/System/Globalization/TimeSpanParse.cs | 1556 |
1 files changed, 1556 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/TimeSpanParse.cs b/src/mscorlib/src/System/Globalization/TimeSpanParse.cs new file mode 100644 index 0000000000..7588c0a1d1 --- /dev/null +++ b/src/mscorlib/src/System/Globalization/TimeSpanParse.cs @@ -0,0 +1,1556 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// +//////////////////////////////////////////////////////////////////////////// +// +// +// Purpose: This class is called 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. +// +//////////////////////////////////////////////////////////////////////////// +namespace System.Globalization { + using System.Text; + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + + internal static class TimeSpanParse { + // ---- SECTION: members for internal support ---------* + internal static void ValidateStyles(TimeSpanStyles style, String parameterName) { + if (style != TimeSpanStyles.None && style != TimeSpanStyles.AssumeNegative) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidTimeSpanStyles"), parameterName); + } + + internal const int unlimitedDigits = -1; + internal const int maxFractionDigits = 7; + + internal const int maxDays = 10675199; + internal const int maxHours = 23; + internal const int maxMinutes = 59; + internal const int maxSeconds = 59; + internal const int maxFraction = 9999999; + + #region InternalSupport + enum TimeSpanThrowStyle { + None = 0, + All = 1, + } + + private enum ParseFailureKind { + None = 0, + ArgumentNull = 1, + Format = 2, + FormatWithParameter = 3, + Overflow = 4, + } + + [Flags] + enum TimeSpanStandardStyles { // 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 { + None = 0, // None of the TimeSpanToken fields are set + End = 1, // '\0' + Num = 2, // Number + Sep = 3, // literal + NumOverflow = 4, // Number that overflowed + } + + private static readonly TimeSpanToken zero = new TimeSpanToken(0); + 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 String sep; // Store the literal that we are parsing (if any) + + public TimeSpanToken(int number) { + ttt = TTT.Num; + num = number; + zeroes = 0; + sep = null; + } + + public TimeSpanToken(int leadingZeroes, int number) { + ttt = TTT.Num; + num = number; + zeroes = leadingZeroes; + sep = null; + } + + public bool IsInvalidNumber(int maxValue, int maxPrecision) { + Contract.Assert(ttt == TTT.Num); + Contract.Assert(num > -1); + Contract.Assert(maxValue > 0); + Contract.Assert(maxPrecision == maxFractionDigits || maxPrecision == unlimitedDigits); + + if (num > maxValue) + return true; + if (maxPrecision == unlimitedDigits) + return false; // all validation past this point applies only to fields with precision limits + if (zeroes > maxPrecision) + return true; + if (num == 0 || zeroes == 0) + return false; + + // num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision + return (num >= (maxValue/(long)Math.Pow(10, zeroes-1))); + } + } + + // + // TimeSpanTokenizer + // + // Actions: TimeSpanTokenizer.GetNextToken() returns the next token in the input string. + // + struct TimeSpanTokenizer { + private int m_pos; + private String m_value; + + internal void Init(String input) { + Init(input, 0); + } + internal void Init(String input, int startPosition) { + m_pos = startPosition; + m_value = input; + } + // used by the parsing routines that operate on standard-formats + internal TimeSpanToken GetNextToken() { + Contract.Assert(m_pos > -1); + + TimeSpanToken tok = new TimeSpanToken(); + char ch = CurrentChar; + + if (ch == (char)0) { + tok.ttt = TTT.End; + return tok; + } + + if (ch >= '0' && ch <= '9') { + tok.ttt = TTT.Num; + tok.num = 0; + tok.zeroes = 0; + do { + if ((tok.num & 0xF0000000) != 0) { + tok.ttt = TTT.NumOverflow; + return tok; + } + tok.num = tok.num * 10 + ch - '0'; + if (tok.num == 0) tok.zeroes++; + if (tok.num < 0) { + tok.ttt = TTT.NumOverflow; + return tok; + } + ch = NextChar; + } while (ch >= '0' && ch <= '9'); + return tok; + } + else { + tok.ttt = TTT.Sep; + int startIndex = m_pos; + int length = 0; + + while (ch != (char)0 && (ch < '0' || '9' < ch)) { + ch = NextChar; + length++; + } + tok.sep = m_value.Substring(startIndex, length); + return tok; + } + } + + internal Boolean EOL { + get { + return m_pos >= (m_value.Length-1); + } + } + // BackOne, NextChar, CurrentChar - used by ParseExact (ParseByFormat) to operate + // on custom-formats where exact character-by-character control is allowed + internal void BackOne() { + if (m_pos > 0) --m_pos; + } + + internal char NextChar { + get { + m_pos++; + return CurrentChar; + } + } + internal char CurrentChar { + get { + if (m_pos > -1 && m_pos < m_value.Length) { + return m_value[m_pos]; + } + else { + return (char) 0; + } + } + } + } + + + + // This stores intermediary parsing state for the standard formats + struct TimeSpanRawInfo { + internal TimeSpanFormat.FormatLiterals PositiveInvariant { + get { + return TimeSpanFormat.PositiveInvariantFormatLiterals; + } + } + internal TimeSpanFormat.FormatLiterals NegativeInvariant { + get { + return TimeSpanFormat.NegativeInvariantFormatLiterals; + } + } + + internal TimeSpanFormat.FormatLiterals PositiveLocalized { + get { + if (!m_posLocInit) { + m_posLoc = new TimeSpanFormat.FormatLiterals(); + m_posLoc.Init(m_fullPosPattern, false); + m_posLocInit = true; + } + return m_posLoc; + } + } + internal TimeSpanFormat.FormatLiterals NegativeLocalized { + get { + if (!m_negLocInit) { + m_negLoc = new TimeSpanFormat.FormatLiterals(); + m_negLoc.Init(m_fullNegPattern, false); + m_negLocInit = true; + } + return m_negLoc; + } + } + + internal Boolean FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 5 + && NumCount == 4 + && pattern.Start == literals[0] + && pattern.DayHourSep == literals[1] + && pattern.HourMinuteSep == literals[2] + && pattern.AppCompatLiteral == literals[3] + && pattern.End == literals[4]; + } + + internal Boolean PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 4 + && NumCount == 3 + && pattern.Start == literals[0] + && pattern.HourMinuteSep == literals[1] + && pattern.AppCompatLiteral == literals[2] + && pattern.End == literals[3]; + } + // DHMSF (all values matched) + internal Boolean FullMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == MaxLiteralTokens + && NumCount == MaxNumericTokens + && pattern.Start == literals[0] + && pattern.DayHourSep == literals[1] + && pattern.HourMinuteSep == literals[2] + && pattern.MinuteSecondSep == literals[3] + && pattern.SecondFractionSep == literals[4] + && pattern.End == literals[5]; + } + // D (no hours, minutes, seconds, or fractions) + internal Boolean FullDMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 2 + && NumCount == 1 + && pattern.Start == literals[0] + && pattern.End == literals[1]; + } + // HM (no days, seconds, or fractions) + internal Boolean FullHMMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 3 + && NumCount == 2 + && pattern.Start == literals[0] + && pattern.HourMinuteSep == literals[1] + && pattern.End == literals[2]; + } + // DHM (no seconds or fraction) + internal Boolean FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 4 + && NumCount == 3 + && pattern.Start == literals[0] + && pattern.DayHourSep == literals[1] + && pattern.HourMinuteSep == literals[2] + && pattern.End == literals[3]; + + } + // HMS (no days or fraction) + internal Boolean FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 4 + && NumCount == 3 + && pattern.Start == literals[0] + && pattern.HourMinuteSep == literals[1] + && pattern.MinuteSecondSep == literals[2] + && pattern.End == literals[3]; + } + // DHMS (no fraction) + internal Boolean FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 5 + && NumCount == 4 + && pattern.Start == literals[0] + && pattern.DayHourSep == literals[1] + && pattern.HourMinuteSep == literals[2] + && pattern.MinuteSecondSep == literals[3] + && pattern.End == literals[4]; + } + // HMSF (no days) + internal Boolean FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) { + return SepCount == 5 + && NumCount == 4 + && pattern.Start == literals[0] + && pattern.HourMinuteSep == literals[1] + && pattern.MinuteSecondSep == literals[2] + && pattern.SecondFractionSep == literals[3] + && pattern.End == literals[4]; + } + + internal TTT lastSeenTTT; + internal int tokenCount; + internal int SepCount; + internal int NumCount; + internal String[] literals; + internal TimeSpanToken[] numbers; // raw numbers + + private TimeSpanFormat.FormatLiterals m_posLoc; + private TimeSpanFormat.FormatLiterals m_negLoc; + private Boolean m_posLocInit; + private Boolean m_negLocInit; + private String m_fullPosPattern; + private String m_fullNegPattern; + + private const int MaxTokens = 11; + private const int MaxLiteralTokens = 6; + private const int MaxNumericTokens = 5; + + internal void Init(DateTimeFormatInfo dtfi) { + Contract.Assert(dtfi != null); + + lastSeenTTT = TTT.None; + tokenCount = 0; + SepCount = 0; + NumCount = 0; + + literals = new String[MaxLiteralTokens]; + numbers = new TimeSpanToken[MaxNumericTokens]; + + m_fullPosPattern = dtfi.FullTimeSpanPositivePattern; + m_fullNegPattern = dtfi.FullTimeSpanNegativePattern; + m_posLocInit = false; + m_negLocInit = false; + } + + internal Boolean ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result) { + if (tok.ttt == TTT.NumOverflow) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge", null); + return false; + } + if (tok.ttt != TTT.Sep && tok.ttt != TTT.Num) { + // Some unknown token or a repeat token type in the input + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null); + return false; + } + + switch (tok.ttt) { + case TTT.Sep: + if (!AddSep(tok.sep, ref result)) return false; + break; + case TTT.Num: + if (tokenCount == 0) { + if (!AddSep(String.Empty, ref result)) return false; + } + if (!AddNum(tok, ref result)) return false; + break; + default: + break; + } + + lastSeenTTT = tok.ttt; + Contract.Assert(tokenCount == (SepCount + NumCount), "tokenCount == (SepCount + NumCount)"); + return true; + } + + private bool AddSep(String sep, ref TimeSpanResult result) { + if (SepCount >= MaxLiteralTokens || tokenCount >= MaxTokens) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null); + return false; + } + literals[SepCount++] = sep; + tokenCount++; + return true; + } + private bool AddNum(TimeSpanToken num, ref TimeSpanResult result) { + if (NumCount >= MaxNumericTokens || tokenCount >= MaxTokens) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null); + return false; + } + numbers[NumCount++] = num; + tokenCount++; + return true; + } + } + + // This will store the result of the parsing. And it will eventually be used to construct a TimeSpan instance. + struct TimeSpanResult { + internal TimeSpan parsedTimeSpan; + internal TimeSpanThrowStyle throwStyle; + + internal ParseFailureKind m_failure; + internal string m_failureMessageID; + internal object m_failureMessageFormatArgument; + internal string m_failureArgumentName; + + internal void Init(TimeSpanThrowStyle canThrow) { + parsedTimeSpan = default(TimeSpan); + throwStyle = canThrow; + } + internal void SetFailure(ParseFailureKind failure, string failureMessageID) { + SetFailure(failure, failureMessageID, null, null); + } + internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) { + SetFailure(failure, failureMessageID, failureMessageFormatArgument, null); + } + internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, + string failureArgumentName) { + m_failure = failure; + m_failureMessageID = failureMessageID; + m_failureMessageFormatArgument = failureMessageFormatArgument; + m_failureArgumentName = failureArgumentName; + if (throwStyle != TimeSpanThrowStyle.None) { + throw GetTimeSpanParseException(); + } + } + + internal Exception GetTimeSpanParseException() { + switch (m_failure) { + case ParseFailureKind.ArgumentNull: + return new ArgumentNullException(m_failureArgumentName, Environment.GetResourceString(m_failureMessageID)); + + case ParseFailureKind.FormatWithParameter: + return new FormatException(Environment.GetResourceString(m_failureMessageID, m_failureMessageFormatArgument)); + + case ParseFailureKind.Format: + return new FormatException(Environment.GetResourceString(m_failureMessageID)); + + case ParseFailureKind.Overflow: + return new OverflowException(Environment.GetResourceString(m_failureMessageID)); + + default: + Contract.Assert(false, "Unknown TimeSpanParseFailure: " + m_failure); + return new FormatException(Environment.GetResourceString("Format_InvalidString")); + } + } + } + + static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result) { + if (days.IsInvalidNumber(maxDays, unlimitedDigits) + || hours.IsInvalidNumber(maxHours, unlimitedDigits) + || minutes.IsInvalidNumber(maxMinutes, unlimitedDigits) + || seconds.IsInvalidNumber(maxSeconds, unlimitedDigits) + || fraction.IsInvalidNumber(maxFraction, maxFractionDigits)) { + result = 0; + return false; + } + + Int64 ticks = ((Int64)days.num * 3600 * 24 + (Int64)hours.num * 3600 + (Int64)minutes.num * 60 + seconds.num) * 1000; + if (ticks > TimeSpan.MaxMilliSeconds || ticks < TimeSpan.MinMilliSeconds) { + result = 0; + return false; + } + + // Normalize the fraction component + // + // string representation => (zeroes,num) => resultant fraction ticks + // --------------------- ------------ ------------------------ + // ".9999999" => (0,9999999) => 9,999,999 ticks (same as constant maxFraction) + // ".1" => (0,1) => 1,000,000 ticks + // ".01" => (1,1) => 100,000 ticks + // ".001" => (2,1) => 10,000 ticks + long f = fraction.num; + if (f != 0) { + long lowerLimit = TimeSpan.TicksPerTenthSecond; + if (fraction.zeroes > 0) { + long divisor = (long)Math.Pow(10, fraction.zeroes); + lowerLimit = lowerLimit / divisor; + } + while (f < lowerLimit) { + f *= 10; + } + } + result = ((long)ticks * TimeSpan.TicksPerMillisecond) + f; + if (positive && result < 0) { + result = 0; + return false; + } + return true; + } + #endregion + + + // ---- SECTION: internal static methods called by System.TimeSpan ---------* + // + // [Try]Parse, [Try]ParseExact, and [Try]ParseExactMultiple + // + // Actions: Main methods called from TimeSpan.Parse + #region ParseMethods + internal static TimeSpan Parse(String input, IFormatProvider formatProvider) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.All); + + if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) { + return parseResult.parsedTimeSpan; + } + else { + throw parseResult.GetTimeSpanParseException(); + } + } + internal static Boolean TryParse(String input, IFormatProvider formatProvider, out TimeSpan result) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.None); + + if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) { + result = parseResult.parsedTimeSpan; + return true; + } + else { + result = default(TimeSpan); + return false; + } + } + internal static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.All); + + if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) { + return parseResult.parsedTimeSpan; + } + else { + throw parseResult.GetTimeSpanParseException(); + } + } + internal static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.None); + + if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) { + result = parseResult.parsedTimeSpan; + return true; + } + else { + result = default(TimeSpan); + return false; + } + } + internal static TimeSpan ParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.All); + + if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) { + return parseResult.parsedTimeSpan; + } + else { + throw parseResult.GetTimeSpanParseException(); + } + } + internal static Boolean TryParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { + TimeSpanResult parseResult = new TimeSpanResult(); + parseResult.Init(TimeSpanThrowStyle.None); + + if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) { + result = parseResult.parsedTimeSpan; + return true; + } + else { + result = default(TimeSpan); + return false; + } + } + #endregion + + + // ---- SECTION: private static methods that do the actual work ---------* + #region TryParseTimeSpan + // + // TryParseTimeSpan + // + // Actions: Common private Parse method called by both Parse and TryParse + // + private static Boolean TryParseTimeSpan(String input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result) { + if (input == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input"); + return false; + } + + input = input.Trim(); + if (input == String.Empty) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + TimeSpanTokenizer tokenizer = new TimeSpanTokenizer(); + tokenizer.Init(input); + + TimeSpanRawInfo 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)) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + tok = tokenizer.GetNextToken(); + } + if (!tokenizer.EOL) { + // embedded nulls in the input string + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + if (!ProcessTerminalState(ref raw, style, ref result)) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + return true; + } + + + + // + // ProcessTerminalState + // + // Actions: Validate the terminal state of a standard format parse. + // Sets result.parsedTimeSpan on success. + // + // Calculates the resultant TimeSpan from the TimeSpanRawInfo + // + // 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 + private static Boolean ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.lastSeenTTT == TTT.Num) { + TimeSpanToken tok = new TimeSpanToken(); + tok.ttt = TTT.Sep; + tok.sep = String.Empty; + if (!raw.ProcessToken(ref tok, ref result)) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + } + + 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: + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + } + + // + // ProcessTerminal_DHMSF + // + // Actions: Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case. + // Sets result.parsedTimeSpan on success. + // + private static Boolean ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.SepCount != 6 || raw.NumCount != 5) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + 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; + } + } + long ticks; + if (match) { + if (!TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], raw.numbers[4], out ticks)) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + if (!positive) { + ticks = -ticks; + if (ticks > 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + result.parsedTimeSpan._ticks = ticks; + return true; + } + + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + // + // ProcessTerminal_HMS_F_D + // + // Actions: Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds", or "Days.Hours:Minutes:.Fraction" terminal case. + // Sets result.parsedTimeSpan on success. + // + private static Boolean ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.SepCount != 5 || raw.NumCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0); + bool loc = ((style & TimeSpanStandardStyles.Localized) != 0); + + long ticks = 0; + bool positive = false; + bool match = false; + bool overflow = false; + + if (inv) { + if (raw.FullHMSFMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMSMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullHMSFMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMSMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks); + overflow = overflow || !match; + } + } + if (loc) { + if (!match && raw.FullHMSFMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMSMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullHMSFMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMSMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks); + overflow = overflow || !match; + } + } + + if (match) { + if (!positive) { + ticks = -ticks; + if (ticks > 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + result.parsedTimeSpan._ticks = ticks; + return true; + } + + if (overflow) { + // we found at least one literal pattern match but the numbers just didn't fit + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + else { + // we couldn't find a thing + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + } + + // + // ProcessTerminal_HM_S_D + // + // Actions: Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case + // + private static Boolean ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.SepCount != 4 || raw.NumCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0); + bool loc = ((style & TimeSpanStandardStyles.Localized) != 0); + + bool positive = false; + bool match = false; + bool overflow = false; + + long ticks = 0; + + if (inv) { + if (raw.FullHMSMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullHMSMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks); + overflow = overflow || !match; + } + } + if (loc) { + if (!match && raw.FullHMSMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized)) { + positive = true; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullHMSMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.FullDHMMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks); + overflow = overflow || !match; + } + if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized)) { + positive = false; + match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks); + overflow = overflow || !match; + } + } + + if (match) { + if (!positive) { + ticks = -ticks; + if (ticks > 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + result.parsedTimeSpan._ticks = ticks; + return true; + } + + if (overflow) { + // we found at least one literal pattern match but the numbers just didn't fit + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + else { + // we couldn't find a thing + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + } + + // + // ProcessTerminal_HM + // + // Actions: Validate the 2-number "Hours:Minutes" terminal case + // + private static Boolean ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.SepCount != 3 || raw.NumCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0); + bool loc = ((style & TimeSpanStandardStyles.Localized) != 0); + + bool positive = false; + bool 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; + } + } + + long ticks = 0; + if (match) { + if (!TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, zero, out ticks)) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + if (!positive) { + ticks = -ticks; + if (ticks > 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + result.parsedTimeSpan._ticks = ticks; + return true; + } + + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + + // + // ProcessTerminal_D + // + // Actions: Validate the 1-number "Days" terminal case + // + private static Boolean ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) { + if (raw.SepCount != 2 || raw.NumCount != 1 || (style & TimeSpanStandardStyles.RequireFull) != 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0); + bool loc = ((style & TimeSpanStandardStyles.Localized) != 0); + + bool positive = false; + bool 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; + } + } + + long ticks = 0; + if (match) { + if (!TryTimeToTicks(positive, raw.numbers[0], zero, zero, zero, zero, out ticks)) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + if (!positive) { + ticks = -ticks; + if (ticks > 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + result.parsedTimeSpan._ticks = ticks; + return true; + } + + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + #endregion + + #region TryParseExactTimeSpan + // + // TryParseExactTimeSpan + // + // Actions: Common private ParseExact method called by both ParseExact and TryParseExact + // + private static Boolean TryParseExactTimeSpan(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) { + if (input == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input"); + return false; + } + if (format == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format"); + return false; + } + if (format.Length == 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier"); + return false; + } + + if (format.Length == 1) { + TimeSpanStandardStyles style = TimeSpanStandardStyles.None; + + if (format[0] == 'c' || format[0] == 't' || format[0] == 'T') { + // fast path for legacy style TimeSpan formats. + return TryParseTimeSpanConstant(input, ref result); + } + else if (format[0] == 'g') { + style = TimeSpanStandardStyles.Localized; + } + else if (format[0] == 'G') { + style = TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull; + } + else { + result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier"); + return false; + } + return TryParseTimeSpan(input, style, formatProvider, ref result); + } + + return TryParseByFormat(input, format, styles, ref result); + } + + // + // TryParseByFormat + // + // Actions: Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan. + // + private static Boolean TryParseByFormat(String input, String format, TimeSpanStyles styles, ref TimeSpanResult result) { + Contract.Assert(input != null, "input != null"); + Contract.Assert(format != null, "format != null"); + + 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' + + TimeSpanTokenizer tokenizer = new TimeSpanTokenizer(); + tokenizer.Init(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)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + seenHH = true; + break; + case 'm': + tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch); + if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + seenMM = true; + break; + case 's': + tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch); + if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + 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)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + seenFF = true; + break; + case 'F': + tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch); + if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + 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)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + seenDD = true; + break; + case '\'': + case '\"': + StringBuilder enquotedString = new StringBuilder(); + if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen)) { + result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch); + return false; + } + if (!ParseExactLiteral(ref tokenizer, enquotedString)) { + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + 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 != (int)'%') { + 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. + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + 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. + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + break; + default: + result.SetFailure(ParseFailureKind.Format, "Format_InvalidString"); + return false; + } + i += tokenLen; + } + + + if (!tokenizer.EOL) { + // the custom format didn't consume the entire input + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + long ticks = 0; + bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0; + if (TryTimeToTicks(positive, new TimeSpanToken(dd), + new TimeSpanToken(hh), + new TimeSpanToken(mm), + new TimeSpanToken(ss), + new TimeSpanToken(leadingZeroes, ff), + out ticks)) { + if (!positive) ticks = -ticks; + result.parsedTimeSpan._ticks = ticks; + return true; + } + else { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + + } + } + + private static Boolean 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 Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result) { + result = 0; + zeroes = 0; + + int tokenLength = 0; + while (tokenLength < maxDigitLength) { + char ch = tokenizer.NextChar; + if (ch < '0' || ch > '9') { + tokenizer.BackOne(); + break; + } + result = result * 10 + (ch - '0'); + if (result == 0) zeroes++; + tokenLength++; + } + return (tokenLength >= minDigitLength); + } + private static Boolean ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString) { + for (int i = 0; i < enquotedString.Length; i++) { + if (enquotedString[i] != tokenizer.NextChar) + return false; + } + return true; + } + #endregion + + #region TryParseTimeSpanConstant + // + // TryParseTimeSpanConstant + // + // Actions: 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. + // + private static Boolean TryParseTimeSpanConstant(String input, ref TimeSpanResult result) { + return (new StringParser().TryParse(input, ref result)); + } + + private struct StringParser { + private String 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(String input, ref TimeSpanResult result) { + result.parsedTimeSpan._ticks = 0; + + if (input == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input"); + return false; + } + 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) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + else { + if (time < 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + } + SkipBlanks(); + if (pos < len) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + result.parsedTimeSpan._ticks = 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) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + i = i * 10 + ch - '0'; + if (i < 0) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + NextChar(); + } + if (p == pos) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + if (i > max) { + result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge"); + return false; + } + 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 != ':') { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + 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(); + } + } + #endregion + + #region TryParseExactMultipleTimeSpan + // + // TryParseExactMultipleTimeSpan + // + // Actions: Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple + // + private static Boolean TryParseExactMultipleTimeSpan(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) { + if (input == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input"); + return false; + } + if (formats == null) { + result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats"); + return false; + } + + if (input.Length == 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return false; + } + + if (formats.Length == 0) { + result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier"); + return false; + } + + // + // 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) { + result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier"); + return false; + } + + // Create a new non-throwing result each time to ensure the runs are independent. + TimeSpanResult innerResult = new TimeSpanResult(); + innerResult.Init(TimeSpanThrowStyle.None); + + if(TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult)) { + result.parsedTimeSpan = innerResult.parsedTimeSpan; + return true; + } + } + + result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan"); + return (false); + } + #endregion + } +} |