// TODO(misc): Tidy this up a *lot*. public ParseResult <Period> Parse(string text) { if (text is null) { return(ParseResult <Period> .ArgumentNull("text")); } if (text.Length == 0) { return(ParseResult <Period> .ValueStringEmpty); } ValueCursor valueCursor = new ValueCursor(text); valueCursor.MoveNext(); if (valueCursor.Current != 'P') { return(ParseResult <Period> .MismatchedCharacter(valueCursor, 'P')); } bool inDate = true; PeriodBuilder builder = new PeriodBuilder(); PeriodUnits unitsSoFar = 0; while (valueCursor.MoveNext()) { if (inDate && valueCursor.Current == 'T') { inDate = false; continue; } bool negative = valueCursor.Current == '-'; var failure = valueCursor.ParseInt64 <Period>(out long unitValue); if (failure != null) { return(failure); } if (valueCursor.Length == valueCursor.Index) { return(ParseResult <Period> .EndOfString(valueCursor)); } // Various failure cases: // - Repeated unit (e.g. P1M2M) // - Time unit is in date part (e.g. P5M) // - Date unit is in time part (e.g. PT1D) // - Unit is in incorrect order (e.g. P5D1Y) // - Unit is invalid (e.g. P5J) // - Unit is missing (e.g. P5) PeriodUnits unit; switch (valueCursor.Current) { case 'Y': unit = PeriodUnits.Years; break; case 'M': unit = inDate ? PeriodUnits.Months : PeriodUnits.Minutes; break; case 'W': unit = PeriodUnits.Weeks; break; case 'D': unit = PeriodUnits.Days; break; case 'H': unit = PeriodUnits.Hours; break; case 'S': unit = PeriodUnits.Seconds; break; case ',': case '.': unit = PeriodUnits.Nanoseconds; break; // Special handling below default: return(InvalidUnit(valueCursor, valueCursor.Current)); } if ((unit & unitsSoFar) != 0) { return(RepeatedUnit(valueCursor, valueCursor.Current)); } // This handles putting months before years, for example. Less significant units // have higher integer representations. if (unit < unitsSoFar) { return(MisplacedUnit(valueCursor, valueCursor.Current)); } // The result of checking "there aren't any time units in this unit" should be // equal to "we're still in the date part". if ((unit & PeriodUnits.AllTimeUnits) == 0 != inDate) { return(MisplacedUnit(valueCursor, valueCursor.Current)); } // Seen a . or , which need special handling. if (unit == PeriodUnits.Nanoseconds) { // Check for already having seen seconds, e.g. PT5S0.5 if ((unitsSoFar & PeriodUnits.Seconds) != 0) { return(MisplacedUnit(valueCursor, valueCursor.Current)); } builder.Seconds = unitValue; if (!valueCursor.MoveNext()) { return(ParseResult <Period> .MissingNumber(valueCursor)); } // Can cope with at most 999999999 nanoseconds if (!valueCursor.ParseFraction(9, 9, out int totalNanoseconds, 1)) { return(ParseResult <Period> .MissingNumber(valueCursor)); } // Use whether or not the seconds value was negative (even if 0) // as the indication of whether this value is negative. if (negative) { totalNanoseconds = -totalNanoseconds; } builder.Milliseconds = (totalNanoseconds / NanosecondsPerMillisecond) % MillisecondsPerSecond; builder.Ticks = (totalNanoseconds / NanosecondsPerTick) % TicksPerMillisecond; builder.Nanoseconds = totalNanoseconds % NanosecondsPerTick; if (valueCursor.Current != 'S') { return(ParseResult <Period> .MismatchedCharacter(valueCursor, 'S')); } if (valueCursor.MoveNext()) { return(ParseResult <Period> .ExpectedEndOfString(valueCursor)); } return(ParseResult <Period> .ForValue(builder.Build())); } builder[unit] = unitValue; unitsSoFar |= unit; } if (unitsSoFar == 0) { return(ParseResult <Period> .ForInvalidValue(valueCursor, TextErrorMessages.EmptyPeriod)); } return(ParseResult <Period> .ForValue(builder.Build())); }
/// <summary> /// Parses digits at the current point in the string as a signed 64-bit integer value. /// Currently this method only supports cultures whose negative sign is "-" (and /// using ASCII digits). /// </summary> /// <param name="result">The result integer value. The value of this is not guaranteed /// to be anything specific if the return value is non-null.</param> /// <returns>null if the digits were parsed, or the appropriate parse failure</returns> internal ParseResult <T> ParseInt64 <T>(out long result) { unchecked { result = 0L; int startIndex = Index; bool negative = Current == '-'; if (negative) { if (!MoveNext()) { Move(startIndex); return(ParseResult <T> .EndOfString(this)); } } int count = 0; int digit; while (result < 922337203685477580 && (digit = GetDigit()) != -1) { result = result * 10 + digit; count++; if (!MoveNext()) { break; } } if (count == 0) { Move(startIndex); return(ParseResult <T> .MissingNumber(this)); } if (result >= 922337203685477580 && (digit = GetDigit()) != -1) { if (result > 922337203685477580) { return(BuildNumberOutOfRangeResult <T>(startIndex)); } if (negative && digit == 8) { MoveNext(); result = long.MinValue; return(null); } if (digit > 7) { return(BuildNumberOutOfRangeResult <T>(startIndex)); } // We know we can cope with this digit... result = result * 10 + digit; MoveNext(); if (GetDigit() != -1) { // Too many digits. Die. return(BuildNumberOutOfRangeResult <T>(startIndex)); } } if (negative) { result = -result; } return(null); } }