summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mscorlib/shared/System/DateTime.cs18
-rw-r--r--src/mscorlib/shared/System/DateTimeOffset.cs10
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeFormat.cs304
3 files changed, 244 insertions, 88 deletions
diff --git a/src/mscorlib/shared/System/DateTime.cs b/src/mscorlib/shared/System/DateTime.cs
index d3116ee25a..9c3b3989e4 100644
--- a/src/mscorlib/shared/System/DateTime.cs
+++ b/src/mscorlib/shared/System/DateTime.cs
@@ -1255,46 +1255,46 @@ namespace System
public String ToLongDateString()
{
- return DateTimeFormat.Format(this, "D", DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, "D", null);
}
public String ToLongTimeString()
{
- return DateTimeFormat.Format(this, "T", DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, "T", null);
}
public String ToShortDateString()
{
- return DateTimeFormat.Format(this, "d", DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, "d", null);
}
public String ToShortTimeString()
{
- return DateTimeFormat.Format(this, "t", DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, "t", null);
}
public override String ToString()
{
- return DateTimeFormat.Format(this, null, DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, null, null);
}
public String ToString(String format)
{
- return DateTimeFormat.Format(this, format, DateTimeFormatInfo.CurrentInfo);
+ return DateTimeFormat.Format(this, format, null);
}
public String ToString(IFormatProvider provider)
{
- return DateTimeFormat.Format(this, null, DateTimeFormatInfo.GetInstance(provider));
+ return DateTimeFormat.Format(this, null, provider);
}
public String ToString(String format, IFormatProvider provider)
{
- return DateTimeFormat.Format(this, format, DateTimeFormatInfo.GetInstance(provider));
+ return DateTimeFormat.Format(this, format, provider);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider provider = null) =>
- DateTimeFormat.TryFormat(this, destination, out charsWritten, format, DateTimeFormatInfo.GetInstance(provider));
+ DateTimeFormat.TryFormat(this, destination, out charsWritten, format, provider);
public DateTime ToUniversalTime()
{
diff --git a/src/mscorlib/shared/System/DateTimeOffset.cs b/src/mscorlib/shared/System/DateTimeOffset.cs
index 1498f9365c..3c3f3f42a2 100644
--- a/src/mscorlib/shared/System/DateTimeOffset.cs
+++ b/src/mscorlib/shared/System/DateTimeOffset.cs
@@ -755,26 +755,26 @@ namespace System
public override String ToString()
{
- return DateTimeFormat.Format(ClockDateTime, null, DateTimeFormatInfo.CurrentInfo, Offset);
+ return DateTimeFormat.Format(ClockDateTime, null, null, Offset);
}
public String ToString(String format)
{
- return DateTimeFormat.Format(ClockDateTime, format, DateTimeFormatInfo.CurrentInfo, Offset);
+ return DateTimeFormat.Format(ClockDateTime, format, null, Offset);
}
public String ToString(IFormatProvider formatProvider)
{
- return DateTimeFormat.Format(ClockDateTime, null, DateTimeFormatInfo.GetInstance(formatProvider), Offset);
+ return DateTimeFormat.Format(ClockDateTime, null, formatProvider, Offset);
}
public String ToString(String format, IFormatProvider formatProvider)
{
- return DateTimeFormat.Format(ClockDateTime, format, DateTimeFormatInfo.GetInstance(formatProvider), Offset);
+ return DateTimeFormat.Format(ClockDateTime, format, formatProvider, Offset);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider formatProvider = null) =>
- DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, DateTimeFormatInfo.GetInstance(formatProvider), Offset);
+ DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, formatProvider, Offset);
public DateTimeOffset ToUniversalTime()
{
diff --git a/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs b/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs
index cd3a150033..092ad0365d 100644
--- a/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs
+++ b/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
namespace System
{
@@ -852,9 +853,15 @@ namespace System
offset = offset.Negate();
}
- AppendNumber(result, offset.Hours, 2);
+ Append2DigitNumber(result, offset.Hours);
result.Append(':');
- AppendNumber(result, offset.Minutes, 2);
+ Append2DigitNumber(result, offset.Minutes);
+ }
+
+ private static void Append2DigitNumber(StringBuilder result, int val)
+ {
+ result.Append((char)('0' + (val / 10)));
+ result.Append((char)('0' + (val % 10)));
}
internal static String GetRealFormat(ReadOnlySpan<char> format, DateTimeFormatInfo dtfi)
@@ -981,19 +988,65 @@ namespace System
return GetRealFormat(format, dtfi);
}
- internal static String Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi)
+ internal static String Format(DateTime dateTime, String format, IFormatProvider provider)
{
- return Format(dateTime, format, dtfi, NullOffset);
+ return Format(dateTime, format, provider, NullOffset);
}
- internal static string Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi, TimeSpan offset) =>
- StringBuilderCache.GetStringAndRelease(FormatStringBuilder(dateTime, format, dtfi, offset));
+ internal static string Format(DateTime dateTime, String format, IFormatProvider provider, TimeSpan offset)
+ {
+ if (format != null && format.Length == 1)
+ {
+ // Optimize for these standard formats that are not affected by culture.
+ switch (format[0])
+ {
+ // Round trip format
+ case 'o':
+ case 'O':
+ const int MinFormatOLength = 27, MaxFormatOLength = 33;
+ Span<char> span = stackalloc char[MaxFormatOLength];
+ TryFormatO(dateTime, offset, span, out int ochars);
+ Debug.Assert(ochars >= MinFormatOLength && ochars <= MaxFormatOLength);
+ return span.Slice(0, ochars).ToString();
+
+ // RFC1123
+ case 'r':
+ case 'R':
+ const int FormatRLength = 29;
+ string str = string.FastAllocateString(FormatRLength);
+ TryFormatR(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out int rchars);
+ Debug.Assert(rchars == str.Length);
+ return str;
+ }
+ }
+
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);
+ return StringBuilderCache.GetStringAndRelease(FormatStringBuilder(dateTime, format, dtfi, offset));
+ }
- internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi) =>
- TryFormat(dateTime, destination, out charsWritten, format, dtfi, NullOffset);
+ internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider) =>
+ TryFormat(dateTime, destination, out charsWritten, format, provider, NullOffset);
- internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset)
+ internal static bool TryFormat(DateTime dateTime, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider, TimeSpan offset)
{
+ if (format.Length == 1)
+ {
+ // Optimize for these standard formats that are not affected by culture.
+ switch (format[0])
+ {
+ // Round trip format
+ case 'o':
+ case 'O':
+ return TryFormatO(dateTime, offset, destination, out charsWritten);
+
+ // RFC1123
+ case 'r':
+ case 'R':
+ return TryFormatR(dateTime, offset, destination, out charsWritten);
+ }
+ }
+
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);
StringBuilder sb = FormatStringBuilder(dateTime, format, dtfi, offset);
bool success = sb.Length <= destination.Length;
@@ -1011,7 +1064,7 @@ namespace System
return success;
}
- internal static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset)
+ private static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset)
{
Debug.Assert(dtfi != null);
if (format.Length == 0)
@@ -1060,101 +1113,204 @@ namespace System
if (format.Length == 1)
{
- switch (format[0])
+ format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
+ }
+
+ return FormatCustomized(dateTime, format, dtfi, offset, result: null);
+ }
+
+ // 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 TryFormatO(DateTime dateTime, TimeSpan offset, Span<char> destination, out int charsWritten)
+ {
+ const int MinimumBytesNeeded = 27;
+
+ int charsRequired = MinimumBytesNeeded;
+ DateTimeKind kind = DateTimeKind.Local;
+
+ if (offset == NullOffset)
+ {
+ kind = dateTime.Kind;
+ if (kind == DateTimeKind.Local)
{
- case 'O':
- case 'o':
- return FastFormatRoundtrip(dateTime, offset);
- case 'R':
- case 'r':
- return FastFormatRfc1123(dateTime, offset, dtfi);
+ offset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
+ charsRequired += 6;
+ }
+ else if (kind == DateTimeKind.Utc)
+ {
+ charsRequired += 1;
}
+ }
+ else
+ {
+ charsRequired += 6;
+ }
- format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
+ if (destination.Length < charsRequired)
+ {
+ charsWritten = 0;
+ return false;
}
+ charsWritten = charsRequired;
+
+ // Hoist most of the bounds checks on destination.
+ { var unused = destination[MinimumBytesNeeded - 1]; }
+
+ WriteFourDecimalDigits((uint)dateTime.Year, destination, 0);
+ destination[4] = '-';
+ WriteTwoDecimalDigits((uint)dateTime.Month, destination, 5);
+ destination[7] = '-';
+ WriteTwoDecimalDigits((uint)dateTime.Day, destination, 8);
+ destination[10] = 'T';
+ WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 11);
+ destination[13] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 14);
+ destination[16] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Second, destination, 17);
+ destination[19] = '.';
+ WriteDigits((uint)((ulong)dateTime.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
+
+ if (kind == DateTimeKind.Local)
+ {
+ char sign;
+ if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
+ {
+ sign = '-';
+ offset = TimeSpan.FromTicks(-offset.Ticks);
+ }
+ else
+ {
+ sign = '+';
+ }
- return FormatCustomized(dateTime, format, dtfi, offset, result: null);
+ // Writing the value backward allows the JIT to optimize by
+ // performing a single bounds check against buffer.
+ WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
+ destination[30] = ':';
+ WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
+ destination[27] = sign;
+ }
+ else if (kind == DateTimeKind.Utc)
+ {
+ destination[27] = 'Z';
+ }
+
+ return true;
}
- internal static StringBuilder FastFormatRfc1123(DateTime dateTime, TimeSpan offset, DateTimeFormatInfo dtfi)
+ // Rfc1123
+ // 01234567890123456789012345678
+ // -----------------------------
+ // Tue, 03 Jan 2017 08:08:05 GMT
+ private static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span<char> destination, out int charsWritten)
{
- // ddd, dd MMM yyyy HH:mm:ss GMT
- const int Rfc1123FormatLength = 29;
- StringBuilder result = StringBuilderCache.Acquire(Rfc1123FormatLength);
+ // Writing the check in this fashion elides all bounds checks on 'destination'
+ // for the remainder of the method.
+ if (28 >= (uint)destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
if (offset != NullOffset)
{
- // Convert to UTC invariants
+ // Convert to UTC invariants.
dateTime = dateTime - offset;
}
dateTime.GetDatePart(out int year, out int month, out int day);
- result.Append(InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek]);
- result.Append(',');
- result.Append(' ');
- AppendNumber(result, day, 2);
- result.Append(' ');
- result.Append(InvariantAbbreviatedMonthNames[month - 1]);
- result.Append(' ');
- AppendNumber(result, year, 4);
- result.Append(' ');
- AppendHHmmssTimeOfDay(result, dateTime);
- result.Append(' ');
- result.Append(Gmt);
- return result;
+ string dayAbbrev = InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek];
+ Debug.Assert(dayAbbrev.Length == 3);
+
+ string monthAbbrev = InvariantAbbreviatedMonthNames[month - 1];
+ Debug.Assert(monthAbbrev.Length == 3);
+
+ destination[0] = dayAbbrev[0];
+ destination[1] = dayAbbrev[1];
+ destination[2] = dayAbbrev[2];
+ destination[3] = ',';
+ destination[4] = ' ';
+ WriteTwoDecimalDigits((uint)day, destination, 5);
+ destination[7] = ' ';
+ destination[8] = monthAbbrev[0];
+ destination[9] = monthAbbrev[1];
+ destination[10] = monthAbbrev[2];
+ destination[11] = ' ';
+ WriteFourDecimalDigits((uint)year, destination, 12);
+ destination[16] = ' ';
+ WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 17);
+ destination[19] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 20);
+ destination[22] = ':';
+ WriteTwoDecimalDigits((uint)dateTime.Second, destination, 23);
+ destination[25] = ' ';
+ destination[26] = 'G';
+ destination[27] = 'M';
+ destination[28] = 'T';
+
+ charsWritten = 29;
+ return true;
}
- internal static StringBuilder FastFormatRoundtrip(DateTime dateTime, TimeSpan offset)
+ /// <summary>
+ /// 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)]
+ private static void WriteTwoDecimalDigits(uint value, Span<char> destination, int offset)
{
- // yyyy-MM-ddTHH:mm:ss.fffffffK
- const int roundTripFormatLength = 28;
- StringBuilder result = StringBuilderCache.Acquire(roundTripFormatLength);
+ Debug.Assert(0 <= value && value <= 99);
- dateTime.GetDatePart(out int year, out int month, out int day);
- AppendNumber(result, year, 4);
- result.Append('-');
- AppendNumber(result, month, 2);
- result.Append('-');
- AppendNumber(result, day, 2);
- result.Append('T');
- AppendHHmmssTimeOfDay(result, dateTime);
- result.Append('.');
+ uint temp = '0' + value;
+ value /= 10;
+ destination[offset + 1] = (char)(temp - (value * 10));
+ destination[offset] = (char)('0' + value);
+ }
- long fraction = dateTime.Ticks % TimeSpan.TicksPerSecond;
- AppendNumber(result, fraction, 7);
+ /// <summary>
+ /// 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)]
+ private static void WriteFourDecimalDigits(uint value, Span<char> buffer, int startingIndex = 0)
+ {
+ Debug.Assert(0 <= value && value <= 9999);
- FormatCustomizedRoundripTimeZone(dateTime, offset, result);
+ uint temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 3] = (char)(temp - (value * 10));
- return result;
- }
+ temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 2] = (char)(temp - (value * 10));
- private static void AppendHHmmssTimeOfDay(StringBuilder result, DateTime dateTime)
- {
- // HH:mm:ss
- AppendNumber(result, dateTime.Hour, 2);
- result.Append(':');
- AppendNumber(result, dateTime.Minute, 2);
- result.Append(':');
- AppendNumber(result, dateTime.Second, 2);
+ temp = '0' + value;
+ value /= 10;
+ buffer[startingIndex + 1] = (char)(temp - (value * 10));
+
+ buffer[startingIndex] = (char)('0' + value);
}
- internal static void AppendNumber(StringBuilder builder, long val, int digits)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteDigits(ulong value, Span<char> buffer)
{
- for (int i = 0; i < digits; i++)
- {
- builder.Append('0');
- }
+ // 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.
- int index = 1;
- while (val > 0 && index <= digits)
+ for (int i = buffer.Length - 1; i >= 1; i--)
{
- builder[builder.Length - index] = (char)('0' + (val % 10));
- val = val / 10;
- index++;
+ ulong temp = '0' + value;
+ value /= 10;
+ buffer[i] = (char)(temp - (value * 10));
}
- Debug.Assert(val == 0, "DateTimeFormat.AppendNumber(): digits less than size of val");
+ Debug.Assert(value < 10);
+ buffer[0] = (char)('0' + value);
}
internal static String[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi)