diff options
-rw-r--r-- | src/mscorlib/shared/System/DateTime.cs | 18 | ||||
-rw-r--r-- | src/mscorlib/shared/System/DateTimeOffset.cs | 10 | ||||
-rw-r--r-- | src/mscorlib/shared/System/Globalization/DateTimeFormat.cs | 304 |
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) |