// 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 { /// <summary> /// Parses a TimeSpan at the start of a Utf8 string. /// </summary> /// <param name="source">The Utf8 string to parse</param> /// <param name="value">Receives the parsed value</param> /// <param name="bytesConsumed">On a successful parse, receives the length in bytes of the substring that was parsed </param> /// <param name="standardFormat">Expected format of the Utf8 string</param> /// <returns> /// true for success. "bytesConsumed" contains the length in bytes of the substring that was parsed. /// false if the string was not syntactically valid or an overflow or underflow occurred. "bytesConsumed" is set to 0. /// </returns> /// <remarks> /// Formats supported: /// c/t/T (default) [-][d.]hh:mm:ss[.fffffff] (constant format) /// G [-]d:hh:mm:ss.fffffff (general long) /// g [-][d:]h:mm:ss[.f[f[f[f[f[f[f[]]]]]]] (general short) /// </remarks> /// <exceptions> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryParse(ReadOnlySpan<byte> source, out TimeSpan value, out int bytesConsumed, char standardFormat = default) => standardFormat switch { default(char) or 'c' or 't' or 'T' => TryParseTimeSpanC(source, out value, out bytesConsumed), 'G' => TryParseTimeSpanBigG(source, out value, out bytesConsumed), 'g' => TryParseTimeSpanLittleG(source, out value, out bytesConsumed), _ => ParserHelpers.TryParseThrowFormatException(out value, out bytesConsumed), }; /// <summary> /// Parse the fraction portion of a TimeSpan. Must be 1..7 digits. If fewer than 7, zeroes are implied to the right. If more than 7, the TimeSpan /// parser rejects the string (even if the extra digits are all zeroes.) /// </summary> private static bool TryParseTimeSpanFraction(ReadOnlySpan<byte> source, out uint value, out int bytesConsumed) { int srcIndex = 0; if (srcIndex == source.Length) { value = default; bytesConsumed = 0; return false; } uint digit = source[srcIndex] - 48u; // '0' if (digit > 9) { value = default; bytesConsumed = 0; return false; } srcIndex++; uint fraction = digit; int digitCount = 1; while (srcIndex < source.Length) { digit = source[srcIndex] - 48u; // '0' if (digit > 9) break; srcIndex++; digitCount++; if (digitCount > Utf8Constants.DateTimeNumFractionDigits) { // Yes, TimeSpan fraction parsing is that picky. value = default; bytesConsumed = 0; return false; } fraction = 10 * fraction + digit; } switch (digitCount) { case 7: break; case 6: fraction *= 10; break; case 5: fraction *= 100; break; case 4: fraction *= 1000; break; case 3: fraction *= 10000; break; case 2: fraction *= 100000; break; default: Debug.Assert(digitCount == 1); fraction *= 1000000; break; } value = fraction; bytesConsumed = srcIndex; return true; } /// <summary> /// Overflow-safe TryCreateTimeSpan /// </summary> private static bool TryCreateTimeSpan(bool isNegative, uint days, uint hours, uint minutes, uint seconds, uint fraction, out TimeSpan timeSpan) { const long MaxMilliSeconds = long.MaxValue / TimeSpan.TicksPerMillisecond; const long MinMilliSeconds = long.MinValue / TimeSpan.TicksPerMillisecond; if (hours > 23 || minutes > 59 || seconds > 59) { timeSpan = default; return false; } Debug.Assert(fraction <= Utf8Constants.MaxDateTimeFraction); // This value comes from TryParseTimeSpanFraction() which already rejects any fraction string longer than 7 digits. long millisecondsWithoutFraction = (((long)days) * 3600 * 24 + ((long)hours) * 3600 + ((long)minutes) * 60 + seconds) * 1000; long ticks; if (isNegative) { millisecondsWithoutFraction = -millisecondsWithoutFraction; if (millisecondsWithoutFraction < MinMilliSeconds) { timeSpan = default; return false; } long ticksWithoutFraction = millisecondsWithoutFraction * TimeSpan.TicksPerMillisecond; if (ticksWithoutFraction < long.MinValue + fraction) { timeSpan = default; return false; } ticks = ticksWithoutFraction - fraction; } else { if (millisecondsWithoutFraction > MaxMilliSeconds) { timeSpan = default; return false; } long ticksWithoutFraction = millisecondsWithoutFraction * TimeSpan.TicksPerMillisecond; if (ticksWithoutFraction > long.MaxValue - fraction) { timeSpan = default; return false; } ticks = ticksWithoutFraction + fraction; } timeSpan = new TimeSpan(ticks); return true; } } } |