File: src\libraries\System.Private.CoreLib\src\System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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;
        }
    }
}