// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; namespace System.Buffers.Text { public static partial class Utf8Parser { private enum ComponentParseResult : byte { // Do not change or add values in this enum unless you review every use of the TimeSpanSplitter.Separators field. That field is an "array of four // ComponentParseResults" encoded as a 32-bit integer with each of its four bytes containing one of 0 (NoMoreData), 1 (Colon) or 2 (Period). // (So a value of 0x01010200 means the string parsed as "nn:nn:nn.nnnnnnn") NoMoreData = 0, Colon = 1, Period = 2, ParseFailure = 3, } private struct TimeSpanSplitter { public uint V1; public uint V2; public uint V3; public uint V4; public uint V5; public bool IsNegative; // Encodes an "array of four ComponentParseResults" as a 32-bit integer with each of its four bytes containing one of 0 (NoMoreData), 1 (Colon) or 2 (Period). // (So a value of 0x01010200 means the string parsed as "nn:nn:nn.nnnnnnn") public uint Separators; public bool TrySplitTimeSpan(ReadOnlySpan<byte> source, bool periodUsedToSeparateDay, out int bytesConsumed) { int srcIndex = 0; byte c = default; // Unlike many other data types, TimeSpan allow leading whitespace. while (srcIndex < source.Length) { c = source[srcIndex]; if (!(c == ' ' || c == '\t')) break; srcIndex++; } if (srcIndex == source.Length) { bytesConsumed = 0; return false; } // Check for an option negative sign. ('+' is not allowed.) if (c == Utf8Constants.Minus) { IsNegative = true; srcIndex++; if (srcIndex == source.Length) { bytesConsumed = 0; return false; } } // From here, we terminate on anything that's not a digit, ':' or '.' The '.' is only allowed after at least three components have // been specified. If we see it earlier, we'll assume that's an error and fail out rather than treating it as the end of data. // // Timespan has to start with a number - parse the first one. // if (!TryParseUInt32D(source.Slice(srcIndex), out V1, out int justConsumed)) { bytesConsumed = 0; return false; } srcIndex += justConsumed; // // Split out the second number (if any) For the 'c' format, a period might validly appear here as it;s used both to separate the day and the fraction - however, // the fraction is always the fourth component at earliest, so if we do see a period at this stage, always parse the integer as a regular integer, not as // a fraction. // ComponentParseResult result = ParseComponent(source, neverParseAsFraction: periodUsedToSeparateDay, ref srcIndex, out V2); if (result == ComponentParseResult.ParseFailure) { bytesConsumed = 0; return false; } else if (result == ComponentParseResult.NoMoreData) { bytesConsumed = srcIndex; return true; } else { Debug.Assert(result == ComponentParseResult.Colon || result == ComponentParseResult.Period); Separators |= ((uint)result) << 24; } // // Split out the third number (if any) // result = ParseComponent(source, false, ref srcIndex, out V3); if (result == ComponentParseResult.ParseFailure) { bytesConsumed = 0; return false; } else if (result == ComponentParseResult.NoMoreData) { bytesConsumed = srcIndex; return true; } else { Debug.Assert(result == ComponentParseResult.Colon || result == ComponentParseResult.Period); Separators |= ((uint)result) << 16; } // // Split out the fourth number (if any) // result = ParseComponent(source, false, ref srcIndex, out V4); if (result == ComponentParseResult.ParseFailure) { bytesConsumed = 0; return false; } else if (result == ComponentParseResult.NoMoreData) { bytesConsumed = srcIndex; return true; } else { Debug.Assert(result == ComponentParseResult.Colon || result == ComponentParseResult.Period); Separators |= ((uint)result) << 8; } // // Split out the fifth number (if any) // result = ParseComponent(source, false, ref srcIndex, out V5); if (result == ComponentParseResult.ParseFailure) { bytesConsumed = 0; return false; } else if (result == ComponentParseResult.NoMoreData) { bytesConsumed = srcIndex; return true; } else { Debug.Assert(result == ComponentParseResult.Colon || result == ComponentParseResult.Period); Separators |= (uint)result; } // // There cannot legally be a sixth number. If the next character is a period or colon, treat this as a error as it's likely // to indicate the start of a sixth number. Otherwise, treat as end of parse with data left over. // if (srcIndex != source.Length && (source[srcIndex] == Utf8Constants.Period || source[srcIndex] == Utf8Constants.Colon)) { bytesConsumed = 0; return false; } bytesConsumed = srcIndex; return true; } // // Look for a separator followed by an unsigned integer. // private static ComponentParseResult ParseComponent(ReadOnlySpan<byte> source, bool neverParseAsFraction, ref int srcIndex, out uint value) { if (srcIndex == source.Length) { value = default; return ComponentParseResult.NoMoreData; } byte c = source[srcIndex]; if (c == Utf8Constants.Colon || (c == Utf8Constants.Period && neverParseAsFraction)) { srcIndex++; if (!TryParseUInt32D(source.Slice(srcIndex), out value, out int bytesConsumed)) { value = default; return ComponentParseResult.ParseFailure; } srcIndex += bytesConsumed; return c == Utf8Constants.Colon ? ComponentParseResult.Colon : ComponentParseResult.Period; } else if (c == Utf8Constants.Period) { srcIndex++; if (!TryParseTimeSpanFraction(source.Slice(srcIndex), out value, out int bytesConsumed)) { value = default; return ComponentParseResult.ParseFailure; } srcIndex += bytesConsumed; return ComponentParseResult.Period; } else { value = default; return ComponentParseResult.NoMoreData; } } } } } |