diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs | 356 |
1 files changed, 256 insertions, 100 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs index 595fb5631..73fb0e9b3 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormat.cs +++ b/src/System.Private.CoreLib/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 { @@ -133,8 +134,8 @@ namespace System 's', 't', 'T', 'u', 'U', 'y', 'Y', }; - internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; - internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; + internal const string RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; + internal const string RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; private const int DEFAULT_ALL_DATETIMES_SIZE = 132; @@ -143,7 +144,7 @@ namespace System internal static readonly string[] InvariantAbbreviatedDayNames = InvariantFormatInfo.AbbreviatedDayNames; internal const string Gmt = "GMT"; - internal static String[] fixedNumberFormats = new String[] { + internal static string[] fixedNumberFormats = new string[] { "0", "00", "000", @@ -164,7 +165,7 @@ namespace System // If the digits of the value is greater than len, no leading zero is added. // // Notes: - // The function can format to Int32.MaxValue. + // The function can format to int.MaxValue. // //////////////////////////////////////////////////////////////////////////// internal static void FormatDigits(StringBuilder outputBuffer, int value, int len) @@ -173,7 +174,7 @@ namespace System FormatDigits(outputBuffer, value, len, false); } - internal unsafe static void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit) + internal static unsafe void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit) { Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0"); @@ -222,7 +223,7 @@ namespace System return (index - pos); } - private static String FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi) + private static string FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi) { Debug.Assert(dayOfWeek >= 0 && dayOfWeek <= 6, "dayOfWeek >= 0 && dayOfWeek <= 6"); if (repeat == 3) @@ -234,7 +235,7 @@ namespace System return (dtfi.GetDayName((DayOfWeek)dayOfWeek)); } - private static String FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi) + private static string FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi) { Debug.Assert(month >= 1 && month <= 12, "month >=1 && month <= 12"); if (repeatCount == 3) @@ -275,7 +276,7 @@ namespace System Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7. */ - private static String FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi) + private static string FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi) { Debug.Assert(repeatCount != 3 || repeatCount != 4, "repeateCount should be 3 or 4"); if (dtfi.Calendar.IsLeapYear(dtfi.Calendar.GetYear(time))) @@ -346,7 +347,7 @@ namespace System { // Here we can't find the matching quote. throw new FormatException( - String.Format( + string.Format( CultureInfo.CurrentCulture, SR.Format_BadQuote, quoteChar)); } @@ -668,7 +669,7 @@ namespace System } else { - String fmtPattern = "D" + tokenLen.ToString(); + string fmtPattern = "D" + tokenLen.ToString(); result.Append(year.ToString(fmtPattern, CultureInfo.InvariantCulture)); } } @@ -764,10 +765,10 @@ namespace System // output the 'z' famliy of formats, which output a the offset from UTC, e.g. "-07:30" - private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, ReadOnlySpan<char> format, Int32 tokenLen, Boolean timeOnly, StringBuilder result) + private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, ReadOnlySpan<char> format, int tokenLen, bool timeOnly, StringBuilder result) { // See if the instance already has an offset - Boolean dateTimeFormat = (offset == NullOffset); + bool dateTimeFormat = (offset == NullOffset); if (dateTimeFormat) { // No offset. The instance is a DateTime and the output should be the local time zone @@ -852,14 +853,20 @@ 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); } - internal static String GetRealFormat(ReadOnlySpan<char> format, DateTimeFormatInfo dtfi) + private static void Append2DigitNumber(StringBuilder result, int val) { - String realFormat = null; + result.Append((char)('0' + (val / 10))); + result.Append((char)('0' + (val % 10))); + } + + internal static string GetRealFormat(ReadOnlySpan<char> format, DateTimeFormatInfo dtfi) + { + string realFormat = null; switch (format[0]) { @@ -924,7 +931,7 @@ namespace System // This method also convert the dateTime if necessary (e.g. when the format is in Universal time), // and change dtfi if necessary (e.g. when the format should use invariant culture). // - private static String ExpandPredefinedFormat(ReadOnlySpan<char> format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, ref TimeSpan offset) + private static string ExpandPredefinedFormat(ReadOnlySpan<char> format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, ref TimeSpan offset) { switch (format[0]) { @@ -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,12 +1064,12 @@ 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) { - Boolean timeOnlySpecialCase = false; + bool timeOnlySpecialCase = false; if (dateTime.Ticks < Calendar.TicksPerDay) { // If the time is less than 1 day, consider it as time of day. @@ -1060,108 +1113,211 @@ 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) + internal static string[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi) { Debug.Assert(dtfi != null); - String[] allFormats = null; - String[] results = null; + string[] allFormats = null; + string[] results = null; switch (format) { @@ -1178,7 +1334,7 @@ namespace System case 'y': case 'Y': allFormats = dtfi.GetAllDateTimePatterns(format); - results = new String[allFormats.Length]; + results = new string[allFormats.Length]; for (int i = 0; i < allFormats.Length; i++) { results[i] = Format(dateTime, allFormats[i], dtfi); @@ -1187,7 +1343,7 @@ namespace System case 'U': DateTime universalTime = dateTime.ToUniversalTime(); allFormats = dtfi.GetAllDateTimePatterns(format); - results = new String[allFormats.Length]; + results = new string[allFormats.Length]; for (int i = 0; i < allFormats.Length; i++) { results[i] = Format(universalTime, allFormats[i], dtfi); @@ -1203,7 +1359,7 @@ namespace System case 'O': case 's': case 'u': - results = new String[] { Format(dateTime, new String(format, 1), dtfi) }; + results = new string[] { Format(dateTime, new string(format, 1), dtfi) }; break; default: throw new FormatException(SR.Format_InvalidString); @@ -1211,19 +1367,19 @@ namespace System return (results); } - internal static String[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi) + internal static string[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi) { - List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE); + List<string> results = new List<string>(DEFAULT_ALL_DATETIMES_SIZE); for (int i = 0; i < allStandardFormats.Length; i++) { - String[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi); + string[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi); for (int j = 0; j < strings.Length; j++) { results.Add(strings[j]); } } - String[] value = new String[results.Count]; + string[] value = new string[results.Count]; results.CopyTo(0, value, 0, results.Count); return (value); } |