Skip to content

Commit

Permalink
Implement IUtf8SpanFormattable on DateTime, DateTimeOffset, DateOnly,…
Browse files Browse the repository at this point in the history
… TimeOnly, TimeSpan, Char, Rune (#84469)

* Implement IUtf8SpanFormattable on DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, Char, Rune

* Address PR feedback

Also dedup Utf8Formatter for TimeSpan with TimeSpan's new IUtf8SpanFormattable implementation and a little more cleanup.

And fix parameter name of TryFormat to match approved name.
  • Loading branch information
stephentoub authored Apr 9, 2023
1 parent efb64cf commit ef1ba77
Show file tree
Hide file tree
Showing 26 changed files with 767 additions and 855 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.G.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.L.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.O.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Date.R.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.E.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Text\Utf8Formatter\Utf8Formatter.Decimal.F.cs" />
Expand Down Expand Up @@ -401,7 +399,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Icu.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.Nls.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.WebAssembly.cs" Condition="'$(TargetsWasi)' == 'true' or '$(TargetsBrowser)' == 'true'"/>
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TextInfo.WebAssembly.cs" Condition="'$(TargetsWasi)' == 'true' or '$(TargetsBrowser)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\ThaiBuddhistCalendar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TimeSpanFormat.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\TimeSpanParse.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

Expand Down Expand Up @@ -32,8 +33,6 @@ public static char GetSymbolOrDefault(in StandardFormat format, char defaultSymb
return symbol;
}

#region UTF-8 Helper methods

/// <summary>
/// Fills a buffer with the ASCII character '0' (0x30).
/// </summary>
Expand All @@ -48,7 +47,7 @@ public static void FillWithAsciiZeros(Span<byte> buffer)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteDigits(ulong value, Span<byte> buffer)
public static void WriteDigits<TChar>(ulong value, Span<TChar> buffer) where TChar : unmanaged, IBinaryInteger<TChar>
{
// We can mutate the 'value' parameter since it's a copy-by-value local.
// It'll be used to represent the value left over after each division by 10.
Expand All @@ -57,11 +56,11 @@ public static void WriteDigits(ulong value, Span<byte> buffer)
{
ulong temp = '0' + value;
value /= 10;
buffer[i] = (byte)(temp - (value * 10));
buffer[i] = TChar.CreateTruncating(temp - (value * 10));
}

Debug.Assert(value < 10);
buffer[0] = (byte)('0' + value);
buffer[0] = TChar.CreateTruncating('0' + value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -92,66 +91,74 @@ public static void WriteDigitsWithGroupSeparator(ulong value, Span<byte> buffer)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteDigits(uint value, Span<byte> buffer)
public static void WriteDigits<TChar>(uint value, Span<TChar> buffer) where TChar : unmanaged, IBinaryInteger<TChar>
{
// We can mutate the 'value' parameter since it's a copy-by-value local.
// It'll be used to represent the value left over after each division by 10.
Debug.Assert(buffer.Length > 0);

for (int i = buffer.Length - 1; i >= 1; i--)
{
uint temp = '0' + value;
value /= 10;
buffer[i] = (byte)(temp - (value * 10));
buffer[i] = TChar.CreateTruncating(temp - (value * 10));
}

Debug.Assert(value < 10);
buffer[0] = (byte)('0' + value);
buffer[0] = TChar.CreateTruncating('0' + value);
}

/// <summary>
/// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset.
/// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset.
/// This method performs best when the starting index is a constant literal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteFourDecimalDigits(uint value, Span<byte> buffer, int startingIndex = 0)
public static unsafe void WriteTwoDigits<TChar>(uint value, Span<TChar> buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(value <= 9999);
Debug.Assert(startingIndex <= buffer.Length - 4);
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
Debug.Assert(value <= 99);
Debug.Assert(startingIndex <= buffer.Length - 2);

(value, uint remainder) = Math.DivRem(value, 100);
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
Number.WriteTwoDigits(bufferPtr + startingIndex, value);
Number.WriteTwoDigits(bufferPtr + startingIndex + 2, remainder);
}
}

/// <summary>
/// Writes a value [ 00 .. 99 ] to the buffer starting at the specified offset.
/// Writes a value [ 0000 .. 9999 ] to the buffer starting at the specified offset.
/// This method performs best when the starting index is a constant literal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteTwoDecimalDigits(uint value, Span<byte> buffer, int startingIndex = 0)
public static unsafe void WriteFourDigits<TChar>(uint value, Span<TChar> buffer, int startingIndex = 0) where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(value <= 99);
Debug.Assert(startingIndex <= buffer.Length - 2);
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
Debug.Assert(value <= 9999);
Debug.Assert(startingIndex <= buffer.Length - 4);

fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
(value, uint remainder) = Math.DivRem(value, 100);
fixed (TChar* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
Number.WriteTwoDigits(bufferPtr + startingIndex, value);
Number.WriteTwoDigits(bufferPtr + startingIndex + 2, remainder);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyFourBytes(ReadOnlySpan<byte> source, Span<byte> destination) =>
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination),
Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(source)));

#endregion UTF-8 Helper methods
public static void CopyFour<TChar>(ReadOnlySpan<TChar> source, Span<TChar> destination) where TChar : unmanaged, IBinaryInteger<TChar>
{
if (typeof(TChar) == typeof(byte))
{
Unsafe.WriteUnaligned(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(destination)),
Unsafe.ReadUnaligned<uint>(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(source))));
}
else
{
Debug.Assert(typeof(TChar) == typeof(char));
Unsafe.WriteUnaligned(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(destination)),
Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<TChar, byte>(ref MemoryMarshal.GetReference(source))));
}
}

//
// Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate.
//
/// <summary>Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate.</summary>
public static bool TryFormatThrowFormatException(out int bytesWritten)
{
bytesWritten = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,22 @@ private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span<byt
value.GetDate(out int year, out int month, out int day);
value.GetTime(out int hour, out int minute, out int second);

FormattingHelpers.WriteTwoDecimalDigits((uint)month, destination, 0);
FormattingHelpers.WriteTwoDigits((uint)month, destination, 0);
destination[2] = Utf8Constants.Slash;

FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 3);
FormattingHelpers.WriteTwoDigits((uint)day, destination, 3);
destination[5] = Utf8Constants.Slash;

FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 6);
FormattingHelpers.WriteFourDigits((uint)year, destination, 6);
destination[10] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 11);
FormattingHelpers.WriteTwoDigits((uint)hour, destination, 11);
destination[13] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 14);
FormattingHelpers.WriteTwoDigits((uint)minute, destination, 14);
destination[16] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 17);
FormattingHelpers.WriteTwoDigits((uint)second, destination, 17);

if (offset != Utf8Constants.NullUtcOffset)
{
Expand All @@ -80,9 +80,9 @@ private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span<byt
// Writing the value backward allows the JIT to optimize by
// performing a single bounds check against buffer.

FormattingHelpers.WriteTwoDecimalDigits((uint)offsetMinutes, destination, 24);
FormattingHelpers.WriteTwoDigits((uint)offsetMinutes, destination, 24);
destination[23] = Utf8Constants.Colon;
FormattingHelpers.WriteTwoDecimalDigits((uint)offsetHours, destination, 21);
FormattingHelpers.WriteTwoDigits((uint)offsetHours, destination, 21);
destination[20] = sign;
destination[19] = Utf8Constants.Space;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@ private static bool TryFormatDateTimeL(DateTime value, Span<byte> destination, o
value.GetDate(out int year, out int month, out int day);
value.GetTime(out int hour, out int minute, out int second);

FormattingHelpers.CopyFourBytes("sun,mon,tue,wed,thu,fri,sat,"u8.Slice(4 * (int)value.DayOfWeek), destination);
FormattingHelpers.CopyFour("sun,mon,tue,wed,thu,fri,sat,"u8.Slice(4 * (int)value.DayOfWeek), destination);
destination[4] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 5);
FormattingHelpers.WriteTwoDigits((uint)day, destination, 5);
destination[7] = Utf8Constants.Space;

FormattingHelpers.CopyFourBytes("jan feb mar apr may jun jul aug sep oct nov dec "u8.Slice(4 * (month - 1)), destination.Slice(8));
FormattingHelpers.CopyFour("jan feb mar apr may jun jul aug sep oct nov dec "u8.Slice(4 * (month - 1)), destination.Slice(8));

FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 12);
FormattingHelpers.WriteFourDigits((uint)year, destination, 12);
destination[16] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 17);
FormattingHelpers.WriteTwoDigits((uint)hour, destination, 17);
destination[19] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 20);
FormattingHelpers.WriteTwoDigits((uint)minute, destination, 20);
destination[22] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 23);
FormattingHelpers.WriteTwoDigits((uint)second, destination, 23);

FormattingHelpers.CopyFourBytes(" gmt"u8, destination.Slice(25));
FormattingHelpers.CopyFour(" gmt"u8, destination.Slice(25));

bytesWritten = 29;
return true;
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit ef1ba77

Please sign in to comment.