//
////////////////////////////////////////////////////////////////////////////
-using System.Text;
-using System;
using System.Diagnostics;
-using System.Diagnostics.Contracts;
-using System.Globalization;
using System.Runtime.CompilerServices;
+using System.Text;
namespace System.Globalization
{
internal const int maxFraction = 9999999;
#region InternalSupport
- private enum TimeSpanThrowStyle
- {
- None = 0,
- All = 1,
- }
-
- private enum ParseFailureKind
+ private enum ParseFailureKind : byte
{
None = 0,
ArgumentNull = 1,
}
[Flags]
- private enum TimeSpanStandardStyles
+ private enum TimeSpanStandardStyles : byte
{ // Standard Format Styles
None = 0x00000000,
Invariant = 0x00000001, //Allow Invariant Culture
}
// TimeSpan Token Types
- private enum TTT
+ private enum TTT : byte
{
None = 0, // None of the TimeSpanToken fields are set
End = 1, // '\0'
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(int number)
- {
- ttt = TTT.Num;
- num = number;
- zeroes = 0;
- sep = default(ReadOnlySpan<char>);
- }
+ public TimeSpanToken(TTT type) : this(type, 0, 0, default(ReadOnlySpan<char>)) { }
+
+ public TimeSpanToken(int number) : this(TTT.Num, number, 0, default(ReadOnlySpan<char>)) { }
- public TimeSpanToken(int leadingZeroes, int number)
+ public TimeSpanToken(int number, int leadingZeroes) : this(TTT.Num, number, leadingZeroes, default(ReadOnlySpan<char>)) { }
+
+ public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char> separator)
{
- ttt = TTT.Num;
+ ttt = type;
num = number;
zeroes = leadingZeroes;
- sep = default(ReadOnlySpan<char>);
+ sep = separator;
}
- public bool IsInvalidNumber(int maxValue, int maxPrecision)
+ public bool IsInvalidFraction()
{
Debug.Assert(ttt == TTT.Num);
Debug.Assert(num > -1);
- Debug.Assert(maxValue > 0);
- Debug.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)
+ if (num > maxFraction || zeroes > maxFractionDigits)
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)));
+ return num >= maxFraction / Pow10(zeroes - 1);
}
}
[IsByRefLike]
private struct TimeSpanTokenizer
{
- private int m_pos;
private ReadOnlySpan<char> m_value;
+ private int m_pos;
- internal void Init(ReadOnlySpan<char> input)
- {
- Init(input, 0);
- }
- internal void Init(ReadOnlySpan<char> input, int startPosition)
+ internal TimeSpanTokenizer(ReadOnlySpan<char> input) : this(input, 0) { }
+
+ internal TimeSpanTokenizer(ReadOnlySpan<char> input, int startPosition)
{
- m_pos = startPosition;
m_value = input;
+ m_pos = startPosition;
}
+
// used by the parsing routines that operate on standard-formats
internal TimeSpanToken GetNextToken()
{
- Debug.Assert(m_pos > -1);
-
- TimeSpanToken tok = new TimeSpanToken();
- char ch = CurrentChar;
-
- if (ch == (char)0)
+ // Get the position of the next character to be processed. If there is no
+ // next character, we're at the end.
+ int pos = m_pos;
+ Debug.Assert(pos > -1);
+ if (pos >= m_value.Length)
{
- tok.ttt = TTT.End;
- return tok;
+ return new TimeSpanToken(TTT.End);
}
- if (ch >= '0' && ch <= '9')
+ // Now retrieve that character. If it's a digit, we're processing a number.
+ int num = m_value[pos] - '0';
+ if ((uint)num <= 9)
{
- tok.ttt = TTT.Num;
- tok.num = 0;
- tok.zeroes = 0;
- do
+ int zeroes = 0;
+ if (num == 0)
{
- if ((tok.num & 0xF0000000) != 0)
+ // Read all leading zeroes.
+ zeroes = 1;
+ while (true)
{
- tok.ttt = TTT.NumOverflow;
- return tok;
+ int digit;
+ if (++m_pos >= m_value.Length || (uint)(digit = m_value[m_pos] - '0') > 9)
+ {
+ return new TimeSpanToken(TTT.Num, 0, zeroes, default(ReadOnlySpan<char>));
+ }
+
+ if (digit == 0)
+ {
+ zeroes++;
+ continue;
+ }
+
+ num = digit;
+ break;
+ }
+ }
+
+ // Continue to read as long as we're reading digits.
+ while (++m_pos < m_value.Length)
+ {
+ int digit = m_value[m_pos] - '0';
+ if ((uint)digit > 9)
+ {
+ break;
}
- tok.num = tok.num * 10 + ch - '0';
- if (tok.num == 0) tok.zeroes++;
- if (tok.num < 0)
+
+ num = num * 10 + digit;
+ if ((num & 0xF0000000) != 0)
{
- tok.ttt = TTT.NumOverflow;
- return tok;
+ return new TimeSpanToken(TTT.NumOverflow);
}
- ch = NextChar;
- } while (ch >= '0' && ch <= '9');
- return tok;
+ }
+
+ return new TimeSpanToken(TTT.Num, num, zeroes, default(ReadOnlySpan<char>));
}
- else
- {
- tok.ttt = TTT.Sep;
- int startIndex = m_pos;
- int length = 0;
- while (ch != (char)0 && (ch < '0' || '9' < ch))
+ // 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 (++m_pos >= m_value.Length || (uint)(m_value[m_pos] - '0') <= 9)
{
- ch = NextChar;
- length++;
+ break;
}
- tok.sep = m_value.Slice(startIndex, length);
- return tok;
+ length++;
}
+
+ // Return the separator.
+ return new TimeSpanToken(TTT.Sep, 0, 0, m_value.Slice(pos, length));
}
internal Boolean EOL
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()
{
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;
- }
+ int pos = ++m_pos;
+ return (uint)pos < (uint)m_value.Length ?
+ m_value[pos] :
+ (char)0;
}
}
}
-
-
// This stores intermediary parsing state for the standard formats
[IsByRefLike]
private struct TimeSpanRawInfo
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 (!AddNum(tok, ref result)) return false;
break;
- default:
+ case TTT.Sep:
+ if (!AddSep(tok.sep, ref result)) return false;
break;
+ case TTT.NumOverflow:
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge", null);
+ return false;
+ default:
+ // Some unknown token or a repeat token type in the input
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
+ return false;
}
lastSeenTTT = tok.ttt;
case 2: literals2 = sep; break;
case 3: literals3 = sep; break;
case 4: literals4 = sep; break;
- case 5: literals5 = sep; break;
-#if DEBUG
- default: Debug.Fail($"Expected MaxLiteralTokens == 6"); break;
-#endif
+ default: literals5 = sep; break;
}
tokenCount++;
case 1: numbers1 = num; break;
case 2: numbers2 = num; break;
case 3: numbers3 = num; break;
- case 4: numbers4 = num; break;
-#if DEBUG
- default: Debug.Fail($"Expected MaxNumericTokens == 5"); break;
-#endif
+ default: numbers4 = num; break;
}
tokenCount++;
private 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 readonly bool throwOnFailure;
- internal void Init(TimeSpanThrowStyle canThrow)
+ internal TimeSpanResult(bool canThrow) : this()
{
- parsedTimeSpan = default(TimeSpan);
- throwStyle = canThrow;
+ throwOnFailure = 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()
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, string failureArgumentName)
{
- switch (m_failure)
+ if (throwOnFailure)
{
- case ParseFailureKind.ArgumentNull:
- return new ArgumentNullException(m_failureArgumentName, SR.GetResourceString(m_failureMessageID));
-
- case ParseFailureKind.FormatWithParameter:
- return new FormatException(SR.Format(SR.GetResourceString(m_failureMessageID), m_failureMessageFormatArgument));
+ switch (failure)
+ {
+ case ParseFailureKind.ArgumentNull:
+ throw new ArgumentNullException(failureArgumentName, SR.GetResourceString(failureMessageID));
- case ParseFailureKind.Format:
- return new FormatException(SR.GetResourceString(m_failureMessageID));
+ case ParseFailureKind.FormatWithParameter:
+ throw new FormatException(SR.Format(SR.GetResourceString(failureMessageID), failureMessageFormatArgument));
- case ParseFailureKind.Overflow:
- return new OverflowException(SR.GetResourceString(m_failureMessageID));
+ case ParseFailureKind.Overflow:
+ throw new OverflowException(SR.GetResourceString(failureMessageID));
- default:
- Debug.Assert(false, "Unknown TimeSpanParseFailure: " + m_failure);
- return new FormatException(SR.Format_InvalidString);
+ default:
+ Debug.Assert(failure == ParseFailureKind.Format, $"Unexpected failure {failure}");
+ throw new FormatException(SR.GetResourceString(failureMessageID));
+ }
}
}
}
+ private 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.IsInvalidNumber(maxDays, unlimitedDigits)
- || hours.IsInvalidNumber(maxHours, unlimitedDigits)
- || minutes.IsInvalidNumber(maxMinutes, unlimitedDigits)
- || seconds.IsInvalidNumber(maxSeconds, unlimitedDigits)
- || fraction.IsInvalidNumber(maxFraction, maxFractionDigits))
+ if (days.num > maxDays ||
+ hours.num > maxHours ||
+ minutes.num > maxMinutes ||
+ seconds.num > maxSeconds ||
+ fraction.IsInvalidFraction())
{
result = 0;
return false;
long lowerLimit = TimeSpan.TicksPerTenthSecond;
if (fraction.zeroes > 0)
{
- long divisor = (long)Math.Pow(10, fraction.zeroes);
+ long divisor = Pow10(fraction.zeroes);
lowerLimit = lowerLimit / divisor;
}
while (f < lowerLimit)
#region ParseMethods
internal static TimeSpan Parse(ReadOnlySpan<char> 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();
- }
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: true);
+ bool success = TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
}
internal static Boolean TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, out TimeSpan result)
{
- TimeSpanResult parseResult = new TimeSpanResult();
- parseResult.Init(TimeSpanThrowStyle.None);
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: false);
if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult))
{
}
internal static TimeSpan ParseExact(ReadOnlySpan<char> 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();
- }
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: true);
+ bool success = TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
}
internal static Boolean TryParseExact(ReadOnlySpan<char> input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
{
- TimeSpanResult parseResult = new TimeSpanResult();
- parseResult.Init(TimeSpanThrowStyle.None);
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: false);
if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult))
{
}
internal static TimeSpan ParseExactMultiple(ReadOnlySpan<char> 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();
- }
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: true);
+ bool success = TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult);
+ Debug.Assert(success, "Should have thrown on failure");
+ return parseResult.parsedTimeSpan;
}
internal static Boolean TryParseExactMultiple(ReadOnlySpan<char> input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
{
- TimeSpanResult parseResult = new TimeSpanResult();
- parseResult.Init(TimeSpanThrowStyle.None);
+ TimeSpanResult parseResult = new TimeSpanResult(canThrow: false);
if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult))
{
return false;
}
- TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
- tokenizer.Init(input);
+ TimeSpanTokenizer tokenizer = new TimeSpanTokenizer(input);
TimeSpanRawInfo raw = new TimeSpanRawInfo();
raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
}
tok = tokenizer.GetNextToken();
}
- if (!tokenizer.EOL)
- {
- // embedded nulls in the input string
- result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
- return false;
- }
+ Debug.Assert(tokenizer.EOL);
+
if (!ProcessTerminalState(ref raw, style, ref result))
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
//
private static Boolean ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
{
- if (raw.SepCount != 6 || raw.NumCount != 5)
+ if (raw.SepCount != 6)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
return false;
}
+ Debug.Assert(raw.NumCount == 5);
bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
//
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)
+ if (raw.SepCount != 5 || (style & TimeSpanStandardStyles.RequireFull) != 0)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
return false;
}
+ Debug.Assert(raw.NumCount == 4);
bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
//
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)
+ if (raw.SepCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
return false;
}
+ Debug.Assert(raw.NumCount == 3);
bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
//
private static Boolean ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
{
- if (raw.SepCount != 3 || raw.NumCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ if (raw.SepCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
return false;
}
+ Debug.Assert(raw.NumCount == 2);
bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
//
private static Boolean ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
{
- if (raw.SepCount != 2 || raw.NumCount != 1 || (style & TimeSpanStandardStyles.RequireFull) != 0)
+ if (raw.SepCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
return false;
}
+ Debug.Assert(raw.NumCount == 1);
bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(format));
return false;
}
+
if (format.Length == 0)
{
result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
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
+ switch (format[0])
{
- result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
- return false;
+ 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:
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
+ return false;
}
- return TryParseTimeSpan(input, style, formatProvider, ref result);
}
return TryParseByFormat(input, format, styles, ref result);
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);
+ TimeSpanTokenizer tokenizer = new TimeSpanTokenizer(input, -1);
while (i < format.Length)
{
new TimeSpanToken(hh),
new TimeSpanToken(mm),
new TimeSpanToken(ss),
- new TimeSpanToken(leadingZeroes, ff),
+ new TimeSpanToken(ff, leadingZeroes),
out ticks))
{
if (!positive) ticks = -ticks;
}
// Create a new non-throwing result each time to ensure the runs are independent.
- TimeSpanResult innerResult = new TimeSpanResult();
- innerResult.Init(TimeSpanThrowStyle.None);
+ TimeSpanResult innerResult = new TimeSpanResult(canThrow: false);
if (TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult))
{