// 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())); }