diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index b453bb5bd0a93b..3c20e81baf9d4b 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -128,8 +128,6 @@
-
-
diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs
index e4f226cd4d0b59..2c9581ab5ed7b3 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Constants.cs
@@ -14,12 +14,6 @@ internal static partial class Utf8Constants
public const byte Space = (byte)' ';
public const byte Hyphen = (byte)'-';
- // Invariant formatting uses groups of 3 for each number group separated by commas.
- // ex. 1,234,567,890
- public const int GroupSize = 3;
-
- public static readonly TimeSpan NullUtcOffset = TimeSpan.MinValue; // Utc offsets must range from -14:00 to 14:00 so this is never a valid offset.
-
public const int DateTimeMaxUtcOffsetHours = 14; // The UTC offset portion of a TimeSpan or DateTime can be no more than 14 hours and no less than -14 hours.
public const int DateTimeNumFractionDigits = 7; // TimeSpan and DateTime formats allow exactly up to many digits for specifying the fraction after the seconds.
diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs
deleted file mode 100644
index 2a5d463e053167..00000000000000
--- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Runtime.InteropServices;
-
-namespace System.Buffers.Text
-{
- public static partial class Utf8Formatter
- {
- //
- // 'G' format for DateTime.
- //
- // 0123456789012345678
- // ---------------------------------
- // 05/25/2017 10:30:15
- //
- // Also handles the default ToString() format for DateTimeOffset
- //
- // 01234567890123456789012345
- // --------------------------
- // 05/25/2017 10:30:15 -08:00
- //
- private static unsafe bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten)
- {
- const int MinimumBytesNeeded = 19;
-
- int bytesRequired = MinimumBytesNeeded;
-
- if (offset != Utf8Constants.NullUtcOffset)
- {
- bytesRequired += 7; // Space['+'|'-']hh:mm
- }
-
- if (destination.Length < bytesRequired)
- {
- bytesWritten = 0;
- return false;
- }
-
- bytesWritten = bytesRequired;
-
- value.GetDate(out int year, out int month, out int day);
- value.GetTime(out int hour, out int minute, out int second);
-
- fixed (byte* dest = &MemoryMarshal.GetReference(destination))
- {
- Number.WriteTwoDigits((uint)month, dest);
- dest[2] = Utf8Constants.Slash;
-
- Number.WriteTwoDigits((uint)day, dest + 3);
- dest[5] = Utf8Constants.Slash;
-
- Number.WriteFourDigits((uint)year, dest + 6);
- dest[10] = Utf8Constants.Space;
-
- Number.WriteTwoDigits((uint)hour, dest + 11);
- dest[13] = Utf8Constants.Colon;
-
- Number.WriteTwoDigits((uint)minute, dest + 14);
- dest[16] = Utf8Constants.Colon;
-
- Number.WriteTwoDigits((uint)second, dest + 17);
-
- if (offset != Utf8Constants.NullUtcOffset)
- {
- int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
- byte sign;
-
- if (offsetTotalMinutes < 0)
- {
- sign = Utf8Constants.Minus;
- offsetTotalMinutes = -offsetTotalMinutes;
- }
- else
- {
- sign = Utf8Constants.Plus;
- }
-
- int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);
-
- dest[19] = Utf8Constants.Space;
- dest[20] = sign;
- Number.WriteTwoDigits((uint)offsetHours, dest + 21);
- dest[23] = Utf8Constants.Colon;
- Number.WriteTwoDigits((uint)offsetMinutes, dest + 24);
- }
- }
-
- return true;
- }
- }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.L.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.L.cs
deleted file mode 100644
index 679f9613fba850..00000000000000
--- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.L.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Globalization;
-using System.Text;
-
-namespace System.Buffers.Text
-{
- public static partial class Utf8Formatter
- {
- // Rfc1123 - lowercase
- //
- // 01234567890123456789012345678
- // -----------------------------
- // tue, 03 jan 2017 08:08:05 gmt
- //
- private static bool TryFormatDateTimeL(DateTime value, Span destination, out int bytesWritten)
- {
- if (value.TryFormat(destination, out bytesWritten, "r", CultureInfo.InvariantCulture))
- {
- Debug.Assert(bytesWritten == 29);
- Ascii.ToLowerInPlace(destination.Slice(0, bytesWritten), out bytesWritten);
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs
index 3d8e68b9c4660b..fc4bb0c6cc29ef 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.cs
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Text;
+
namespace System.Buffers.Text
{
public static partial class Utf8Formatter
@@ -29,18 +32,15 @@ public static partial class Utf8Formatter
///
public static bool TryFormat(DateTimeOffset value, Span destination, out int bytesWritten, StandardFormat format = default)
{
- TimeSpan offset = Utf8Constants.NullUtcOffset;
- char symbol = format.Symbol;
if (format.IsDefault)
{
- symbol = 'G';
- offset = value.Offset;
+ return DateTimeFormat.TryFormatInvariantG(value.DateTime, value.Offset, destination, out bytesWritten);
}
- switch (symbol)
+ switch (format.Symbol)
{
case 'R':
- return DateTimeFormat.TryFormatR(value.UtcDateTime, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten);
+ return DateTimeFormat.TryFormatR(value.UtcDateTime, NullOffset, destination, out bytesWritten);
case 'O':
return DateTimeFormat.TryFormatO(value.DateTime, value.Offset, destination, out bytesWritten);
@@ -49,7 +49,7 @@ public static bool TryFormat(DateTimeOffset value, Span destination, out i
return TryFormatDateTimeL(value.UtcDateTime, destination, out bytesWritten);
case 'G':
- return TryFormatDateTimeG(value.DateTime, offset, destination, out bytesWritten);
+ return DateTimeFormat.TryFormatInvariantG(value.DateTime, NullOffset, destination, out bytesWritten);
default:
ThrowHelper.ThrowFormatException_BadFormatSpecifier();
@@ -83,21 +83,36 @@ public static bool TryFormat(DateTime value, Span destination, out int byt
switch (FormattingHelpers.GetSymbolOrDefault(format, 'G'))
{
case 'R':
- return DateTimeFormat.TryFormatR(value, new TimeSpan(DateTimeFormat.NullOffset), destination, out bytesWritten);
+ return DateTimeFormat.TryFormatR(value, NullOffset, destination, out bytesWritten);
case 'O':
- return DateTimeFormat.TryFormatO(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten);
+ return DateTimeFormat.TryFormatO(value, NullOffset, destination, out bytesWritten);
case 'l':
return TryFormatDateTimeL(value, destination, out bytesWritten);
case 'G':
- return TryFormatDateTimeG(value, Utf8Constants.NullUtcOffset, destination, out bytesWritten);
+ return DateTimeFormat.TryFormatInvariantG(value, NullOffset, destination, out bytesWritten);
default:
ThrowHelper.ThrowFormatException_BadFormatSpecifier();
goto case 'R'; // unreachable
}
}
+
+ // Rfc1123 lowercased
+ private static bool TryFormatDateTimeL(DateTime value, Span destination, out int bytesWritten)
+ {
+ if (DateTimeFormat.TryFormatR(value, NullOffset, destination, out bytesWritten))
+ {
+ Debug.Assert(bytesWritten == DateTimeFormat.FormatRLength);
+ Ascii.ToLowerInPlace(destination.Slice(0, bytesWritten), out bytesWritten);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static TimeSpan NullOffset => new TimeSpan(DateTimeFormat.NullOffset);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs
index 85c86d3dcd29cd..71dd943a444132 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs
@@ -166,6 +166,11 @@ public void Dispose()
}
}
+ // Note that consuming implementations depend on the list only growing if it's absolutely
+ // required. If the list is already large enough to hold the additional items be added,
+ // it must not grow. The list is used in a number of places where the reference is checked
+ // and it's expected to match the initial reference provided to the constructor if that
+ // span was sufficiently large.
private void Grow(int additionalCapacityRequired = 1)
{
const int ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
index 3dbd64024a912d..9ee652e823ed5d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
@@ -772,7 +772,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] stri
}
}
- DateTimeFormat.IsValidCustomDateFormat(format.AsSpan(), throwOnError: true);
+ DateTimeFormat.IsValidCustomDateOnlyFormat(format.AsSpan(), throwOnError: true);
return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider);
}
@@ -820,7 +820,7 @@ private bool TryFormatCore(Span destination, out int charsWritten,
}
}
- if (!DateTimeFormat.IsValidCustomDateFormat(format, throwOnError: false))
+ if (!DateTimeFormat.IsValidCustomDateOnlyFormat(format, throwOnError: false))
{
throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, format.ToString(), nameof(DateOnly)));
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs
index a521731c654714..88d6a0fb098b4f 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs
@@ -132,6 +132,12 @@ internal static class DateTimeFormat
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 FormatOMinLength = 27, FormatOMaxLength = 33;
+ private const int FormatInvariantGMinLength = 19, FormatInvariantGMaxLength = 26;
+ internal const int FormatRLength = 29;
+ private const int FormatSLength = 19;
+ private const int FormatuLength = 20;
+
private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
internal static readonly DateTimeFormatInfo InvariantFormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
@@ -190,9 +196,8 @@ internal static unsafe void FormatDigits(ref ValueListBuilder outp
internal static int ParseRepeatPattern(ReadOnlySpan format, int pos, char patternChar)
{
- int len = format.Length;
int index = pos + 1;
- while ((index < len) && (format[index] == patternChar))
+ while ((uint)index < (uint)format.Length && format[index] == patternChar)
{
index++;
}
@@ -447,8 +452,12 @@ private static void FormatCustomized(
case 'h':
tokenLen = ParseRepeatPattern(format, i, ch);
- hour12 = dateTime.Hour % 12;
- if (hour12 == 0)
+ hour12 = dateTime.Hour;
+ if (hour12 > 12)
+ {
+ hour12 -= 12;
+ }
+ else if (hour12 == 0)
{
hour12 = 12;
}
@@ -503,7 +512,7 @@ private static void FormatCustomized(
else
{
// No fraction to emit, so see if we should remove decimal also.
- if (result.Length > 0 && result[result.Length - 1] == TChar.CastFrom('.'))
+ if (result.Length > 0 && result[^1] == TChar.CastFrom('.'))
{
result.Length--;
}
@@ -723,11 +732,10 @@ private static void FormatCustomized(
break;
default:
- // NOTENOTE : we can remove this rule if we enforce the enforced quote
- // character rule.
+ // NOTENOTE : we can remove this rule if we enforce the enforced quote character rule.
// That is, if we ask everyone to use single quote or double quote to insert characters,
// then we can remove this default block.
- result.Append(TChar.CastFrom(ch));
+ AppendChar(ref result, ch);
tokenLen = 1;
break;
}
@@ -885,236 +893,287 @@ private static unsafe void FormatCustomizedRoundripTimeZone(DateTime date
}
}
- internal static string GetRealFormat(ReadOnlySpan format, DateTimeFormatInfo dtfi)
- {
- string realFormat;
-
- switch (format[0])
+ internal static string ExpandStandardFormatToCustomPattern(char format, DateTimeFormatInfo dtfi) =>
+ format switch
{
- case 'd': // Short Date
- realFormat = dtfi.ShortDatePattern;
- break;
- case 'D': // Long Date
- realFormat = dtfi.LongDatePattern;
- break;
- case 'f': // Full (long date + short time)
- realFormat = dtfi.LongDatePattern + " " + dtfi.ShortTimePattern;
- break;
- case 'F': // Full (long date + long time)
- realFormat = dtfi.FullDateTimePattern;
- break;
- case 'g': // General (short date + short time)
- realFormat = dtfi.GeneralShortTimePattern;
- break;
- case 'G': // General (short date + long time)
- realFormat = dtfi.GeneralLongTimePattern;
- break;
- case 'm':
- case 'M': // Month/Day Date
- realFormat = dtfi.MonthDayPattern;
- break;
- case 'o':
- case 'O':
- realFormat = RoundtripFormat;
- break;
- case 'r':
- case 'R': // RFC 1123 Standard
- realFormat = dtfi.RFC1123Pattern;
- break;
- case 's': // Sortable without Time Zone Info
- realFormat = dtfi.SortableDateTimePattern;
- break;
- case 't': // Short Time
- realFormat = dtfi.ShortTimePattern;
- break;
- case 'T': // Long Time
- realFormat = dtfi.LongTimePattern;
- break;
- case 'u': // Universal with Sortable format
- realFormat = dtfi.UniversalSortableDateTimePattern;
- break;
- case 'U': // Universal with Full (long date + long time) format
- realFormat = dtfi.FullDateTimePattern;
- break;
- case 'y':
- case 'Y': // Year/Month Date
- realFormat = dtfi.YearMonthPattern;
- break;
- default:
- throw new FormatException(SR.Format_InvalidString);
- }
- return realFormat;
- }
+ 'd' => dtfi.ShortDatePattern, // Short Date
+ 'D' => dtfi.LongDatePattern, // Long Date
+ 'f' => dtfi.LongDatePattern + " " + dtfi.ShortTimePattern, // Full (long date + short time)
+ 'F' => dtfi.FullDateTimePattern, // Full (long date + long time)
+ 'g' => dtfi.GeneralShortTimePattern, // General (short date + short time)
+ 'G' => dtfi.GeneralLongTimePattern, // General (short date + long time)
+ 'm' or 'M' => dtfi.MonthDayPattern, // Month/Day Date
+ 'o' or 'O' => RoundtripFormat, // Roundtrip Format
+ 'r' or 'R' => dtfi.RFC1123Pattern, // RFC 1123 Standard
+ 's' => dtfi.SortableDateTimePattern, // Sortable without Time Zone Info
+ 't' => dtfi.ShortTimePattern, // Short Time
+ 'T' => dtfi.LongTimePattern, // Long Time
+ 'u' => dtfi.UniversalSortableDateTimePattern, // Universal with Sortable format
+ 'U' => dtfi.FullDateTimePattern, // Universal with Full (long date + long time) format
+ 'y' or 'Y' => dtfi.YearMonthPattern, // Year/Month Date
+ _ => throw new FormatException(SR.Format_InvalidString),
+ };
+
+ internal static string Format(DateTime dateTime, string? format, IFormatProvider? provider) =>
+ Format(dateTime, format, provider, new TimeSpan(NullOffset));
- // Expand a pre-defined format string (like "D" for long date) to the real format that
- // we are going to use in the date time parsing.
- // 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 format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, TimeSpan offset)
+ internal static string Format(DateTime dateTime, string? format, IFormatProvider? provider, TimeSpan offset)
{
- switch (format[0])
+ DateTimeFormatInfo dtfi;
+
+ if (string.IsNullOrEmpty(format))
{
- case 'o':
- case 'O': // Round trip format
- dtfi = DateTimeFormatInfo.InvariantInfo;
- break;
- case 'r':
- case 'R': // RFC 1123 Standard
- case 'u': // Universal time in sortable format.
- if (offset.Ticks != NullOffset)
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+
+ if (offset.Ticks == NullOffset) // default DateTime.ToString case
+ {
+ if (IsTimeOnlySpecialCase(dateTime, dtfi))
{
- // Convert to UTC invariants mean this will be in range
- dateTime -= offset;
+ string str = string.FastAllocateString(FormatSLength);
+ TryFormatS(dateTime, new Span(ref str.GetRawStringData(), str.Length), out int charsWritten);
+ Debug.Assert(charsWritten == FormatSLength);
+ return str;
}
- dtfi = DateTimeFormatInfo.InvariantInfo;
- break;
- case 's': // Sortable without Time Zone Info
- dtfi = DateTimeFormatInfo.InvariantInfo;
- break;
- case 'U': // Universal time in culture dependent format.
- if (offset.Ticks != NullOffset)
+ else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
{
- // This format is not supported by DateTimeOffset
- throw new FormatException(SR.Format_InvalidString);
+ string str = string.FastAllocateString(FormatInvariantGMinLength);
+ TryFormatInvariantG(dateTime, offset, new Span(ref str.GetRawStringData(), str.Length), out int charsWritten);
+ Debug.Assert(charsWritten == FormatInvariantGMinLength);
+ return str;
}
- // Universal time is always in Gregorian calendar.
- //
- // Change the Calendar to be Gregorian Calendar.
- //
- dtfi = (DateTimeFormatInfo)dtfi.Clone();
- if (dtfi.Calendar.GetType() != typeof(GregorianCalendar))
+ else
{
- dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
+ format = dtfi.GeneralLongTimePattern; // "G"
}
- dateTime = dateTime.ToUniversalTime();
- break;
+ }
+ else // default DateTimeOffset.ToString case
+ {
+ if (IsTimeOnlySpecialCase(dateTime, dtfi))
+ {
+ format = RoundtripDateTimeUnfixed;
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ }
+ else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
+ {
+ string str = string.FastAllocateString(FormatInvariantGMaxLength);
+ TryFormatInvariantG(dateTime, offset, new Span(ref str.GetRawStringData(), str.Length), out int charsWritten);
+ Debug.Assert(charsWritten == FormatInvariantGMaxLength);
+ return str;
+ }
+ else
+ {
+ format = dtfi.DateTimeOffsetPattern;
+ }
+ }
}
- return GetRealFormat(format, dtfi);
- }
-
- internal static string Format(DateTime dateTime, string? format, IFormatProvider? provider)
- {
- return Format(dateTime, format, provider, new TimeSpan(NullOffset));
- }
-
- internal static string Format(DateTime dateTime, string? format, IFormatProvider? provider, TimeSpan offset)
- {
- if (format != null && format.Length == 1)
+ else if (format.Length == 1)
{
- // Optimize for these standard formats that are not affected by culture.
- switch ((char)(format[0] | 0x20))
+ int charsWritten;
+ string str;
+ switch (format[0])
{
// Round trip format
- case 'o':
- const int MinFormatOLength = 27, MaxFormatOLength = 33;
- Span 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':
- const int FormatRLength = 29;
- string str = string.FastAllocateString(FormatRLength);
- TryFormatR(dateTime, offset, new Span(ref str.GetRawStringData(), str.Length), out int rchars);
- Debug.Assert(rchars == str.Length);
+ case 'o' or 'O':
+ Span span = stackalloc char[FormatOMaxLength];
+ TryFormatO(dateTime, offset, span, out charsWritten);
+ Debug.Assert(charsWritten is >= FormatOMinLength and <= FormatOMaxLength);
+ return span.Slice(0, charsWritten).ToString();
+
+ // RFC1123 format
+ case 'r' or 'R':
+ str = string.FastAllocateString(FormatRLength);
+ TryFormatR(dateTime, offset, new Span(ref str.GetRawStringData(), str.Length), out charsWritten);
+ Debug.Assert(charsWritten == str.Length);
return str;
+
+ // Sortable format
+ case 's':
+ str = string.FastAllocateString(FormatSLength);
+ TryFormatS(dateTime, new Span(ref str.GetRawStringData(), str.Length), out charsWritten);
+ Debug.Assert(charsWritten == str.Length);
+ return str;
+
+ // Universal time in sortable format
+ case 'u':
+ str = string.FastAllocateString(FormatuLength);
+ TryFormatu(dateTime, offset, new Span(ref str.GetRawStringData(), str.Length), out charsWritten);
+ Debug.Assert(charsWritten == str.Length);
+ return str;
+
+ // Universal time in culture dependent format
+ case 'U':
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ PrepareFormatU(ref dateTime, ref dtfi, offset);
+ format = dtfi.FullDateTimePattern;
+ break;
+
+ // All other standard formats
+ default:
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ format = ExpandStandardFormatToCustomPattern(format[0], dtfi);
+ break;
}
}
+ else
+ {
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ }
var vlb = new ValueListBuilder(stackalloc char[256]);
- FormatIntoBuilder(dateTime, format, DateTimeFormatInfo.GetInstance(provider), offset, ref vlb);
+ FormatCustomized(dateTime, format, dtfi, offset, ref vlb);
string resultString = vlb.AsSpan().ToString();
vlb.Dispose();
return resultString;
}
- internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IUtfChar =>
- TryFormat(dateTime, destination, out written, format, provider, new TimeSpan(NullOffset));
+ internal static bool TryFormat(DateTime dateTime, Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) where TChar : unmanaged, IUtfChar =>
+ TryFormat(dateTime, destination, out charsWritten, format, provider, new TimeSpan(NullOffset));
- internal static bool TryFormat(DateTime dateTime, Span destination, out int written, ReadOnlySpan format, IFormatProvider? provider, TimeSpan offset) where TChar : unmanaged, IUtfChar
+ internal static bool TryFormat(DateTime dateTime, Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider, TimeSpan offset) where TChar : unmanaged, IUtfChar
{
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
- if (format.Length == 1)
+ DateTimeFormatInfo dtfi;
+
+ if (format.IsEmpty)
{
- // Optimize for these standard formats that are not affected by culture.
- switch ((char)(format[0] | 0x20))
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+
+ if (offset.Ticks == NullOffset) // default DateTime.ToString case
+ {
+ if (IsTimeOnlySpecialCase(dateTime, dtfi))
+ {
+ return TryFormatS(dateTime, destination, out charsWritten);
+ }
+ else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
+ {
+ return TryFormatInvariantG(dateTime, offset, destination, out charsWritten);
+ }
+ else
+ {
+ format = dtfi.GeneralLongTimePattern; // "G"
+ }
+ }
+ else // default DateTimeOffset.ToString case
+ {
+ if (IsTimeOnlySpecialCase(dateTime, dtfi))
+ {
+ format = RoundtripDateTimeUnfixed;
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ }
+ else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
+ {
+ return TryFormatInvariantG(dateTime, offset, destination, out charsWritten);
+ }
+ else
+ {
+ format = dtfi.DateTimeOffsetPattern;
+ }
+ }
+ }
+ else if (format.Length == 1)
+ {
+ switch (format[0])
{
// Round trip format
- case 'o':
- return TryFormatO(dateTime, offset, destination, out written);
+ case 'o' or 'O':
+ return TryFormatO(dateTime, offset, destination, out charsWritten);
- // RFC1123
- case 'r':
- return TryFormatR(dateTime, offset, destination, out written);
+ // RFC1123 format
+ case 'r' or 'R':
+ return TryFormatR(dateTime, offset, destination, out charsWritten);
+
+ // Sortable format
+ case 's':
+ return TryFormatS(dateTime, destination, out charsWritten);
+
+ // Universal time in sortable format
+ case 'u':
+ return TryFormatu(dateTime, offset, destination, out charsWritten);
+
+ // Universal time in culture dependent format
+ case 'U':
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ PrepareFormatU(ref dateTime, ref dtfi, offset);
+ format = dtfi.FullDateTimePattern;
+ break;
+
+ // All other standard formats
+ default:
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ format = ExpandStandardFormatToCustomPattern(format[0], dtfi);
+ break;
}
}
+ else
+ {
+ dtfi = DateTimeFormatInfo.GetInstance(provider);
+ }
- var vlb = new ValueListBuilder(stackalloc TChar[256]);
- FormatIntoBuilder(dateTime, format, DateTimeFormatInfo.GetInstance(provider), offset, ref vlb);
- bool copied = vlb.TryCopyTo(destination, out written);
+ var vlb = new ValueListBuilder(destination);
+ FormatCustomized(dateTime, format, dtfi, offset, ref vlb);
+ bool success = Unsafe.AreSame(ref MemoryMarshal.GetReference(destination), ref MemoryMarshal.GetReference(vlb.AsSpan()));
+ if (success)
+ {
+ // The reference inside of the builder is still the destination. That means the builder didn't need to grow to beyond
+ // the space in the destination, which means the formatting operation was successful and fully wrote the data to
+ // the destination. All we need to do now is store how much was written.
+ charsWritten = vlb.Length;
+ }
+ else
+ {
+ // The reference inside of the builder is no longer the destination. That means the builder needed to grow beyond
+ // the builder. However, it's possible it grew unnecessarily, e.g. when formatting a fraction it might grow but then
+ // realize it didn't need to write any data and remove a preceding period. As such, we need to try to copy the data
+ // just in case it does actually fit.
+ success = vlb.TryCopyTo(destination, out charsWritten);
+ }
vlb.Dispose();
- return copied;
+ return success;
}
- private static void FormatIntoBuilder(DateTime dateTime, ReadOnlySpan format, DateTimeFormatInfo dtfi, TimeSpan offset, ref ValueListBuilder result) where TChar : unmanaged, IUtfChar
+ /// Check whether this DateTime needs to be treated specially as time-only for formatting purposes.
+ /// This is only relevant when no format is specified.
+ private static bool IsTimeOnlySpecialCase(DateTime dateTime, DateTimeFormatInfo dtfi) =>
+ // If the time is less than 1 day, consider it as time of day. This is a workaround for VB,
+ // since they use ticks less then one day to be time of day. In cultures which use calendar
+ // other than Gregorian calendar, these alternative calendar may not support ticks less than
+ // a day. For example, Japanese calendar only supports date after 1868/9/8. This will pose a
+ // problem when people in VB get the time of day, and use it to call ToString(), which will
+ // use the general format (short date + long time). Since Japanese calendar does not support
+ // Gregorian year 0001, an exception will be thrown when we try to get the Japanese year for
+ // Gregorian year 0001. Therefore, the workaround allows them to call ToString() for time of
+ // day from a DateTime by formatting as ISO 8601 format.
+ dateTime.Ticks < Calendar.TicksPerDay &&
+ dtfi.Calendar.ID is
+ CalendarId.JAPAN or
+ CalendarId.TAIWAN or
+ CalendarId.HIJRI or
+ CalendarId.HEBREW or
+ CalendarId.JULIAN or
+ CalendarId.UMALQURA or
+ CalendarId.PERSIAN;
+
+ /// For handling the "U" format, update the DateTime and DateTimeFormatInfo appropriately.
+ private static void PrepareFormatU(ref DateTime dateTime, ref DateTimeFormatInfo dtfi, TimeSpan offset)
{
- Debug.Assert(dtfi != null);
- if (format.Length == 0)
+ if (offset.Ticks != NullOffset)
{
- bool timeOnlySpecialCase = false;
- if (dateTime.Ticks < Calendar.TicksPerDay)
- {
- // If the time is less than 1 day, consider it as time of day.
- // Just print out the short time format.
- //
- // This is a workaround for VB, since they use ticks less then one day to be
- // time of day. In cultures which use calendar other than Gregorian calendar, these
- // alternative calendar may not support ticks less than a day.
- // For example, Japanese calendar only supports date after 1868/9/8.
- // This will pose a problem when people in VB get the time of day, and use it
- // to call ToString(), which will use the general format (short date + long time).
- // Since Japanese calendar does not support Gregorian year 0001, an exception will be
- // thrown when we try to get the Japanese year for Gregorian year 0001.
- // Therefore, the workaround allows them to call ToString() for time of day from a DateTime by
- // formatting as ISO 8601 format.
- switch (dtfi.Calendar.ID)
- {
- case CalendarId.JAPAN:
- case CalendarId.TAIWAN:
- case CalendarId.HIJRI:
- case CalendarId.HEBREW:
- case CalendarId.JULIAN:
- case CalendarId.UMALQURA:
- case CalendarId.PERSIAN:
- timeOnlySpecialCase = true;
- dtfi = DateTimeFormatInfo.InvariantInfo;
- break;
- }
- }
- if (offset.Ticks == NullOffset)
- {
- // Default DateTime.ToString case.
- format = timeOnlySpecialCase ? "s" : "G";
- }
- else
- {
- // Default DateTimeOffset.ToString case.
- format = timeOnlySpecialCase ? RoundtripDateTimeUnfixed : dtfi.DateTimeOffsetPattern;
- }
+ // This format is not supported by DateTimeOffset
+ throw new FormatException(SR.Format_InvalidString);
}
- if (format.Length == 1)
+ // Universal time is always in Gregorian calendar. Ensure Gregorian is used.
+ // Change the Calendar to be Gregorian Calendar.
+ if (dtfi.Calendar.GetType() != typeof(GregorianCalendar))
{
- format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, offset);
+ dtfi = (DateTimeFormatInfo)dtfi.Clone();
+ dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
}
-
- FormatCustomized(dateTime, format, dtfi, offset, ref result);
+ dateTime = dateTime.ToUniversalTime();
}
- internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool throwOnError)
+ internal static bool IsValidCustomDateOnlyFormat(ReadOnlySpan format, bool throwOnError)
{
int i = 0;
@@ -1185,7 +1244,7 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool thr
}
- internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool throwOnError)
+ internal static bool IsValidCustomTimeOnlyFormat(ReadOnlySpan format, bool throwOnError)
{
int length = format.Length;
int i = 0;
@@ -1379,9 +1438,7 @@ internal static unsafe bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int y
// 2017-06-12T05:30:45.7680000 (interpreted as local time wrt to current time zone)
internal static unsafe bool TryFormatO(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar
{
- const int MinimumBytesNeeded = 27;
-
- int charsRequired = MinimumBytesNeeded;
+ int charsRequired = FormatOMinLength;
DateTimeKind kind = DateTimeKind.Local;
if (offset.Ticks == NullOffset)
@@ -1455,19 +1512,95 @@ internal static unsafe bool TryFormatO(DateTime dateTime, TimeSpan offset
return true;
}
+ // Sortable format. Offset and Kind are ignored.
+ // 012345678901234567890123456789012
+ // ---------------------------------
+ // 2017-06-12T05:30:45
+ internal static unsafe bool TryFormatS(DateTime dateTime, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar
+ {
+ if (destination.Length < FormatSLength)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = FormatSLength;
+
+ dateTime.GetDate(out int year, out int month, out int day);
+ dateTime.GetTime(out int hour, out int minute, out int second);
+
+ fixed (TChar* dest = &MemoryMarshal.GetReference(destination))
+ {
+ Number.WriteFourDigits((uint)year, dest);
+ dest[4] = TChar.CastFrom('-');
+ Number.WriteTwoDigits((uint)month, dest + 5);
+ dest[7] = TChar.CastFrom('-');
+ Number.WriteTwoDigits((uint)day, dest + 8);
+ dest[10] = TChar.CastFrom('T');
+ Number.WriteTwoDigits((uint)hour, dest + 11);
+ dest[13] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)minute, dest + 14);
+ dest[16] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)second, dest + 17);
+ }
+
+ return true;
+ }
+
+ // Sortable universal format. Kind is ignored.
+ // 012345678901234567890123456789012
+ // ---------------------------------
+ // 2017-06-12 05:30:45Z
+ internal static unsafe bool TryFormatu(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar
+ {
+ if (destination.Length < FormatuLength)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = FormatuLength;
+
+ if (offset.Ticks != NullOffset)
+ {
+ dateTime -= offset;
+ }
+
+ dateTime.GetDate(out int year, out int month, out int day);
+ dateTime.GetTime(out int hour, out int minute, out int second);
+
+ fixed (TChar* dest = &MemoryMarshal.GetReference(destination))
+ {
+ Number.WriteFourDigits((uint)year, dest);
+ dest[4] = TChar.CastFrom('-');
+ Number.WriteTwoDigits((uint)month, dest + 5);
+ dest[7] = TChar.CastFrom('-');
+ Number.WriteTwoDigits((uint)day, dest + 8);
+ dest[10] = TChar.CastFrom(' ');
+ Number.WriteTwoDigits((uint)hour, dest + 11);
+ dest[13] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)minute, dest + 14);
+ dest[16] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)second, dest + 17);
+ dest[19] = TChar.CastFrom('Z');
+ }
+
+ return true;
+ }
+
// Rfc1123
// 01234567890123456789012345678
// -----------------------------
// Tue, 03 Jan 2017 08:08:05 GMT
internal static unsafe bool TryFormatR(DateTime dateTime, TimeSpan offset, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar
{
- if (destination.Length <= 28)
+ if (destination.Length < FormatRLength)
{
charsWritten = 0;
return false;
}
- charsWritten = 29;
+ charsWritten = FormatRLength;
if (offset.Ticks != NullOffset)
{
@@ -1515,6 +1648,71 @@ internal static unsafe bool TryFormatR(DateTime dateTime, TimeSpan offset
return true;
}
+ // 'G' format for DateTime when using the invariant culture
+ // 0123456789012345678
+ // ---------------------------------
+ // 05/25/2017 10:30:15
+ //
+ // Also default "" format for DateTimeOffset when using the invariant culture
+ // 01234567890123456789012345
+ // --------------------------
+ // 05/25/2017 10:30:15 -08:00
+ internal static unsafe bool TryFormatInvariantG(DateTime value, TimeSpan offset, Span destination, out int bytesWritten) where TChar : unmanaged, IUtfChar
+ {
+ int bytesRequired = FormatInvariantGMinLength;
+ if (offset.Ticks != NullOffset)
+ {
+ bytesRequired += 7; // Space['+'|'-']hh:mm
+ }
+
+ if (destination.Length < bytesRequired)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ bytesWritten = bytesRequired;
+
+ value.GetDate(out int year, out int month, out int day);
+ value.GetTime(out int hour, out int minute, out int second);
+
+ fixed (TChar* dest = &MemoryMarshal.GetReference(destination))
+ {
+ Number.WriteTwoDigits((uint)month, dest);
+ dest[2] = TChar.CastFrom('/');
+ Number.WriteTwoDigits((uint)day, dest + 3);
+ dest[5] = TChar.CastFrom('/');
+ Number.WriteFourDigits((uint)year, dest + 6);
+ dest[10] = TChar.CastFrom(' ');
+
+ Number.WriteTwoDigits((uint)hour, dest + 11);
+ dest[13] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)minute, dest + 14);
+ dest[16] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)second, dest + 17);
+
+ if (offset.Ticks != NullOffset)
+ {
+ int offsetMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
+ TChar sign = TChar.CastFrom('+');
+ if (offsetMinutes < 0)
+ {
+ sign = TChar.CastFrom('-');
+ offsetMinutes = -offsetMinutes;
+ }
+ (int offsetHours, offsetMinutes) = Math.DivRem(offsetMinutes, 60);
+
+ dest[19] = TChar.CastFrom(' ');
+ dest[20] = sign;
+ Number.WriteTwoDigits((uint)offsetHours, dest + 21);
+ dest[23] = TChar.CastFrom(':');
+ Number.WriteTwoDigits((uint)offsetMinutes, dest + 24);
+ }
+ }
+
+ return true;
+ }
+
internal static string[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi)
{
Debug.Assert(dtfi != null);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs
index e661479b8e4f98..e0e2e7b5ffac9c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs
@@ -51,10 +51,6 @@ internal enum DateTimeFormatFlags
public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
{
- // cache for the invariant culture.
- // invariantInfo is constant irrespective of your current culture.
- private static volatile DateTimeFormatInfo? s_invariantInfo;
-
// an index which points to a record in Culture Data Table.
private readonly CultureData _cultureData;
@@ -292,20 +288,7 @@ private void InitializeOverridableProperties(CultureData cultureData, CalendarId
/// Returns a default DateTimeFormatInfo that will be universally
/// supported and constant irrespective of the current culture.
///
- public static DateTimeFormatInfo InvariantInfo
- {
- get
- {
- if (s_invariantInfo == null)
- {
- DateTimeFormatInfo info = new DateTimeFormatInfo();
- info.Calendar.SetReadOnlyState(true);
- info._isReadOnly = true;
- s_invariantInfo = info;
- }
- return s_invariantInfo;
- }
- }
+ public static DateTimeFormatInfo InvariantInfo => DateTimeFormat.InvariantFormatInfo;
///
/// Returns the current culture's DateTimeFormatInfo.
@@ -541,14 +524,15 @@ public string GetEraName(int era)
}
// The following is based on the assumption that the era value is starting from 1, and has a
- // serial values.
- // If that ever changes, the code has to be changed.
- if ((--era) < EraNames.Length && (era >= 0))
+ // serial values. If that ever changes, the code has to be changed.
+ string[] names = EraNames;
+ era--;
+ if ((uint)era >= names.Length)
{
- return m_eraNames![era];
+ throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue);
}
- throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue);
+ return names[era];
}
internal string[] AbbreviatedEraNames => m_abbrevEraNames ??= _cultureData.AbbrevEraNames(Calendar.ID);
@@ -570,12 +554,14 @@ public string GetAbbreviatedEraName(int era)
era = Calendar.CurrentEraValue;
}
- if ((--era) < m_abbrevEraNames!.Length && (era >= 0))
+ string[] names = m_abbrevEraNames!;
+ era--;
+ if ((uint)era >= (uint)names.Length)
{
- return m_abbrevEraNames[era];
+ throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue);
}
- throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue);
+ return names[era];
}
internal string[] AbbreviatedEnglishEraNames
@@ -839,10 +825,7 @@ internal ReadOnlySpan PMDesignatorTChar() where TChar : unmanaged,
///
public string ShortDatePattern
{
- get
- {
- return shortDatePattern ??= UnclonedShortDatePatterns[0]; // initialize from the 1st array value if not set
- }
+ get => shortDatePattern ??= UnclonedShortDatePatterns[0]; // initialize from the 1st array value if not set
set
{
if (IsReadOnly)
@@ -914,7 +897,7 @@ private void OnShortTimePatternChanged()
internal string GeneralShortTimePattern => generalShortTimePattern ??= ShortDatePattern + " " + ShortTimePattern;
///
- /// Return the pattern for 'g' general format: shortDate + Long time.
+ /// Return the pattern for 'G' general format: shortDate + Long time.
/// We put this internal property here so that we can avoid doing the
/// concatation every time somebody asks for the general format.
///
@@ -935,9 +918,10 @@ internal string DateTimeOffsetPattern
bool foundZ = false;
bool inQuote = false;
char quote = '\'';
- for (int i = 0; !foundZ && i < LongTimePattern.Length; i++)
+ string longTimePattern = LongTimePattern;
+ for (int i = 0; !foundZ && i < longTimePattern.Length; i++)
{
- switch (LongTimePattern[i])
+ switch (longTimePattern[i])
{
case 'z':
/* if we aren't in a quote, we've found a z */
@@ -946,14 +930,14 @@ internal string DateTimeOffsetPattern
break;
case '\'':
case '\"':
- if (inQuote && (quote == LongTimePattern[i]))
+ if (inQuote && (quote == longTimePattern[i]))
{
/* we were in a quote and found a matching exit quote, so we are outside a quote now */
inQuote = false;
}
else if (!inQuote)
{
- quote = LongTimePattern[i];
+ quote = longTimePattern[i];
inQuote = true;
}
else
@@ -971,8 +955,8 @@ internal string DateTimeOffsetPattern
}
dateTimeOffsetPattern = foundZ ?
- ShortDatePattern + " " + LongTimePattern :
- ShortDatePattern + " " + LongTimePattern + " zzz";
+ ShortDatePattern + " " + longTimePattern :
+ ShortDatePattern + " " + longTimePattern + " zzz";
}
return dateTimeOffsetPattern;
}
@@ -1189,15 +1173,13 @@ internal string InternalGetMonthName(int month, MonthNameStyles style, bool abbr
// The month range is from 1 ~ m_monthNames.Length
// (actually is 13 right now for all cases)
- if ((month < 1) || (month > monthNamesArray.Length))
+ month--;
+ if ((uint)month >= (uint)monthNamesArray.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(month),
- month,
- SR.Format(SR.ArgumentOutOfRange_Range, 1, monthNamesArray.Length));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, monthNamesArray.Length);
}
- return monthNamesArray[month - 1];
+ return monthNamesArray[month];
}
///
@@ -1248,17 +1230,14 @@ internal string[] InternalGetLeapYearMonthNames()
public string GetAbbreviatedDayName(DayOfWeek dayofweek)
{
- if (dayofweek < DayOfWeek.Sunday || dayofweek > DayOfWeek.Saturday)
+ string[] names = InternalGetAbbreviatedDayOfWeekNames(); // Use the internal method to avoid a clone.
+ int dow = (int)dayofweek;
+ if ((uint)dow >= (uint)names.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(dayofweek),
- dayofweek,
- SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Saturday);
}
- // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
- // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
- return InternalGetAbbreviatedDayOfWeekNames()[(int)dayofweek];
+ return names[dow];
}
///
@@ -1266,17 +1245,14 @@ public string GetAbbreviatedDayName(DayOfWeek dayofweek)
///
public string GetShortestDayName(DayOfWeek dayOfWeek)
{
- if (dayOfWeek < DayOfWeek.Sunday || dayOfWeek > DayOfWeek.Saturday)
+ string[] names = InternalGetSuperShortDayNames();
+ int dow = (int)dayOfWeek;
+ if ((uint)dow >= (uint)names.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(dayOfWeek),
- dayOfWeek,
- SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayOfWeek), dayOfWeek, DayOfWeek.Sunday, DayOfWeek.Saturday);
}
- // Don't call the public property SuperShortDayNames here since a clone is needed in that
- // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
- return InternalGetSuperShortDayNames()[(int)dayOfWeek];
+ return names[dow];
}
///
@@ -1381,44 +1357,38 @@ public string[] GetAllDateTimePatterns(char format)
public string GetDayName(DayOfWeek dayofweek)
{
- if ((int)dayofweek < 0 || (int)dayofweek > 6)
+ string[] names = InternalGetDayOfWeekNames(); // Use the internal method so we don't clone the array unnecessarily
+ int dow = (int)dayofweek;
+ if ((uint)dow >= (uint)names.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(dayofweek),
- dayofweek,
- SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Saturday);
}
- // Use the internal one so that we don't clone the array unnecessarily
- return InternalGetDayOfWeekNames()[(int)dayofweek];
+ return names[dow];
}
public string GetAbbreviatedMonthName(int month)
{
- if (month < 1 || month > 13)
+ string[] names = InternalGetAbbreviatedMonthNames(); // Use the internal method so we don't clone the array unnecessarily
+ month--;
+ if ((uint)month >= (uint)names.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(month),
- month,
- SR.Format(SR.ArgumentOutOfRange_Range, 1, 13));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13);
}
- // Use the internal one so we don't clone the array unnecessarily
- return InternalGetAbbreviatedMonthNames()[month - 1];
+ return names[month];
}
public string GetMonthName(int month)
{
- if (month < 1 || month > 13)
+ string[] names = InternalGetMonthNames(); // Use the internal method so we don't clone the array unnecessarily
+ month--;
+ if ((uint)month >= (uint)names.Length)
{
- throw new ArgumentOutOfRangeException(
- nameof(month),
- month,
- SR.Format(SR.ArgumentOutOfRange_Range, 1, 13));
+ ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13);
}
- // Use the internal one so we don't clone the array unnecessarily
- return InternalGetMonthNames()[month - 1];
+ return names[month];
}
///
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs
index ddf5bae5452cbc..5f539984d7b4f3 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs
@@ -3904,13 +3904,13 @@ X X X Parsed year Parsed month Parsed day
// This method also set the dtfi according/parseInfo to some special pre-defined
// formats.
//
- private static string ExpandPredefinedFormat(ReadOnlySpan format, scoped ref DateTimeFormatInfo dtfi, scoped ref ParsingInfo parseInfo, scoped ref DateTimeResult result)
+ private static string ExpandPredefinedFormat(char format, scoped ref DateTimeFormatInfo dtfi, scoped ref ParsingInfo parseInfo, scoped ref DateTimeResult result)
{
//
// Check the format to see if we need to override the dtfi to be InvariantInfo,
// and see if we need to set up the userUniversalTime flag.
//
- switch (format[0])
+ switch (format)
{
case 's': // Sortable format (in local time)
case 'o':
@@ -3956,7 +3956,7 @@ private static string ExpandPredefinedFormat(ReadOnlySpan format, scoped r
//
// Expand the pre-defined format character to the real format from DateTimeFormatInfo.
//
- return DateTimeFormat.GetRealFormat(format, dtfi);
+ return DateTimeFormat.ExpandStandardFormatToCustomPattern(format, dtfi);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -4601,7 +4601,7 @@ private static bool DoStrictParse(
return false;
}
- formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result);
+ formatParam = ExpandPredefinedFormat(formatParamChar, ref dtfi, ref parseInfo, ref result);
}
result.calendar = parseInfo.calendar;
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs
index 6e7c58422fc646..ac22a353040c1b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs
@@ -207,7 +207,7 @@ private void VerifyWritable()
public static NumberFormatInfo InvariantInfo => s_invariantInfo ??=
// Lazy create the invariant info. This cannot be done in a .cctor because exceptions can
// be thrown out of a .cctor stack that will need this.
- new NumberFormatInfo { _isReadOnly = true };
+ CultureInfo.InvariantCulture.NumberFormat;
public static NumberFormatInfo GetInstance(IFormatProvider? formatProvider)
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
index 142b94d84a5459..b0100cb1d92e3e 100644
--- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
@@ -209,6 +209,12 @@ internal static void ThrowArgumentOutOfRange_TimeSpanTooLong()
throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
}
+ [DoesNotReturn]
+ internal static void ThrowArgumentOutOfRange_Range(string parameterName, T value, T minInclusive, T maxInclusive)
+ {
+ throw new ArgumentOutOfRangeException(parameterName, value, SR.Format(SR.ArgumentOutOfRange_Range, minInclusive, maxInclusive));
+ }
+
[DoesNotReturn]
internal static void ThrowOverflowException()
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs
index 7c402472df58f6..a30ab1e87d0bb5 100644
--- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs
@@ -945,7 +945,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] stri
}
}
- DateTimeFormat.IsValidCustomTimeFormat(format.AsSpan(), throwOnError: true);
+ DateTimeFormat.IsValidCustomTimeOnlyFormat(format.AsSpan(), throwOnError: true);
return DateTimeFormat.Format(ToDateTime(), format, provider);
}
@@ -991,7 +991,7 @@ private bool TryFormatCore(Span destination, out int written, [Str
}
}
- if (!DateTimeFormat.IsValidCustomTimeFormat(format, throwOnError: false))
+ if (!DateTimeFormat.IsValidCustomTimeOnlyFormat(format, throwOnError: false))
{
throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, format.ToString(), nameof(TimeOnly)));
}
diff --git a/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs
index 6fe8e0e1b38ba9..2e2ff83a504f9d 100644
--- a/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs
+++ b/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs
@@ -1253,93 +1253,193 @@ public static IEnumerable