summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser
diff options
context:
space:
mode:
authorTanner Gooding <tagoo@outlook.com>2018-11-08 17:58:24 -0800
committerJan Kotas <jkotas@microsoft.com>2018-11-09 06:14:46 -0800
commit0fccc78cfea93bafbba07cc4a84a32582a3af88f (patch)
treedc73e9450660f41ed8d225804f48a30322f85086 /src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser
parent00f5934a3e34977c7a1502da604f2dae90040888 (diff)
downloadcoreclr-0fccc78cfea93bafbba07cc4a84a32582a3af88f.tar.gz
coreclr-0fccc78cfea93bafbba07cc4a84a32582a3af88f.tar.bz2
coreclr-0fccc78cfea93bafbba07cc4a84a32582a3af88f.zip
Moving the Utf8Parser/Utf8Formatter to be shared (dotnet/corefx#33348)
Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser')
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/ParserHelpers.cs55
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs61
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs102
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.G.cs177
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs162
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.O.cs290
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.R.cs221
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs149
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs82
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs148
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs243
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs443
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.N.cs383
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs199
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs354
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.N.cs336
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs341
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs192
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs246
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.BigG.cs132
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.C.cs67
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.LittleG.cs62
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs192
-rw-r--r--src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpanSplitter.cs225
24 files changed, 4862 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/ParserHelpers.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/ParserHelpers.cs
new file mode 100644
index 0000000000..b527433a7d
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/ParserHelpers.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers.Text
+{
+ internal static class ParserHelpers
+ {
+ public const int ByteOverflowLength = 3;
+ public const int ByteOverflowLengthHex = 2;
+ public const int UInt16OverflowLength = 5;
+ public const int UInt16OverflowLengthHex = 4;
+ public const int UInt32OverflowLength = 10;
+ public const int UInt32OverflowLengthHex = 8;
+ public const int UInt64OverflowLength = 20;
+ public const int UInt64OverflowLengthHex = 16;
+
+ public const int SByteOverflowLength = 3;
+ public const int SByteOverflowLengthHex = 2;
+ public const int Int16OverflowLength = 5;
+ public const int Int16OverflowLengthHex = 4;
+ public const int Int32OverflowLength = 10;
+ public const int Int32OverflowLengthHex = 8;
+ public const int Int64OverflowLength = 19;
+ public const int Int64OverflowLengthHex = 16;
+
+ public static readonly byte[] s_hexLookup =
+ {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
+ 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
+ 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsDigit(int i)
+ {
+ return (uint)(i - '0') <= ('9' - '0');
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs
new file mode 100644
index 0000000000..41c57143a8
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Boolean.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Binary;
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a Boolean 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:
+ /// G (default) True/False
+ /// l true/false
+ /// </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 bool value, out int bytesConsumed, char standardFormat = default)
+ {
+ if (!(standardFormat == default(char) || standardFormat == 'G' || standardFormat == 'l'))
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+
+ if (source.Length >= 4)
+ {
+ int dw = BinaryPrimitives.ReadInt32LittleEndian(source) & ~0x20202020;
+ if (dw == 0x45555254 /* 'EURT' */)
+ {
+ bytesConsumed = 4;
+ value = true;
+ return true;
+ }
+
+ if (source.Length >= 5)
+ {
+ if (dw == 0x534c4146 /* 'SLAF' */ && (source[4] & ~0x20) == 'E')
+ {
+ bytesConsumed = 5;
+ value = false;
+ return true;
+ }
+ }
+ }
+
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs
new file mode 100644
index 0000000000..73578ea88c
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ //
+ // ToString() format for DateTimeOffset. Does not have a corresponding format symbol but it
+ // is the "G" format postpended with the UTC offset.
+ //
+ // 01234567890123456789012345
+ // --------------------------
+ // 05/25/2017 10:30:15 -08:00
+ //
+ private static bool TryParseDateTimeOffsetDefault(ReadOnlySpan<byte> source, out DateTimeOffset value, out int bytesConsumed)
+ {
+ if (source.Length < 26)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ if (!TryParseDateTimeG(source, out DateTime dateTime, out _, out _))
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ if (source[19] != Utf8Constants.Space)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ byte sign = source[20];
+ if (sign != Utf8Constants.Plus && sign != Utf8Constants.Minus)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ int offsetHours;
+ {
+ uint digit1 = source[21] - 48u; // '0'
+ uint digit2 = source[22] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ offsetHours = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[23] != Utf8Constants.Colon)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ int offsetMinutes;
+ {
+ uint digit1 = source[24] - 48u; // '0'
+ uint digit2 = source[25] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ offsetMinutes = (int)(digit1 * 10 + digit2);
+ }
+
+ TimeSpan offset = new TimeSpan(hours: offsetHours, minutes: offsetMinutes, seconds: 0);
+ if (sign == Utf8Constants.Minus)
+ {
+ offset = -offset;
+ }
+
+ if (!TryCreateDateTimeOffset(dateTime: dateTime, offsetNegative: sign == Utf8Constants.Minus, offsetHours: offsetHours, offsetMinutes: offsetMinutes, out value))
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ bytesConsumed = 26;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.G.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.G.cs
new file mode 100644
index 0000000000..6e8edbcbdf
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.G.cs
@@ -0,0 +1,177 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ //
+ // 'G' format for DateTime.
+ //
+ // 0123456789012345678
+ // ---------------------------------
+ // 05/25/2017 10:30:15
+ //
+ private static bool TryParseDateTimeG(ReadOnlySpan<byte> source, out DateTime value, out DateTimeOffset valueAsOffset, out int bytesConsumed)
+ {
+ if (source.Length < 19)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int month;
+ {
+ uint digit1 = source[0] - 48u; // '0'
+ uint digit2 = source[1] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ month = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[2] != Utf8Constants.Slash)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int day;
+ {
+ uint digit1 = source[3] - 48u; // '0'
+ uint digit2 = source[4] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ day = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[5] != Utf8Constants.Slash)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int year;
+ {
+ uint digit1 = source[6] - 48u; // '0'
+ uint digit2 = source[7] - 48u; // '0'
+ uint digit3 = source[8] - 48u; // '0'
+ uint digit4 = source[9] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4);
+ }
+
+ if (source[10] != Utf8Constants.Space)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int hour;
+ {
+ uint digit1 = source[11] - 48u; // '0'
+ uint digit2 = source[12] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ hour = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[13] != Utf8Constants.Colon)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int minute;
+ {
+ uint digit1 = source[14] - 48u; // '0'
+ uint digit2 = source[15] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ minute = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[16] != Utf8Constants.Colon)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ int second;
+ {
+ uint digit1 = source[17] - 48u; // '0'
+ uint digit2 = source[18] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ second = (int)(digit1 * 10 + digit2);
+ }
+
+ if (!TryCreateDateTimeOffsetInterpretingDataAsLocalTime(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: 0, out valueAsOffset))
+ {
+ bytesConsumed = 0;
+ value = default;
+ valueAsOffset = default;
+ return false;
+ }
+
+ bytesConsumed = 19;
+ value = valueAsOffset.DateTime;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs
new file mode 100644
index 0000000000..d2fb06829a
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Helpers.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Overflow-safe DateTimeOffset factory.
+ /// </summary>
+ private static bool TryCreateDateTimeOffset(DateTime dateTime, bool offsetNegative, int offsetHours, int offsetMinutes, out DateTimeOffset value)
+ {
+ if (((uint)offsetHours) > Utf8Constants.DateTimeMaxUtcOffsetHours)
+ {
+ value = default;
+ return false;
+ }
+
+ if (((uint)offsetMinutes) > 59)
+ {
+ value = default;
+ return false;
+ }
+
+ if (offsetHours == Utf8Constants.DateTimeMaxUtcOffsetHours && offsetMinutes != 0)
+ {
+ value = default;
+ return false;
+ }
+
+ long offsetTicks = (((long)offsetHours) * 3600 + ((long)offsetMinutes) * 60) * TimeSpan.TicksPerSecond;
+ if (offsetNegative)
+ {
+ offsetTicks = -offsetTicks;
+ }
+
+ try
+ {
+ value = new DateTimeOffset(ticks: dateTime.Ticks, offset: new TimeSpan(ticks: offsetTicks));
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ // If we got here, the combination of the DateTime + UTC offset strayed outside the 1..9999 year range. This case seems rare enough
+ // that it's better to catch the exception rather than replicate DateTime's range checking (which it's going to do anyway.)
+ value = default;
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Overflow-safe DateTimeOffset factory.
+ /// </summary>
+ private static bool TryCreateDateTimeOffset(int year, int month, int day, int hour, int minute, int second, int fraction, bool offsetNegative, int offsetHours, int offsetMinutes, out DateTimeOffset value)
+ {
+ if (!TryCreateDateTime(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: fraction, kind: DateTimeKind.Unspecified, out DateTime dateTime))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!TryCreateDateTimeOffset(dateTime: dateTime, offsetNegative: offsetNegative, offsetHours: offsetHours, offsetMinutes: offsetMinutes, out value))
+ {
+ value = default;
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Overflow-safe DateTimeOffset/Local time conversion factory.
+ /// </summary>
+ private static bool TryCreateDateTimeOffsetInterpretingDataAsLocalTime(int year, int month, int day, int hour, int minute, int second, int fraction, out DateTimeOffset value)
+ {
+ if (!TryCreateDateTime(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: fraction, DateTimeKind.Local, out DateTime dateTime))
+ {
+ value = default;
+ return false;
+ }
+
+ try
+ {
+ value = new DateTimeOffset(dateTime);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ // If we got here, the combination of the DateTime + UTC offset strayed outside the 1..9999 year range. This case seems rare enough
+ // that it's better to catch the exception rather than replicate DateTime's range checking (which it's going to do anyway.)
+
+ value = default;
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Overflow-safe DateTime factory.
+ /// </summary>
+ private static bool TryCreateDateTime(int year, int month, int day, int hour, int minute, int second, int fraction, DateTimeKind kind, out DateTime value)
+ {
+ if (year == 0)
+ {
+ value = default;
+ return false;
+ }
+
+ Debug.Assert(year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields so this value is trusted.
+
+ if ((((uint)month) - 1) >= 12)
+ {
+ value = default;
+ return false;
+ }
+
+ uint dayMinusOne = ((uint)day) - 1;
+ if (dayMinusOne >= 28 && dayMinusOne >= DateTime.DaysInMonth(year, month))
+ {
+ value = default;
+ return false;
+ }
+
+ if (((uint)hour) > 23)
+ {
+ value = default;
+ return false;
+ }
+
+ if (((uint)minute) > 59)
+ {
+ value = default;
+ return false;
+ }
+
+ if (((uint)second) > 59)
+ {
+ value = default;
+ return false;
+ }
+
+ Debug.Assert(fraction >= 0 && fraction <= Utf8Constants.MaxDateTimeFraction); // All of our callers to date parse the fraction from fixed 7-digit fields so this value is trusted.
+
+ int[] days = DateTime.IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365;
+ int yearMinusOne = year - 1;
+ int totalDays = (yearMinusOne * 365) + (yearMinusOne / 4) - (yearMinusOne / 100) + (yearMinusOne / 400) + days[month - 1] + day - 1;
+ long ticks = totalDays * TimeSpan.TicksPerDay;
+ int totalSeconds = (hour * 3600) + (minute * 60) + second;
+ ticks += totalSeconds * TimeSpan.TicksPerSecond;
+ ticks += fraction;
+ value = new DateTime(ticks: ticks, kind: kind);
+ return true;
+ }
+
+ private static readonly int[] s_daysToMonth365 = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
+ private static readonly int[] s_daysToMonth366 = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.O.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.O.cs
new file mode 100644
index 0000000000..8d2c681f68
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.O.cs
@@ -0,0 +1,290 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ //
+ // Roundtrippable format. One of
+ //
+ // 012345678901234567890123456789012
+ // ---------------------------------
+ // 2017-06-12T05:30:45.7680000-07:00
+ // 2017-06-12T05:30:45.7680000Z (Z is short for "+00:00" but also distinguishes DateTimeKind.Utc from DateTimeKind.Local)
+ // 2017-06-12T05:30:45.7680000 (interpreted as local time wrt to current time zone)
+ //
+ private static bool TryParseDateTimeOffsetO(ReadOnlySpan<byte> source, out DateTimeOffset value, out int bytesConsumed, out DateTimeKind kind)
+ {
+ if (source.Length < 27)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int year;
+ {
+ uint digit1 = source[0] - 48u; // '0'
+ uint digit2 = source[1] - 48u; // '0'
+ uint digit3 = source[2] - 48u; // '0'
+ uint digit4 = source[3] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4);
+ }
+
+ if (source[4] != Utf8Constants.Hyphen)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int month;
+ {
+ uint digit1 = source[5] - 48u; // '0'
+ uint digit2 = source[6] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ month = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[7] != Utf8Constants.Hyphen)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int day;
+ {
+ uint digit1 = source[8] - 48u; // '0'
+ uint digit2 = source[9] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ day = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[10] != 'T')
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int hour;
+ {
+ uint digit1 = source[11] - 48u; // '0'
+ uint digit2 = source[12] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ hour = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[13] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int minute;
+ {
+ uint digit1 = source[14] - 48u; // '0'
+ uint digit2 = source[15] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ minute = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[16] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int second;
+ {
+ uint digit1 = source[17] - 48u; // '0'
+ uint digit2 = source[18] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ second = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[19] != Utf8Constants.Period)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int fraction;
+ {
+ uint digit1 = source[20] - 48u; // '0'
+ uint digit2 = source[21] - 48u; // '0'
+ uint digit3 = source[22] - 48u; // '0'
+ uint digit4 = source[23] - 48u; // '0'
+ uint digit5 = source[24] - 48u; // '0'
+ uint digit6 = source[25] - 48u; // '0'
+ uint digit7 = source[26] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9 || digit5 > 9 || digit6 > 9 || digit7 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ fraction = (int)(digit1 * 1000000 + digit2 * 100000 + digit3 * 10000 + digit4 * 1000 + digit5 * 100 + digit6 * 10 + digit7);
+ }
+
+ byte offsetChar = (source.Length <= 27) ? default : source[27];
+ if (offsetChar != 'Z' && offsetChar != Utf8Constants.Plus && offsetChar != Utf8Constants.Minus)
+ {
+ if (!TryCreateDateTimeOffsetInterpretingDataAsLocalTime(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: fraction, out value))
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+ bytesConsumed = 27;
+ kind = DateTimeKind.Unspecified;
+ return true;
+ }
+
+ if (offsetChar == 'Z')
+ {
+ // Same as specifying an offset of "+00:00", except that DateTime's Kind gets set to UTC rather than Local
+ if (!TryCreateDateTimeOffset(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: fraction, offsetNegative: false, offsetHours: 0, offsetMinutes: 0, out value))
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ bytesConsumed = 28;
+ kind = DateTimeKind.Utc;
+ return true;
+ }
+
+ Debug.Assert(offsetChar == Utf8Constants.Plus || offsetChar == Utf8Constants.Minus);
+ if (source.Length < 33)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int offsetHours;
+ {
+ uint digit1 = source[28] - 48u; // '0'
+ uint digit2 = source[29] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ offsetHours = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[30] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ int offsetMinutes;
+ {
+ uint digit1 = source[31] - 48u; // '0'
+ uint digit2 = source[32] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ offsetMinutes = (int)(digit1 * 10 + digit2);
+ }
+
+ if (!TryCreateDateTimeOffset(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: fraction, offsetNegative: offsetChar == Utf8Constants.Minus, offsetHours: offsetHours, offsetMinutes: offsetMinutes, out value))
+ {
+ value = default;
+ bytesConsumed = 0;
+ kind = default;
+ return false;
+ }
+
+ bytesConsumed = 33;
+ kind = DateTimeKind.Local;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.R.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.R.cs
new file mode 100644
index 0000000000..316bee01b4
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.R.cs
@@ -0,0 +1,221 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ //
+ // Parse an RFC1123 date string.
+ //
+ // 01234567890123456789012345678
+ // -----------------------------
+ // Tue, 03 Jan 2017 08:08:05 GMT
+ //
+ private static bool TryParseDateTimeOffsetR(ReadOnlySpan<byte> source, uint caseFlipXorMask, out DateTimeOffset dateTimeOffset, out int bytesConsumed)
+ {
+ if (source.Length < 29)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ DayOfWeek dayOfWeek;
+ {
+ uint dow0 = source[0] ^ caseFlipXorMask;
+ uint dow1 = source[1];
+ uint dow2 = source[2];
+ uint comma = source[3];
+ uint dowString = (dow0 << 24) | (dow1 << 16) | (dow2 << 8) | comma;
+ switch (dowString)
+ {
+ case 0x53756E2c /* 'Sun,' */: dayOfWeek = DayOfWeek.Sunday; break;
+ case 0x4d6f6e2c /* 'Mon,' */: dayOfWeek = DayOfWeek.Monday; break;
+ case 0x5475652c /* 'Tue,' */: dayOfWeek = DayOfWeek.Tuesday; break;
+ case 0x5765642c /* 'Wed,' */: dayOfWeek = DayOfWeek.Wednesday; break;
+ case 0x5468752c /* 'Thu,' */: dayOfWeek = DayOfWeek.Thursday; break;
+ case 0x4672692c /* 'Fri,' */: dayOfWeek = DayOfWeek.Friday; break;
+ case 0x5361742c /* 'Sat,' */: dayOfWeek = DayOfWeek.Saturday; break;
+ default:
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+ }
+
+ if (source[4] != Utf8Constants.Space)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ int day;
+ {
+ uint digit1 = source[5] - 48u; // '0'
+ uint digit2 = source[6] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ day = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[7] != Utf8Constants.Space)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ int month;
+ {
+ uint mon0 = source[8] ^ caseFlipXorMask;
+ uint mon1 = source[9];
+ uint mon2 = source[10];
+ uint space = source[11];
+ uint monthString = (mon0 << 24) | (mon1 << 16) | (mon2 << 8) | space;
+ switch (monthString)
+ {
+ case 0x4a616e20 /* 'Jan ' */ : month = 1; break;
+ case 0x46656220 /* 'Feb ' */ : month = 2; break;
+ case 0x4d617220 /* 'Mar ' */ : month = 3; break;
+ case 0x41707220 /* 'Apr ' */ : month = 4; break;
+ case 0x4d617920 /* 'May ' */ : month = 5; break;
+ case 0x4a756e20 /* 'Jun ' */ : month = 6; break;
+ case 0x4a756c20 /* 'Jul ' */ : month = 7; break;
+ case 0x41756720 /* 'Aug ' */ : month = 8; break;
+ case 0x53657020 /* 'Sep ' */ : month = 9; break;
+ case 0x4f637420 /* 'Oct ' */ : month = 10; break;
+ case 0x4e6f7620 /* 'Nov ' */ : month = 11; break;
+ case 0x44656320 /* 'Dec ' */ : month = 12; break;
+ default:
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+ }
+
+ int year;
+ {
+ uint digit1 = source[12] - 48u; // '0'
+ uint digit2 = source[13] - 48u; // '0'
+ uint digit3 = source[14] - 48u; // '0'
+ uint digit4 = source[15] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4);
+ }
+
+ if (source[16] != Utf8Constants.Space)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ int hour;
+ {
+ uint digit1 = source[17] - 48u; // '0'
+ uint digit2 = source[18] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ hour = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[19] != Utf8Constants.Colon)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ int minute;
+ {
+ uint digit1 = source[20] - 48u; // '0'
+ uint digit2 = source[21] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ minute = (int)(digit1 * 10 + digit2);
+ }
+
+ if (source[22] != Utf8Constants.Colon)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ int second;
+ {
+ uint digit1 = source[23] - 48u; // '0'
+ uint digit2 = source[24] - 48u; // '0'
+
+ if (digit1 > 9 || digit2 > 9)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ second = (int)(digit1 * 10 + digit2);
+ }
+
+ {
+ uint space = source[25];
+ uint g = source[26] ^ caseFlipXorMask;
+ uint m = source[27] ^ caseFlipXorMask;
+ uint t = source[28] ^ caseFlipXorMask;
+ uint gmtString = (space << 24) | (g << 16) | (m << 8) | t;
+ if (gmtString != 0x20474d54 /* ' GMT' */)
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+ }
+
+ if (!TryCreateDateTimeOffset(year: year, month: month, day: day, hour: hour, minute: minute, second: second, fraction: 0, offsetNegative: false, offsetHours: 0, offsetMinutes: 0, out dateTimeOffset))
+ {
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ if (dayOfWeek != dateTimeOffset.DayOfWeek)
+ {
+ // If we got here, the day of week did not match the actual date.
+ bytesConsumed = 0;
+ dateTimeOffset = default;
+ return false;
+ }
+
+ bytesConsumed = 29;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs
new file mode 100644
index 0000000000..f103492461
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.cs
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a DateTime 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:
+ /// default 05/25/2017 10:30:15 -08:00
+ /// G 05/25/2017 10:30:15
+ /// R Tue, 03 Jan 2017 08:08:05 GMT (RFC 1123)
+ /// l tue, 03 jan 2017 08:08:05 gmt (Lowercase RFC 1123)
+ /// O 2017-06-12T05:30:45.7680000-07:00 (Round-trippable)
+ /// </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 DateTime value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case 'R':
+ {
+ if (!TryParseDateTimeOffsetR(source, NoFlipCase, out DateTimeOffset dateTimeOffset, out bytesConsumed))
+ {
+ value = default;
+ return false;
+ }
+ value = dateTimeOffset.DateTime; // (returns a DateTimeKind.Unspecified to match DateTime.ParseExact(). Maybe better to return UtcDateTime instead?)
+ return true;
+ }
+
+ case 'l':
+ {
+ if (!TryParseDateTimeOffsetR(source, FlipCase, out DateTimeOffset dateTimeOffset, out bytesConsumed))
+ {
+ value = default;
+ return false;
+ }
+ value = dateTimeOffset.DateTime; // (returns a DateTimeKind.Unspecified to match DateTime.ParseExact(). Maybe better to return UtcDateTime instead?)
+ return true;
+ }
+
+ case 'O':
+ {
+ // Emulates DateTime.ParseExact(text, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind)
+ // In particular, the formatted string "encodes" the DateTimeKind according to the following table:
+ //
+ // 2017-06-12T05:30:45.7680000 - Unspecified
+ // 2017-06-12T05:30:45.7680000+00:00 - Local
+ // 2017-06-12T05:30:45.7680000Z - Utc
+
+ if (!TryParseDateTimeOffsetO(source, out DateTimeOffset dateTimeOffset, out bytesConsumed, out DateTimeKind kind))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ switch (kind)
+ {
+ case DateTimeKind.Local:
+ value = dateTimeOffset.LocalDateTime;
+ break;
+ case DateTimeKind.Utc:
+ value = dateTimeOffset.UtcDateTime;
+ break;
+ default:
+ Debug.Assert(kind == DateTimeKind.Unspecified);
+ value = dateTimeOffset.DateTime;
+ break;
+ }
+
+ return true;
+ }
+
+ case default(char):
+ case 'G':
+ return TryParseDateTimeG(source, out value, out _, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses a DateTimeOffset 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:
+ /// G (default) 05/25/2017 10:30:15
+ /// R Tue, 03 Jan 2017 08:08:05 GMT (RFC 1123)
+ /// l tue, 03 jan 2017 08:08:05 gmt (Lowercase RFC 1123)
+ /// O 2017-06-12T05:30:45.7680000-07:00 (Round-trippable)
+ /// </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 DateTimeOffset value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case 'R':
+ return TryParseDateTimeOffsetR(source, NoFlipCase, out value, out bytesConsumed);
+
+ case 'l':
+ return TryParseDateTimeOffsetR(source, FlipCase, out value, out bytesConsumed);
+
+ case 'O':
+ return TryParseDateTimeOffsetO(source, out value, out bytesConsumed, out _);
+
+ case default(char):
+ return TryParseDateTimeOffsetDefault(source, out value, out bytesConsumed);
+
+ case 'G':
+ return TryParseDateTimeG(source, out DateTime _, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ private const uint FlipCase = 0x00000020u; // XOR mask to flip the case of a letter.
+ private const uint NoFlipCase = 0x00000000u;
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs
new file mode 100644
index 0000000000..c0f1e0c040
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Decimal.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a Decimal 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:
+ /// G/g (default)
+ /// F/f 12.45 Fixed point
+ /// E/e 1.245000e1 Exponential
+ /// </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 decimal value, out int bytesConsumed, char standardFormat = default)
+ {
+ ParseNumberOptions options;
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'G':
+ case 'g':
+ case 'E':
+ case 'e':
+ options = ParseNumberOptions.AllowExponent;
+ break;
+
+ case 'F':
+ case 'f':
+ options = default;
+ break;
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+
+ NumberBuffer number = default;
+ if (!TryParseNumber(source, ref number, out bytesConsumed, options, out bool textUsedExponentNotation))
+ {
+ value = default;
+ return false;
+ }
+
+ if ((!textUsedExponentNotation) && (standardFormat == 'E' || standardFormat == 'e'))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ // More compat with .NET behavior - whether or not a 0 keeps the negative sign depends on whether it an "integer" 0 or a "fractional" 0
+ if (number.Digits[0] == 0 && number.Scale == 0)
+ {
+ number.IsNegative = false;
+ }
+
+ value = default;
+ if (!Number.NumberBufferToDecimal(ref number, ref value))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs
new file mode 100644
index 0000000000..1bdc59d237
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a Single 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:
+ /// G/g (default)
+ /// F/f 12.45 Fixed point
+ /// E/e 1.245000e1 Exponential
+ /// </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 float value, out int bytesConsumed, char standardFormat = default)
+ {
+ if (TryParseNormalAsFloatingPoint(source, out double d, out bytesConsumed, standardFormat))
+ {
+ value = (float)(d);
+ return true;
+ }
+
+ return TryParseAsSpecialFloatingPoint(source, float.PositiveInfinity, float.NegativeInfinity, float.NaN, out value, out bytesConsumed);
+ }
+
+ /// <summary>
+ /// Parses a Double 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:
+ /// G/g (default)
+ /// F/f 12.45 Fixed point
+ /// E/e 1.245000e1 Exponential
+ /// </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 double value, out int bytesConsumed, char standardFormat = default)
+ {
+ if (TryParseNormalAsFloatingPoint(source, out value, out bytesConsumed, standardFormat))
+ return true;
+
+ return TryParseAsSpecialFloatingPoint(source, double.PositiveInfinity, double.NegativeInfinity, double.NaN, out value, out bytesConsumed);
+ }
+
+ //
+ // Attempt to parse the regular floating points (the ones without names like "Infinity" and "NaN")
+ //
+ private static bool TryParseNormalAsFloatingPoint(ReadOnlySpan<byte> source, out double value, out int bytesConsumed, char standardFormat)
+ {
+ ParseNumberOptions options;
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'G':
+ case 'g':
+ case 'E':
+ case 'e':
+ options = ParseNumberOptions.AllowExponent;
+ break;
+
+ case 'F':
+ case 'f':
+ options = default;
+ break;
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+
+ NumberBuffer number = default;
+ if (!TryParseNumber(source, ref number, out bytesConsumed, options, out bool textUsedExponentNotation))
+ {
+ value = default;
+ return false;
+ }
+
+ if ((!textUsedExponentNotation) && (standardFormat == 'E' || standardFormat == 'e'))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ value = Number.NumberBufferToDouble(ref number);
+ return true;
+ }
+
+ //
+ // Assuming the text doesn't look like a normal floating point, we attempt to parse it as one the special floating point values.
+ //
+ private static bool TryParseAsSpecialFloatingPoint<T>(ReadOnlySpan<byte> source, T positiveInfinity, T negativeInfinity, T nan, out T value, out int bytesConsumed)
+ {
+ if (source.Length >= 8 &&
+ source[0] == 'I' && source[1] == 'n' && source[2] == 'f' && source[3] == 'i' &&
+ source[4] == 'n' && source[5] == 'i' && source[6] == 't' && source[7] == 'y')
+ {
+ value = positiveInfinity;
+ bytesConsumed = 8;
+ return true;
+ }
+
+ if (source.Length >= 9 &&
+ source[0] == Utf8Constants.Minus &&
+ source[1] == 'I' && source[2] == 'n' && source[3] == 'f' && source[4] == 'i' &&
+ source[5] == 'n' && source[6] == 'i' && source[7] == 't' && source[8] == 'y')
+ {
+ value = negativeInfinity;
+ bytesConsumed = 9;
+ return true;
+ }
+
+ if (source.Length >= 3 &&
+ source[0] == 'N' && source[1] == 'a' && source[2] == 'N')
+ {
+ value = nan;
+ bytesConsumed = 3;
+ return true;
+ }
+
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs
new file mode 100644
index 0000000000..17dec828bc
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Guid.cs
@@ -0,0 +1,243 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a Guid 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:
+ /// D (default) nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
+ /// B {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
+ /// P (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
+ /// N nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ /// </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 Guid value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'D':
+ return TryParseGuidCore(source, false, ' ', ' ', out value, out bytesConsumed);
+ case 'B':
+ return TryParseGuidCore(source, true, '{', '}', out value, out bytesConsumed);
+ case 'P':
+ return TryParseGuidCore(source, true, '(', ')', out value, out bytesConsumed);
+ case 'N':
+ return TryParseGuidN(source, out value, out bytesConsumed);
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ // nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn (not very Guid-like, but the format is what it is...)
+ private static bool TryParseGuidN(ReadOnlySpan<byte> text, out Guid value, out int bytesConsumed)
+ {
+ if (text.Length < 32)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (!TryParseUInt32X(text.Slice(0, 8), out uint i1, out int justConsumed) || justConsumed != 8)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 8 digits
+ }
+
+ if (!TryParseUInt16X(text.Slice(8, 4), out ushort i2, out justConsumed) || justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // next 4 digits
+ }
+
+ if (!TryParseUInt16X(text.Slice(12, 4), out ushort i3, out justConsumed) || justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // next 4 digits
+ }
+
+ if (!TryParseUInt16X(text.Slice(16, 4), out ushort i4, out justConsumed) || justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // next 4 digits
+ }
+
+ if (!TryParseUInt64X(text.Slice(20), out ulong i5, out justConsumed) || justConsumed != 12)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // next 4 digits
+ }
+
+ bytesConsumed = 32;
+ value = new Guid((int)i1, (short)i2, (short)i3, (byte)(i4 >> 8), (byte)i4,
+ (byte)(i5 >> 40), (byte)(i5 >> 32), (byte)(i5 >> 24), (byte)(i5 >> 16), (byte)(i5 >> 8), (byte)i5);
+ return true;
+ }
+
+ // {8-4-4-4-12}, where number is the number of hex digits, and {/} are ends.
+ private static bool TryParseGuidCore(ReadOnlySpan<byte> source, bool ends, char begin, char end, out Guid value, out int bytesConsumed)
+ {
+ int expectedCodingUnits = 36 + (ends ? 2 : 0); // 32 hex digits + 4 delimiters + 2 optional ends
+
+ if (source.Length < expectedCodingUnits)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (ends)
+ {
+ if (source[0] != begin)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ source = source.Slice(1); // skip begining
+ }
+
+ if (!TryParseUInt32X(source, out uint i1, out int justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (justConsumed != 8)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 8 digits
+ }
+
+ if (source[justConsumed] != '-')
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ source = source.Slice(9); // justConsumed + 1 for delimiter
+
+ if (!TryParseUInt16X(source, out ushort i2, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 4 digits
+ }
+
+ if (source[justConsumed] != '-')
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ source = source.Slice(5); // justConsumed + 1 for delimiter
+
+ if (!TryParseUInt16X(source, out ushort i3, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 4 digits
+ }
+
+ if (source[justConsumed] != '-')
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ source = source.Slice(5); // justConsumed + 1 for delimiter
+
+ if (!TryParseUInt16X(source, out ushort i4, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (justConsumed != 4)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 4 digits
+ }
+
+ if (source[justConsumed] != '-')
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ source = source.Slice(5);// justConsumed + 1 for delimiter
+
+ if (!TryParseUInt64X(source, out ulong i5, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (justConsumed != 12)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false; // 12 digits
+ }
+
+ if (ends && source[justConsumed] != end)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ bytesConsumed = expectedCodingUnits;
+ value = new Guid((int)i1, (short)i2, (short)i3, (byte)(i4 >> 8), (byte)i4,
+ (byte)(i5 >> 40), (byte)(i5 >> 32), (byte)(i5 >> 24), (byte)(i5 >> 16), (byte)(i5 >> 8), (byte)i5);
+
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs
new file mode 100644
index 0000000000..bf1871a1c9
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs
@@ -0,0 +1,443 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseSByteD(ReadOnlySpan<byte> source, out sbyte value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int num = source[index];
+ if (num == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+ else if (num == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = answer * 10 + num - '0';
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if ((uint)answer > (uint)sbyte.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (sbyte)(answer * sign);
+ return true;
+ }
+
+ private static bool TryParseInt16D(ReadOnlySpan<byte> source, out short value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int num = source[index];
+ if (num == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+ else if (num == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = answer * 10 + num - '0';
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if ((uint)answer > (uint)short.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (short)(answer * sign);
+ return true;
+ }
+
+ private static bool TryParseInt32D(ReadOnlySpan<byte> source, out int value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int num = source[index];
+ if (num == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+ else if (num == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ num = source[index];
+ }
+
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ if (answer > int.MaxValue / 10)
+ goto FalseExit; // Overflow
+ answer = answer * 10 + num - '0';
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if ((uint)answer > (uint)int.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = answer * sign;
+ return true;
+ }
+
+ private static bool TryParseInt64D(ReadOnlySpan<byte> source, out long value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ int indexOfFirstDigit = 0;
+ int sign = 1;
+ if (source[0] == '-')
+ {
+ indexOfFirstDigit = 1;
+ sign = -1;
+
+ if (source.Length <= indexOfFirstDigit)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ }
+ else if (source[0] == '+')
+ {
+ indexOfFirstDigit = 1;
+
+ if (source.Length <= indexOfFirstDigit)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ }
+
+ int overflowLength = ParserHelpers.Int64OverflowLength + indexOfFirstDigit;
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ long firstDigit = source[indexOfFirstDigit] - 48; // '0'
+ if (firstDigit < 0 || firstDigit > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ ulong parsedValue = (ulong)firstDigit;
+
+ if (source.Length < overflowLength)
+ {
+ // Length is less than Parsers.Int64OverflowLength; overflow is not possible
+ for (int index = indexOfFirstDigit + 1; index < source.Length; index++)
+ {
+ long nextDigit = source[index] - 48; // '0'
+ if (nextDigit < 0 || nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = ((long)parsedValue) * sign;
+ return true;
+ }
+ parsedValue = parsedValue * 10 + (ulong)nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.Int64OverflowLength; overflow is only possible after Parsers.Int64OverflowLength
+ // digits. There may be no overflow after Parsers.Int64OverflowLength if there are leading zeroes.
+ for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++)
+ {
+ long nextDigit = source[index] - 48; // '0'
+ if (nextDigit < 0 || nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = ((long)parsedValue) * sign;
+ return true;
+ }
+ parsedValue = parsedValue * 10 + (ulong)nextDigit;
+ }
+ for (int index = overflowLength - 1; index < source.Length; index++)
+ {
+ long nextDigit = source[index] - 48; // '0'
+ if (nextDigit < 0 || nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = ((long)parsedValue) * sign;
+ return true;
+ }
+ // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow.
+ // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow.
+ bool positive = sign > 0;
+ bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7);
+ if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = parsedValue * 10 + (ulong)nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = ((long)parsedValue) * sign;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.N.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.N.cs
new file mode 100644
index 0000000000..fd8ce572f2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.N.cs
@@ -0,0 +1,383 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseSByteN(ReadOnlySpan<byte> source, out sbyte value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int c = source[index];
+ if (c == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+ else if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ answer = answer * 10 + c - '0';
+
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if (answer > sbyte.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (sbyte)(answer * sign);
+ return true;
+ }
+
+ private static bool TryParseInt16N(ReadOnlySpan<byte> source, out short value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int c = source[index];
+ if (c == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+ else if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ answer = answer * 10 + c - '0';
+
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if (answer > short.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (short)(answer * sign);
+ return true;
+ }
+
+ private static bool TryParseInt32N(ReadOnlySpan<byte> source, out int value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int c = source[index];
+ if (c == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+ else if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ if (((uint)answer) > int.MaxValue / 10)
+ goto FalseExit;
+
+ answer = answer * 10 + c - '0';
+
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if ((uint)answer > (uint)int.MaxValue + (-1 * sign + 1) / 2)
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = answer * sign;
+ return true;
+ }
+
+ private static bool TryParseInt64N(ReadOnlySpan<byte> source, out long value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int sign = 1;
+ int index = 0;
+ int c = source[index];
+ if (c == '-')
+ {
+ sign = -1;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+ else if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ long answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ if (((ulong)answer) > long.MaxValue / 10)
+ goto FalseExit;
+
+ answer = answer * 10 + c - '0';
+
+ // if sign < 0, (-1 * sign + 1) / 2 = 1
+ // else, (-1 * sign + 1) / 2 = 0
+ if ((ulong)answer > (ulong)(long.MaxValue + (-1 * sign + 1) / 2))
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = answer * sign;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs
new file mode 100644
index 0000000000..b30291c6f2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.cs
@@ -0,0 +1,199 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Internal.Runtime.CompilerServices;
+
+namespace System.Buffers.Text
+{
+ /// <summary>
+ /// Methods to parse common data types to Utf8 strings.
+ /// </summary>
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a SByte 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </remarks>
+ /// <exceptions>
+ /// <cref>System.FormatException</cref> if the format is not valid for this data type.
+ /// </exceptions>
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<byte> source, out sbyte value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseSByteD(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseSByteN(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ value = default;
+ return TryParseByteX(source, out Unsafe.As<sbyte, byte>(ref value), out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses an Int16 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </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 short value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseInt16D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseInt16N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ value = default;
+ return TryParseUInt16X(source, out Unsafe.As<short, ushort>(ref value), out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses an Int32 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </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 int value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseInt32D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseInt32N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ value = default;
+ return TryParseUInt32X(source, out Unsafe.As<int, uint>(ref value), out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses an Int64 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </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 long value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseInt64D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseInt64N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ value = default;
+ return TryParseUInt64X(source, out Unsafe.As<long, ulong>(ref value), out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs
new file mode 100644
index 0000000000..46753f5c57
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.D.cs
@@ -0,0 +1,354 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseByteD(ReadOnlySpan<byte> source, out byte value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int num = source[index];
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = answer * 10 + num - '0';
+ if ((uint)answer > byte.MaxValue)
+ goto FalseExit; // Overflow
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (byte)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt16D(ReadOnlySpan<byte> source, out ushort value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int num = source[index];
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = answer * 10 + num - '0';
+ if ((uint)answer > ushort.MaxValue)
+ goto FalseExit; // Overflow
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (ushort)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt32D(ReadOnlySpan<byte> source, out uint value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int num = source[index];
+ int answer = 0;
+
+ if (ParserHelpers.IsDigit(num))
+ {
+ if (num == '0')
+ {
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ } while (num == '0');
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ }
+
+ answer = num - '0';
+ index++;
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ answer = 10 * answer + num - '0';
+
+ // Potential overflow
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ num = source[index];
+ if (!ParserHelpers.IsDigit(num))
+ goto Done;
+ index++;
+ if (((uint)answer) > uint.MaxValue / 10 || (((uint)answer) == uint.MaxValue / 10 && num > '5'))
+ goto FalseExit; // Overflow
+ answer = answer * 10 + num - '0';
+
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ if (!ParserHelpers.IsDigit(source[index]))
+ goto Done;
+
+ // Guaranteed overflow
+ goto FalseExit;
+ }
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (uint)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt64D(ReadOnlySpan<byte> source, out ulong value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ ulong firstDigit = source[0] - 48u; // '0'
+ if (firstDigit > 9)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ ulong parsedValue = firstDigit;
+
+ if (source.Length < ParserHelpers.Int64OverflowLength)
+ {
+ // Length is less than Parsers.Int64OverflowLength; overflow is not possible
+ for (int index = 1; index < source.Length; index++)
+ {
+ ulong nextDigit = source[index] - 48u; // '0'
+ if (nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = parsedValue * 10 + nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.Int64OverflowLength; overflow is only possible after Parsers.Int64OverflowLength
+ // digits. There may be no overflow after Parsers.Int64OverflowLength if there are leading zeroes.
+ for (int index = 1; index < ParserHelpers.Int64OverflowLength - 1; index++)
+ {
+ ulong nextDigit = source[index] - 48u; // '0'
+ if (nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = parsedValue * 10 + nextDigit;
+ }
+ for (int index = ParserHelpers.Int64OverflowLength - 1; index < source.Length; index++)
+ {
+ ulong nextDigit = source[index] - 48u; // '0'
+ if (nextDigit > 9)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow.
+ // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow.
+ if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5))
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = parsedValue * 10 + nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = parsedValue;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.N.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.N.cs
new file mode 100644
index 0000000000..2db20c1270
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.N.cs
@@ -0,0 +1,336 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ //
+ // Parsing unsigned integers for the 'N' format. Emulating int.TryParse(NumberStyles.AllowThousands | NumberStyles.Integer | NumberStyles.AllowDecimalPoint)
+ //
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseByteN(ReadOnlySpan<byte> source, out byte value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int c = source[index];
+ if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ answer = answer * 10 + c - '0';
+
+ if (answer > byte.MaxValue)
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (byte)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt16N(ReadOnlySpan<byte> source, out ushort value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int c = source[index];
+ if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ answer = answer * 10 + c - '0';
+
+ if (answer > ushort.MaxValue)
+ goto FalseExit; // Overflow
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (ushort)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt32N(ReadOnlySpan<byte> source, out uint value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int c = source[index];
+ if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ int answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ if (((uint)answer) > uint.MaxValue / 10 || (((uint)answer) == uint.MaxValue / 10 && c > '5'))
+ goto FalseExit; // Overflow
+
+ answer = answer * 10 + c - '0';
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (uint)answer;
+ return true;
+ }
+
+ private static bool TryParseUInt64N(ReadOnlySpan<byte> source, out ulong value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ goto FalseExit;
+
+ int index = 0;
+ int c = source[index];
+ if (c == '+')
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ c = source[index];
+ }
+
+ long answer;
+
+ // Handle the first digit (or period) as a special case. This ensures some compatible edge-case behavior with the classic parse routines
+ // (at least one digit must precede any commas, and a string without any digits prior to the decimal point must have at least
+ // one digit after the decimal point.)
+ if (c == Utf8Constants.Period)
+ goto FractionalPartWithoutLeadingDigits;
+ if (!ParserHelpers.IsDigit(c))
+ goto FalseExit;
+ answer = c - '0';
+
+ for (; ; )
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+
+ c = source[index];
+ if (c == Utf8Constants.Comma)
+ continue;
+
+ if (c == Utf8Constants.Period)
+ goto FractionalDigits;
+
+ if (!ParserHelpers.IsDigit(c))
+ goto Done;
+
+ if (((ulong)answer) > ulong.MaxValue / 10 || (((ulong)answer) == ulong.MaxValue / 10 && c > '5'))
+ goto FalseExit; // Overflow
+
+ answer = answer * 10 + c - '0';
+ }
+
+ FractionalPartWithoutLeadingDigits: // If we got here, we found a decimal point before we found any digits. This is legal as long as there's at least one zero after the decimal point.
+ answer = 0;
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto FalseExit;
+ if (source[index] != '0')
+ goto FalseExit;
+
+ FractionalDigits: // "N" format allows a fractional portion despite being an integer format but only if the post-fraction digits are all 0.
+ do
+ {
+ index++;
+ if ((uint)index >= (uint)source.Length)
+ goto Done;
+ c = source[index];
+ }
+ while (c == '0');
+
+ if (ParserHelpers.IsDigit(c))
+ goto FalseExit; // The fractional portion contained a non-zero digit. Treat this as an error, not an early termination.
+ goto Done;
+
+ FalseExit:
+ bytesConsumed = default;
+ value = default;
+ return false;
+
+ Done:
+ bytesConsumed = index;
+ value = (ulong)answer;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs
new file mode 100644
index 0000000000..7e7867a56f
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs
@@ -0,0 +1,341 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseByteX(ReadOnlySpan<byte> source, out byte value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ byte nextCharacter;
+ byte nextDigit;
+
+ // Cache Parsers.s_HexLookup in order to avoid static constructor checks
+ byte[] hexLookup = ParserHelpers.s_hexLookup;
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ nextCharacter = source[0];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ uint parsedValue = nextDigit;
+
+ if (source.Length <= ParserHelpers.ByteOverflowLengthHex)
+ {
+ // Length is less than or equal to Parsers.ByteOverflowLengthHex; overflow is not possible
+ for (int index = 1; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (byte)(parsedValue);
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.ByteOverflowLengthHex; overflow is only possible after Parsers.ByteOverflowLengthHex
+ // digits. There may be no overflow after Parsers.ByteOverflowLengthHex if there are leading zeroes.
+ for (int index = 1; index < ParserHelpers.ByteOverflowLengthHex; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (byte)(parsedValue);
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ for (int index = ParserHelpers.ByteOverflowLengthHex; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (byte)(parsedValue);
+ return true;
+ }
+ // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow
+ if (parsedValue > byte.MaxValue / 0x10)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = (byte)(parsedValue);
+ return true;
+ }
+
+ private static bool TryParseUInt16X(ReadOnlySpan<byte> source, out ushort value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ byte nextCharacter;
+ byte nextDigit;
+
+ // Cache Parsers.s_HexLookup in order to avoid static constructor checks
+ byte[] hexLookup = ParserHelpers.s_hexLookup;
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ nextCharacter = source[0];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ uint parsedValue = nextDigit;
+
+ if (source.Length <= ParserHelpers.Int16OverflowLengthHex)
+ {
+ // Length is less than or equal to Parsers.Int16OverflowLengthHex; overflow is not possible
+ for (int index = 1; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (ushort)(parsedValue);
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.Int16OverflowLengthHex; overflow is only possible after Parsers.Int16OverflowLengthHex
+ // digits. There may be no overflow after Parsers.Int16OverflowLengthHex if there are leading zeroes.
+ for (int index = 1; index < ParserHelpers.Int16OverflowLengthHex; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (ushort)(parsedValue);
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ for (int index = ParserHelpers.Int16OverflowLengthHex; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = (ushort)(parsedValue);
+ return true;
+ }
+ // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow
+ if (parsedValue > ushort.MaxValue / 0x10)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = (ushort)(parsedValue);
+ return true;
+ }
+
+ private static bool TryParseUInt32X(ReadOnlySpan<byte> source, out uint value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ byte nextCharacter;
+ byte nextDigit;
+
+ // Cache Parsers.s_HexLookup in order to avoid static constructor checks
+ byte[] hexLookup = ParserHelpers.s_hexLookup;
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ nextCharacter = source[0];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ uint parsedValue = nextDigit;
+
+ if (source.Length <= ParserHelpers.Int32OverflowLengthHex)
+ {
+ // Length is less than or equal to Parsers.Int32OverflowLengthHex; overflow is not possible
+ for (int index = 1; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.Int32OverflowLengthHex; overflow is only possible after Parsers.Int32OverflowLengthHex
+ // digits. There may be no overflow after Parsers.Int32OverflowLengthHex if there are leading zeroes.
+ for (int index = 1; index < ParserHelpers.Int32OverflowLengthHex; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ for (int index = ParserHelpers.Int32OverflowLengthHex; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow
+ if (parsedValue > uint.MaxValue / 0x10)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = parsedValue;
+ return true;
+ }
+
+ private static bool TryParseUInt64X(ReadOnlySpan<byte> source, out ulong value, out int bytesConsumed)
+ {
+ if (source.Length < 1)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ byte nextCharacter;
+ byte nextDigit;
+
+ // Cache Parsers.s_HexLookup in order to avoid static constructor checks
+ byte[] hexLookup = ParserHelpers.s_hexLookup;
+
+ // Parse the first digit separately. If invalid here, we need to return false.
+ nextCharacter = source[0];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ ulong parsedValue = nextDigit;
+
+ if (source.Length <= ParserHelpers.Int64OverflowLengthHex)
+ {
+ // Length is less than or equal to Parsers.Int64OverflowLengthHex; overflow is not possible
+ for (int index = 1; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+ else
+ {
+ // Length is greater than Parsers.Int64OverflowLengthHex; overflow is only possible after Parsers.Int64OverflowLengthHex
+ // digits. There may be no overflow after Parsers.Int64OverflowLengthHex if there are leading zeroes.
+ for (int index = 1; index < ParserHelpers.Int64OverflowLengthHex; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ for (int index = ParserHelpers.Int64OverflowLengthHex; index < source.Length; index++)
+ {
+ nextCharacter = source[index];
+ nextDigit = hexLookup[nextCharacter];
+ if (nextDigit == 0xFF)
+ {
+ bytesConsumed = index;
+ value = parsedValue;
+ return true;
+ }
+ // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow
+ if (parsedValue > ulong.MaxValue / 0x10)
+ {
+ bytesConsumed = 0;
+ value = default;
+ return false;
+ }
+ parsedValue = (parsedValue << 4) + nextDigit;
+ }
+ }
+
+ bytesConsumed = source.Length;
+ value = parsedValue;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs
new file mode 100644
index 0000000000..ae23c29d04
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.cs
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ /// <summary>
+ /// Parses a Byte 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </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 byte value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseByteD(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseByteN(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ return TryParseByteX(source, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses a UInt16 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </remarks>
+ /// <exceptions>
+ /// <cref>System.FormatException</cref> if the format is not valid for this data type.
+ /// </exceptions>
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<byte> source, out ushort value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseUInt16D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseUInt16N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ return TryParseUInt16X(source, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses a UInt32 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </remarks>
+ /// <exceptions>
+ /// <cref>System.FormatException</cref> if the format is not valid for this data type.
+ /// </exceptions>
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<byte> source, out uint value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseUInt32D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseUInt32N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ return TryParseUInt32X(source, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+
+ /// <summary>
+ /// Parses a UInt64 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:
+ /// G/g (default)
+ /// D/d 32767
+ /// N/n 32,767
+ /// X/x 7fff
+ /// </remarks>
+ /// <exceptions>
+ /// <cref>System.FormatException</cref> if the format is not valid for this data type.
+ /// </exceptions>
+ [CLSCompliant(false)]
+ public static bool TryParse(ReadOnlySpan<byte> source, out ulong value, out int bytesConsumed, char standardFormat = default)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'g':
+ case 'G':
+ case 'd':
+ case 'D':
+ return TryParseUInt64D(source, out value, out bytesConsumed);
+
+ case 'n':
+ case 'N':
+ return TryParseUInt64N(source, out value, out bytesConsumed);
+
+ case 'x':
+ case 'X':
+ return TryParseUInt64X(source, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.TryParseThrowFormatException(out value, out bytesConsumed);
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs
new file mode 100644
index 0000000000..813a1f0a6e
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Number.cs
@@ -0,0 +1,246 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ [Flags]
+ private enum ParseNumberOptions
+ {
+ AllowExponent = 0x00000001,
+ }
+
+ private static bool TryParseNumber(ReadOnlySpan<byte> source, ref NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation)
+ {
+ Debug.Assert(number.Digits[0] == 0 && number.Scale == 0 && !number.IsNegative, "Number not initialized to default(NumberBuffer)");
+
+ textUsedExponentNotation = false;
+
+ if (source.Length == 0)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ Span<byte> digits = number.Digits;
+
+ int srcIndex = 0;
+ int dstIndex = 0;
+
+ // Consume the leading sign if any.
+ byte c = source[srcIndex];
+ switch (c)
+ {
+ case Utf8Constants.Minus:
+ number.IsNegative = true;
+ goto case Utf8Constants.Plus;
+
+ case Utf8Constants.Plus:
+ srcIndex++;
+ if (srcIndex == source.Length)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+ c = source[srcIndex];
+ break;
+
+ default:
+ break;
+ }
+
+ int startIndexDigitsBeforeDecimal = srcIndex;
+
+ // Throw away any leading zeroes
+ while (srcIndex != source.Length)
+ {
+ c = source[srcIndex];
+ if (c != '0')
+ break;
+ srcIndex++;
+ }
+
+ if (srcIndex == source.Length)
+ {
+ digits[0] = 0;
+ number.Scale = 0;
+ number.IsNegative = false;
+ bytesConsumed = srcIndex;
+ number.CheckConsistency();
+ return true;
+ }
+
+ int startIndexNonLeadingDigitsBeforeDecimal = srcIndex;
+ while (srcIndex != source.Length)
+ {
+ c = source[srcIndex];
+ if ((c - 48u) > 9)
+ break;
+ srcIndex++;
+ }
+
+ int numDigitsBeforeDecimal = srcIndex - startIndexDigitsBeforeDecimal;
+ int numNonLeadingDigitsBeforeDecimal = srcIndex - startIndexNonLeadingDigitsBeforeDecimal;
+
+ Debug.Assert(dstIndex == 0);
+ int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, NumberBuffer.BufferSize - 1);
+ source.Slice(startIndexNonLeadingDigitsBeforeDecimal, numNonLeadingDigitsBeforeDecimalToCopy).CopyTo(digits);
+ dstIndex = numNonLeadingDigitsBeforeDecimalToCopy;
+ number.Scale = numNonLeadingDigitsBeforeDecimal;
+
+ if (srcIndex == source.Length)
+ {
+ bytesConsumed = srcIndex;
+ number.CheckConsistency();
+ return true;
+ }
+
+ int numDigitsAfterDecimal = 0;
+ if (c == Utf8Constants.Period)
+ {
+ //
+ // Parse the digits after the decimal point.
+ //
+
+ srcIndex++;
+ int startIndexDigitsAfterDecimal = srcIndex;
+ while (srcIndex != source.Length)
+ {
+ c = source[srcIndex];
+ if ((c - 48u) > 9)
+ break;
+ srcIndex++;
+ }
+ numDigitsAfterDecimal = srcIndex - startIndexDigitsAfterDecimal;
+
+ int startIndexOfDigitsAfterDecimalToCopy = startIndexDigitsAfterDecimal;
+ if (dstIndex == 0)
+ {
+ // Not copied any digits to the Number struct yet. This means we must continue discarding leading zeroes even though they appeared after the decimal point.
+ while (startIndexOfDigitsAfterDecimalToCopy < srcIndex && source[startIndexOfDigitsAfterDecimalToCopy] == '0')
+ {
+ number.Scale--;
+ startIndexOfDigitsAfterDecimalToCopy++;
+ }
+ }
+
+ int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, NumberBuffer.BufferSize - dstIndex - 1);
+ source.Slice(startIndexOfDigitsAfterDecimalToCopy, numDigitsAfterDecimalToCopy).CopyTo(digits.Slice(dstIndex));
+ dstIndex += numDigitsAfterDecimalToCopy;
+ // We "should" really NUL terminate, but there are multiple places we'd have to do this and it is a precondition that the caller pass in a fully zero=initialized Number.
+
+ if (srcIndex == source.Length)
+ {
+ if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
+ {
+ // For compatibility. You can say "5." and ".5" but you can't say "."
+ bytesConsumed = 0;
+ return false;
+ }
+
+ bytesConsumed = srcIndex;
+ number.CheckConsistency();
+ return true;
+ }
+ }
+
+ if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if ((c & ~0x20u) != 'E')
+ {
+ if ((digits[0] == 0) && (numDigitsAfterDecimal == 0))
+ {
+ number.IsNegative = false;
+ }
+
+ bytesConsumed = srcIndex;
+ number.CheckConsistency();
+ return true;
+ }
+
+ //
+ // Parse the exponent after the "E"
+ //
+ textUsedExponentNotation = true;
+ srcIndex++;
+
+ if ((options & ParseNumberOptions.AllowExponent) == 0)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (srcIndex == source.Length)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ bool exponentIsNegative = false;
+ c = source[srcIndex];
+ switch (c)
+ {
+ case Utf8Constants.Minus:
+ exponentIsNegative = true;
+ goto case Utf8Constants.Plus;
+
+ case Utf8Constants.Plus:
+ srcIndex++;
+ if (srcIndex == source.Length)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+ c = source[srcIndex];
+ break;
+
+ default:
+ break;
+ }
+
+ if (!Utf8Parser.TryParseUInt32D(source.Slice(srcIndex), out uint absoluteExponent, out int bytesConsumedByExponent))
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ srcIndex += bytesConsumedByExponent;
+
+ if (exponentIsNegative)
+ {
+ if (number.Scale < int.MinValue + (long)absoluteExponent)
+ {
+ // A scale underflow means all non-zero digits are all so far to the right of the decimal point, no
+ // number format we have will be able to see them. Just pin the scale at the absolute minimum
+ // and let the converter produce a 0 with the max precision available for that type.
+ number.Scale = int.MinValue;
+ }
+ else
+ {
+ number.Scale -= (int)absoluteExponent;
+ }
+ }
+ else
+ {
+ if (number.Scale > int.MaxValue - (long)absoluteExponent)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+ number.Scale += (int)absoluteExponent;
+ }
+
+ bytesConsumed = srcIndex;
+ number.CheckConsistency();
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.BigG.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.BigG.cs
new file mode 100644
index 0000000000..6bcb4d5277
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.BigG.cs
@@ -0,0 +1,132 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseTimeSpanBigG(ReadOnlySpan<byte> source, out TimeSpan value, out int bytesConsumed)
+ {
+ int srcIndex = 0;
+ byte c = default;
+ while (srcIndex != source.Length)
+ {
+ c = source[srcIndex];
+ if (!(c == ' ' || c == '\t'))
+ break;
+ srcIndex++;
+ }
+
+ if (srcIndex == source.Length)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ bool isNegative = false;
+ if (c == Utf8Constants.Minus)
+ {
+ isNegative = true;
+ srcIndex++;
+ if (srcIndex == source.Length)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ }
+
+ if (!TryParseUInt32D(source.Slice(srcIndex), out uint days, out int justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ srcIndex += justConsumed;
+
+ if (srcIndex == source.Length || source[srcIndex++] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (!TryParseUInt32D(source.Slice(srcIndex), out uint hours, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ srcIndex += justConsumed;
+
+ if (srcIndex == source.Length || source[srcIndex++] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (!TryParseUInt32D(source.Slice(srcIndex), out uint minutes, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ srcIndex += justConsumed;
+
+ if (srcIndex == source.Length || source[srcIndex++] != Utf8Constants.Colon)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (!TryParseUInt32D(source.Slice(srcIndex), out uint seconds, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+ srcIndex += justConsumed;
+
+ if (srcIndex == source.Length || source[srcIndex++] != Utf8Constants.Period)
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ if (!TryParseTimeSpanFraction(source.Slice(srcIndex), out uint fraction, out justConsumed))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ srcIndex += justConsumed;
+
+ if (!TryCreateTimeSpan(isNegative: isNegative, days: days, hours: hours, minutes: minutes, seconds: seconds, fraction: fraction, out value))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ //
+ // 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))
+ {
+ value = default;
+ bytesConsumed = 0;
+ return false;
+ }
+
+ bytesConsumed = srcIndex;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.C.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.C.cs
new file mode 100644
index 0000000000..d0a28969be
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.C.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseTimeSpanC(ReadOnlySpan<byte> source, out TimeSpan value, out int bytesConsumed)
+ {
+ TimeSpanSplitter s = default;
+ if (!s.TrySplitTimeSpan(source, periodUsedToSeparateDay: true, out bytesConsumed))
+ {
+ value = default;
+ return false;
+ }
+
+ bool isNegative = s.IsNegative;
+
+ bool success;
+ switch (s.Separators)
+ {
+ case 0x00000000: // dd
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: 0, minutes: 0, seconds: 0, fraction: 0, out value);
+ break;
+
+ case 0x01000000: // hh:mm
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: 0, fraction: 0, out value);
+ break;
+
+ case 0x02010000: // dd.hh:mm
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: s.V2, minutes: s.V3, seconds: 0, fraction: 0, out value);
+ break;
+
+ case 0x01010000: // hh:mm:ss
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: s.V3, fraction: 0, out value);
+ break;
+
+ case 0x02010100: // dd.hh:mm:ss
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: s.V2, minutes: s.V3, seconds: s.V4, fraction: 0, out value);
+ break;
+
+ case 0x01010200: // hh:mm:ss.fffffff
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: s.V3, fraction: s.V4, out value);
+ break;
+
+ case 0x02010102: // dd.hh:mm:ss.fffffff
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: s.V2, minutes: s.V3, seconds: s.V4, fraction: s.V5, out value);
+ break;
+
+ default:
+ value = default;
+ success = false;
+ break;
+ }
+
+ if (!success)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
+
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.LittleG.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.LittleG.cs
new file mode 100644
index 0000000000..19208b9eac
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.LittleG.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers.Text
+{
+ public static partial class Utf8Parser
+ {
+ private static bool TryParseTimeSpanLittleG(ReadOnlySpan<byte> source, out TimeSpan value, out int bytesConsumed)
+ {
+ TimeSpanSplitter s = default;
+ if (!s.TrySplitTimeSpan(source, periodUsedToSeparateDay: false, out bytesConsumed))
+ {
+ value = default;
+ return false;
+ }
+
+ bool isNegative = s.IsNegative;
+
+ bool success;
+ switch (s.Separators)
+ {
+ case 0x00000000: // dd
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: 0, minutes: 0, seconds: 0, fraction: 0, out value);
+ break;
+
+ case 0x01000000: // hh:mm
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: 0, fraction: 0, out value);
+ break;
+
+ case 0x01010000: // hh:mm:ss
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: s.V3, fraction: 0, out value);
+ break;
+
+ case 0x01010100: // dd:hh:mm:ss
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: s.V2, minutes: s.V3, seconds: s.V4, fraction: 0, out value);
+ break;
+
+ case 0x01010200: // hh:mm:ss.fffffff
+ success = TryCreateTimeSpan(isNegative: isNegative, days: 0, hours: s.V1, minutes: s.V2, seconds: s.V3, fraction: s.V4, out value);
+ break;
+
+ case 0x01010102: // dd:hh:mm:ss.fffffff
+ success = TryCreateTimeSpan(isNegative: isNegative, days: s.V1, hours: s.V2, minutes: s.V3, seconds: s.V4, fraction: s.V5, out value);
+ break;
+
+ default:
+ value = default;
+ success = false;
+ break;
+ }
+
+ if (!success)
+ {
+ bytesConsumed = 0;
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs
new file mode 100644
index 0000000000..0ce810b392
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpan.cs
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+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)
+ {
+ switch (standardFormat)
+ {
+ case default(char):
+ case 'c':
+ case 't':
+ case 'T':
+ return TryParseTimeSpanC(source, out value, out bytesConsumed);
+
+ case 'G':
+ return TryParseTimeSpanBigG(source, out value, out bytesConsumed);
+
+ case 'g':
+ return TryParseTimeSpanLittleG(source, out value, out bytesConsumed);
+
+ default:
+ return ThrowHelper.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;
+
+ Debug.Assert(days >= 0 && hours >= 0 && minutes >= 0 && seconds >= 00 && fraction >= 0);
+ 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;
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpanSplitter.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpanSplitter.cs
new file mode 100644
index 0000000000..0c72d1f3a2
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.TimeSpanSplitter.cs
@@ -0,0 +1,225 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+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;
+
+ ComponentParseResult result;
+
+ //
+ // 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.
+ //
+ 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;
+ }
+ }
+ }
+ }
+}