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 ToString_MatchesExpected_MemberData() { // Randomly generated data on .NET Framework with: // using System; + // using System.Globalization; // class Program // { // static void Main() // { - // var rand = new Random(42); + // var rand = new Random(1); // var bytes = new byte[8]; - // int i = 0; - // while (i < 40) + // for (int i = 0; i < 100; ) // { - // DateTimeKind kind = rand.Next(2) == 0 ? DateTimeKind.Utc : DateTimeKind.Unspecified; - // string format; - // switch (rand.Next(4)) - // { - // case 0: format = "o"; break; - // case 1: format = "O"; break; - // case 2: format = "r"; break; - // default: format = "R"; break; - // } - // + // const string Formats = "dDfFgGmMoOrRstTuUyY"; + // string format = Formats[rand.Next(Formats.Length - 1)].ToString(); // try // { // rand.NextBytes(bytes); // long seed1 = BitConverter.ToInt64(bytes, 0); // short seed2 = BitConverter.ToInt16(bytes, 0); // var dto = new DateTimeOffset(seed1, TimeSpan.FromSeconds(seed2)); - // Console.WriteLine($"yield return new object[] {{ new DateTimeOffset({seed1}, TimeSpan.FromSeconds({seed2})), \"{format}\", null, \"{dto.ToString(format)}\" }};"); + // if (format[0] is 'o' or 'O' or 'r' or 'R' or 'u' or 's') + // { + // Console.WriteLine($"yield return new object[] {{ new DateTimeOffset({seed1}, TimeSpan.FromSeconds({seed2})), \"{format}\", null, \"{dto.ToString(format)}\" }};"); + // } + // Console.WriteLine($"yield return new object[] {{ new DateTimeOffset({seed1}, TimeSpan.FromSeconds({seed2})), \"{format}\", CultureInfo.InvariantCulture, \"{dto.ToString(format, CultureInfo.InvariantCulture)}\" }};"); // i++; // } // catch { } // } // } // } - yield return new object[] { new DateTimeOffset(2900400428644841236, TimeSpan.FromSeconds(14100)), "R", null, "Thu, 02 Jan 9192 18:39:24 GMT" }; - yield return new object[] { new DateTimeOffset(1274262903994885572, TimeSpan.FromSeconds(-23100)), "R", null, "Fri, 24 Dec 4038 14:11:39 GMT" }; - yield return new object[] { new DateTimeOffset(1228646631256163984, TimeSpan.FromSeconds(1680)), "R", null, "Tue, 05 Jun 3894 16:37:25 GMT" }; - yield return new object[] { new DateTimeOffset(2075652349868341848, TimeSpan.FromSeconds(31320)), "O", null, "6578-06-25T09:43:06.8341848+08:42" }; - yield return new object[] { new DateTimeOffset(2552829549675618692, TimeSpan.FromSeconds(8580)), "O", null, "8090-08-05T19:56:07.5618692+02:23" }; - yield return new object[] { new DateTimeOffset(2125715934081389968, TimeSpan.FromSeconds(5520)), "o", null, "6737-02-16T08:50:08.1389968+01:32" }; - yield return new object[] { new DateTimeOffset(98868902986124576, TimeSpan.FromSeconds(3360)), "O", null, "0314-04-22T14:24:58.6124576+00:56" }; - yield return new object[] { new DateTimeOffset(2013251697288306712, TimeSpan.FromSeconds(24600)), "O", null, "6380-09-28T10:15:28.8306712+06:50" }; - yield return new object[] { new DateTimeOffset(1037147523427228640, TimeSpan.FromSeconds(23520)), "o", null, "3287-08-04T05:25:42.7228640+06:32" }; - yield return new object[] { new DateTimeOffset(2177752403904984188, TimeSpan.FromSeconds(29820)), "r", null, "Mon, 09 Jan 6902 10:02:50 GMT" }; - yield return new object[] { new DateTimeOffset(832166470435218996, TimeSpan.FromSeconds(9780)), "r", null, "Fri, 12 Jan 2638 12:34:23 GMT" }; - yield return new object[] { new DateTimeOffset(651609783134768240, TimeSpan.FromSeconds(-13200)), "o", null, "2065-11-13T23:45:13.4768240-03:40" }; - yield return new object[] { new DateTimeOffset(1436056534611459616, TimeSpan.FromSeconds(-480)), "o", null, "4551-09-07T11:17:41.1459616-00:08" }; - yield return new object[] { new DateTimeOffset(1028124353647668124, TimeSpan.FromSeconds(-5220)), "o", null, "3258-12-30T17:49:24.7668124-01:27" }; - yield return new object[] { new DateTimeOffset(815446183072290676, TimeSpan.FromSeconds(-10380)), "o", null, "2585-01-17T10:51:47.2290676-02:53" }; - yield return new object[] { new DateTimeOffset(2091185090181553120, TimeSpan.FromSeconds(23520)), "r", null, "Fri, 14 Sep 6627 20:11:38 GMT" }; - yield return new object[] { new DateTimeOffset(2668855365894778960, TimeSpan.FromSeconds(-20400)), "R", null, "Mon, 08 Apr 8458 04:56:29 GMT" }; - yield return new object[] { new DateTimeOffset(1640160533452759488, TimeSpan.FromSeconds(8640)), "O", null, "5198-06-18T22:49:05.2759488+02:24" }; - yield return new object[] { new DateTimeOffset(2958665748788957224, TimeSpan.FromSeconds(20520)), "o", null, "9376-08-21T15:41:18.8957224+05:42" }; - yield return new object[] { new DateTimeOffset(2902544657562766092, TimeSpan.FromSeconds(-29940)), "R", null, "Tue, 20 Oct 9198 00:48:16 GMT" }; - yield return new object[] { new DateTimeOffset(2847595389168931696, TimeSpan.FromSeconds(21360)), "R", null, "Thu, 02 Sep 9024 17:59:16 GMT" }; - yield return new object[] { new DateTimeOffset(2010196475667096780, TimeSpan.FromSeconds(9420)), "r", null, "Sat, 23 Jan 6371 04:22:26 GMT" }; - yield return new object[] { new DateTimeOffset(613442997756722832, TimeSpan.FromSeconds(32400)), "O", null, "1944-12-04T11:16:15.6722832+09:00" }; - yield return new object[] { new DateTimeOffset(921560296274801912, TimeSpan.FromSeconds(13560)), "r", null, "Wed, 23 Apr 2921 13:21:07 GMT" }; - yield return new object[] { new DateTimeOffset(1990689515682669052, TimeSpan.FromSeconds(8700)), "o", null, "6309-03-31T18:59:28.2669052+02:25" }; - yield return new object[] { new DateTimeOffset(620638066929852080, TimeSpan.FromSeconds(24240)), "O", null, "1967-09-23T02:18:12.9852080+06:44" }; - yield return new object[] { new DateTimeOffset(327248350932775524, TimeSpan.FromSeconds(12900)), "O", null, "1038-01-04T15:58:13.2775524+03:35" }; - yield return new object[] { new DateTimeOffset(1370257845275318012, TimeSpan.FromSeconds(-10500)), "O", null, "4343-03-06T13:55:27.5318012-02:55" }; - yield return new object[] { new DateTimeOffset(1239382730779209800, TimeSpan.FromSeconds(12360)), "O", null, "3928-06-13T18:04:37.9209800+03:26" }; - yield return new object[] { new DateTimeOffset(2935667013803687040, TimeSpan.FromSeconds(-17280)), "o", null, "9303-10-05T17:56:20.3687040-04:48" }; - yield return new object[] { new DateTimeOffset(2101626275971711700, TimeSpan.FromSeconds(-15660)), "R", null, "Tue, 16 Oct 6660 00:00:57 GMT" }; - yield return new object[] { new DateTimeOffset(1417918072364232412, TimeSpan.FromSeconds(28380)), "o", null, "4494-03-15T21:07:16.4232412+07:53" }; - yield return new object[] { new DateTimeOffset(962535844977970944, TimeSpan.FromSeconds(3840)), "r", null, "Thu, 27 Feb 3051 01:44:17 GMT" }; - yield return new object[] { new DateTimeOffset(2576630638913059544, TimeSpan.FromSeconds(-1320)), "R", null, "Tue, 07 Jan 8166 09:40:11 GMT" }; - yield return new object[] { new DateTimeOffset(991481917233718112, TimeSpan.FromSeconds(25440)), "r", null, "Thu, 19 Nov 3142 05:18:03 GMT" }; - yield return new object[] { new DateTimeOffset(230115425073485984, TimeSpan.FromSeconds(-10080)), "o", null, "0730-03-18T07:08:27.3485984-02:48" }; - yield return new object[] { new DateTimeOffset(1289946780226617584, TimeSpan.FromSeconds(240)), "r", null, "Sat, 04 Sep 4088 22:56:22 GMT" }; - yield return new object[] { new DateTimeOffset(3119563990129685280, TimeSpan.FromSeconds(-19680)), "R", null, "Sun, 04 Jul 9886 16:44:52 GMT" }; - yield return new object[] { new DateTimeOffset(1167612095351481672, TimeSpan.FromSeconds(-6840)), "r", null, "Thu, 06 Jan 3701 23:12:55 GMT" }; - yield return new object[] { new DateTimeOffset(1617181518122280616, TimeSpan.FromSeconds(26280)), "O", null, "5125-08-24T20:50:12.2280616+07:18" }; + yield return new object[] { new DateTimeOffset(2866003186733990972, TimeSpan.FromSeconds(-15300)), "d", CultureInfo.InvariantCulture, "01/02/9083" }; + yield return new object[] { new DateTimeOffset(17724261114884476, TimeSpan.FromSeconds(-20100)), "o", null, "0057-03-02T04:35:11.4884476-05:35" }; + yield return new object[] { new DateTimeOffset(17724261114884476, TimeSpan.FromSeconds(-20100)), "o", CultureInfo.InvariantCulture, "0057-03-02T04:35:11.4884476-05:35" }; + yield return new object[] { new DateTimeOffset(1922396642931594516, TimeSpan.FromSeconds(21780)), "M", CultureInfo.InvariantCulture, "October 31" }; + yield return new object[] { new DateTimeOffset(483367606946453596, TimeSpan.FromSeconds(22620)), "G", CultureInfo.InvariantCulture, "09/25/1532 05:58:14" }; + yield return new object[] { new DateTimeOffset(69949591831359208, TimeSpan.FromSeconds(-20760)), "o", null, "0222-08-31T04:13:03.1359208-05:46" }; + yield return new object[] { new DateTimeOffset(69949591831359208, TimeSpan.FromSeconds(-20760)), "o", CultureInfo.InvariantCulture, "0222-08-31T04:13:03.1359208-05:46" }; + yield return new object[] { new DateTimeOffset(2558272663974397160, TimeSpan.FromSeconds(17640)), "o", null, "8107-11-05T17:33:17.4397160+04:54" }; + yield return new object[] { new DateTimeOffset(2558272663974397160, TimeSpan.FromSeconds(17640)), "o", CultureInfo.InvariantCulture, "8107-11-05T17:33:17.4397160+04:54" }; + yield return new object[] { new DateTimeOffset(1595228036159296076, TimeSpan.FromSeconds(-11700)), "g", CultureInfo.InvariantCulture, "01/29/5056 17:53" }; + yield return new object[] { new DateTimeOffset(1482818938952517812, TimeSpan.FromSeconds(-30540)), "r", null, "Mon, 13 Nov 4699 23:27:15 GMT" }; + yield return new object[] { new DateTimeOffset(1482818938952517812, TimeSpan.FromSeconds(-30540)), "r", CultureInfo.InvariantCulture, "Mon, 13 Nov 4699 23:27:15 GMT" }; + yield return new object[] { new DateTimeOffset(2374737620308081364, TimeSpan.FromSeconds(15060)), "y", CultureInfo.InvariantCulture, "7526 March" }; + yield return new object[] { new DateTimeOffset(1480473615374849012, TimeSpan.FromSeconds(-27660)), "g", CultureInfo.InvariantCulture, "06/08/4692 03:05" }; + yield return new object[] { new DateTimeOffset(559024831959587456, TimeSpan.FromSeconds(-9600)), "M", CultureInfo.InvariantCulture, "June 24" }; + yield return new object[] { new DateTimeOffset(1184653058942329036, TimeSpan.FromSeconds(24780)), "d", CultureInfo.InvariantCulture, "01/07/3755" }; + yield return new object[] { new DateTimeOffset(516669061683837048, TimeSpan.FromSeconds(30840)), "G", CultureInfo.InvariantCulture, "04/05/1638 14:22:48" }; + yield return new object[] { new DateTimeOffset(2258308651822418992, TimeSpan.FromSeconds(3120)), "T", CultureInfo.InvariantCulture, "03:53:02" }; + yield return new object[] { new DateTimeOffset(1383517800988996320, TimeSpan.FromSeconds(-3360)), "t", CultureInfo.InvariantCulture, "18:01" }; + yield return new object[] { new DateTimeOffset(728656139270861240, TimeSpan.FromSeconds(-20040)), "u", null, "2310-01-09 05:52:47Z" }; + yield return new object[] { new DateTimeOffset(728656139270861240, TimeSpan.FromSeconds(-20040)), "u", CultureInfo.InvariantCulture, "2310-01-09 05:52:47Z" }; + yield return new object[] { new DateTimeOffset(393200115812717732, TimeSpan.FromSeconds(-11100)), "M", CultureInfo.InvariantCulture, "January 01" }; + yield return new object[] { new DateTimeOffset(1359420562790649548, TimeSpan.FromSeconds(-28980)), "u", null, "4308-11-01 18:20:59Z" }; + yield return new object[] { new DateTimeOffset(1359420562790649548, TimeSpan.FromSeconds(-28980)), "u", CultureInfo.InvariantCulture, "4308-11-01 18:20:59Z" }; + yield return new object[] { new DateTimeOffset(1235254827956094012, TimeSpan.FromSeconds(-15300)), "u", null, "3915-05-16 06:21:35Z" }; + yield return new object[] { new DateTimeOffset(1235254827956094012, TimeSpan.FromSeconds(-15300)), "u", CultureInfo.InvariantCulture, "3915-05-16 06:21:35Z" }; + yield return new object[] { new DateTimeOffset(114477656718234848, TimeSpan.FromSeconds(-11040)), "f", CultureInfo.InvariantCulture, "Tuesday, 08 October 0363 06:54" }; + yield return new object[] { new DateTimeOffset(192057803897653116, TimeSpan.FromSeconds(18300)), "O", null, "0609-08-11T02:59:49.7653116+05:05" }; + yield return new object[] { new DateTimeOffset(192057803897653116, TimeSpan.FromSeconds(18300)), "O", CultureInfo.InvariantCulture, "0609-08-11T02:59:49.7653116+05:05" }; + yield return new object[] { new DateTimeOffset(546374288963361880, TimeSpan.FromSeconds(23640)), "G", CultureInfo.InvariantCulture, "05/23/1732 15:34:56" }; + yield return new object[] { new DateTimeOffset(270121790607980192, TimeSpan.FromSeconds(-2400)), "R", null, "Sun, 24 Dec 0856 23:44:20 GMT" }; + yield return new object[] { new DateTimeOffset(270121790607980192, TimeSpan.FromSeconds(-2400)), "R", CultureInfo.InvariantCulture, "Sun, 24 Dec 0856 23:44:20 GMT" }; + yield return new object[] { new DateTimeOffset(2305676294986132560, TimeSpan.FromSeconds(-5040)), "D", CultureInfo.InvariantCulture, "Thursday, 26 May 7307" }; + yield return new object[] { new DateTimeOffset(2532230820032055596, TimeSpan.FromSeconds(-30420)), "T", CultureInfo.InvariantCulture, "17:00:03" }; + yield return new object[] { new DateTimeOffset(1076978232703995584, TimeSpan.FromSeconds(20160)), "T", CultureInfo.InvariantCulture, "14:01:10" }; + yield return new object[] { new DateTimeOffset(1339659304181130620, TimeSpan.FromSeconds(25980)), "M", CultureInfo.InvariantCulture, "March 19" }; + yield return new object[] { new DateTimeOffset(2750426316766699852, TimeSpan.FromSeconds(-7860)), "t", CultureInfo.InvariantCulture, "19:01" }; + yield return new object[] { new DateTimeOffset(972505514843635232, TimeSpan.FromSeconds(-480)), "T", CultureInfo.InvariantCulture, "02:04:44" }; + yield return new object[] { new DateTimeOffset(2427049669893407744, TimeSpan.FromSeconds(-15360)), "R", null, "Sun, 06 Jan 7692 10:39:09 GMT" }; + yield return new object[] { new DateTimeOffset(2427049669893407744, TimeSpan.FromSeconds(-15360)), "R", CultureInfo.InvariantCulture, "Sun, 06 Jan 7692 10:39:09 GMT" }; + yield return new object[] { new DateTimeOffset(2167597301268758764, TimeSpan.FromSeconds(16620)), "u", null, "6869-11-03 23:31:46Z" }; + yield return new object[] { new DateTimeOffset(2167597301268758764, TimeSpan.FromSeconds(16620)), "u", CultureInfo.InvariantCulture, "6869-11-03 23:31:46Z" }; + yield return new object[] { new DateTimeOffset(2833512902056985060, TimeSpan.FromSeconds(-15900)), "u", null, "8980-01-18 00:08:25Z" }; + yield return new object[] { new DateTimeOffset(2833512902056985060, TimeSpan.FromSeconds(-15900)), "u", CultureInfo.InvariantCulture, "8980-01-18 00:08:25Z" }; + yield return new object[] { new DateTimeOffset(2053144072711223968, TimeSpan.FromSeconds(-17760)), "F", CultureInfo.InvariantCulture, "Sunday, 27 February 6507 03:47:51" }; + yield return new object[] { new DateTimeOffset(1245775652276323568, TimeSpan.FromSeconds(-15120)), "d", CultureInfo.InvariantCulture, "09/15/3948" }; + yield return new object[] { new DateTimeOffset(3067145896534734916, TimeSpan.FromSeconds(-1980)), "s", null, "9720-05-26T09:07:33" }; + yield return new object[] { new DateTimeOffset(3067145896534734916, TimeSpan.FromSeconds(-1980)), "s", CultureInfo.InvariantCulture, "9720-05-26T09:07:33" }; + yield return new object[] { new DateTimeOffset(1149266991238407556, TimeSpan.FromSeconds(-6780)), "m", CultureInfo.InvariantCulture, "November 19" }; + yield return new object[] { new DateTimeOffset(1749528579164719160, TimeSpan.FromSeconds(-14280)), "G", CultureInfo.InvariantCulture, "01/14/5545 08:05:16" }; + yield return new object[] { new DateTimeOffset(1375434758076332332, TimeSpan.FromSeconds(31020)), "R", null, "Sat, 01 Aug 4359 00:26:27 GMT" }; + yield return new object[] { new DateTimeOffset(1375434758076332332, TimeSpan.FromSeconds(31020)), "R", CultureInfo.InvariantCulture, "Sat, 01 Aug 4359 00:26:27 GMT" }; + yield return new object[] { new DateTimeOffset(28528038954117508, TimeSpan.FromSeconds(-22140)), "D", CultureInfo.InvariantCulture, "Sunday, 27 May 0091" }; + yield return new object[] { new DateTimeOffset(685194926724353912, TimeSpan.FromSeconds(3960)), "t", CultureInfo.InvariantCulture, "16:24" }; + yield return new object[] { new DateTimeOffset(2887804226685042448, TimeSpan.FromSeconds(-240)), "D", CultureInfo.InvariantCulture, "Sunday, 03 February 9152" }; + yield return new object[] { new DateTimeOffset(863118435880997388, TimeSpan.FromSeconds(4620)), "D", CultureInfo.InvariantCulture, "Wednesday, 12 February 2736" }; + yield return new object[] { new DateTimeOffset(1131353524374008364, TimeSpan.FromSeconds(-3540)), "y", CultureInfo.InvariantCulture, "3586 February" }; + yield return new object[] { new DateTimeOffset(315210523520920416, TimeSpan.FromSeconds(-5280)), "r", null, "Tue, 12 Nov 0999 01:20:32 GMT" }; + yield return new object[] { new DateTimeOffset(315210523520920416, TimeSpan.FromSeconds(-5280)), "r", CultureInfo.InvariantCulture, "Tue, 12 Nov 0999 01:20:32 GMT" }; + yield return new object[] { new DateTimeOffset(2230965644288159332, TimeSpan.FromSeconds(28260)), "D", CultureInfo.InvariantCulture, "Friday, 26 August 7070" }; + yield return new object[] { new DateTimeOffset(2347007386434452848, TimeSpan.FromSeconds(-17040)), "M", CultureInfo.InvariantCulture, "May 16" }; + yield return new object[] { new DateTimeOffset(3057726375364446024, TimeSpan.FromSeconds(-14520)), "T", CultureInfo.InvariantCulture, "03:45:36" }; + yield return new object[] { new DateTimeOffset(1346223178375097544, TimeSpan.FromSeconds(-4920)), "T", CultureInfo.InvariantCulture, "16:17:17" }; + yield return new object[] { new DateTimeOffset(2139495673968405100, TimeSpan.FromSeconds(10860)), "G", CultureInfo.InvariantCulture, "10/17/6780 03:23:16" }; + yield return new object[] { new DateTimeOffset(1393959301494092688, TimeSpan.FromSeconds(13200)), "r", null, "Fri, 13 Apr 4418 16:02:29 GMT" }; + yield return new object[] { new DateTimeOffset(1393959301494092688, TimeSpan.FromSeconds(13200)), "r", CultureInfo.InvariantCulture, "Fri, 13 Apr 4418 16:02:29 GMT" }; + yield return new object[] { new DateTimeOffset(2354387478841092052, TimeSpan.FromSeconds(26580)), "u", null, "7461-10-04 04:48:24Z" }; + yield return new object[] { new DateTimeOffset(2354387478841092052, TimeSpan.FromSeconds(26580)), "u", CultureInfo.InvariantCulture, "7461-10-04 04:48:24Z" }; + yield return new object[] { new DateTimeOffset(2695673889181138404, TimeSpan.FromSeconds(-540)), "T", CultureInfo.InvariantCulture, "22:15:18" }; + yield return new object[] { new DateTimeOffset(3129400016207346692, TimeSpan.FromSeconds(-1020)), "f", CultureInfo.InvariantCulture, "Tuesday, 04 September 9917 18:13" }; + yield return new object[] { new DateTimeOffset(268510768954133436, TimeSpan.FromSeconds(-13380)), "m", CultureInfo.InvariantCulture, "November 17" }; + yield return new object[] { new DateTimeOffset(1675324527268079456, TimeSpan.FromSeconds(10080)), "y", CultureInfo.InvariantCulture, "5309 November" }; + yield return new object[] { new DateTimeOffset(1226179294413194056, TimeSpan.FromSeconds(840)), "s", null, "3886-08-10T23:57:21" }; + yield return new object[] { new DateTimeOffset(1226179294413194056, TimeSpan.FromSeconds(840)), "s", CultureInfo.InvariantCulture, "3886-08-10T23:57:21" }; + yield return new object[] { new DateTimeOffset(1541746737990743828, TimeSpan.FromSeconds(14100)), "g", CultureInfo.InvariantCulture, "08/08/4886 02:16" }; + yield return new object[] { new DateTimeOffset(3014961136493232496, TimeSpan.FromSeconds(-32400)), "O", null, "9555-01-13T08:27:29.3232496-09:00" }; + yield return new object[] { new DateTimeOffset(3014961136493232496, TimeSpan.FromSeconds(-32400)), "O", CultureInfo.InvariantCulture, "9555-01-13T08:27:29.3232496-09:00" }; + yield return new object[] { new DateTimeOffset(93553939704192692, TimeSpan.FromSeconds(7860)), "m", CultureInfo.InvariantCulture, "June 18" }; + yield return new object[] { new DateTimeOffset(1474648860395527136, TimeSpan.FromSeconds(23520)), "s", null, "4673-12-23T12:20:39" }; + yield return new object[] { new DateTimeOffset(1474648860395527136, TimeSpan.FromSeconds(23520)), "s", CultureInfo.InvariantCulture, "4673-12-23T12:20:39" }; + yield return new object[] { new DateTimeOffset(1295864623067010816, TimeSpan.FromSeconds(-26880)), "M", CultureInfo.InvariantCulture, "June 08" }; + yield return new object[] { new DateTimeOffset(2739123151422226552, TimeSpan.FromSeconds(120)), "o", null, "8680-12-08T10:12:22.2226552+00:02" }; + yield return new object[] { new DateTimeOffset(2739123151422226552, TimeSpan.FromSeconds(120)), "o", CultureInfo.InvariantCulture, "8680-12-08T10:12:22.2226552+00:02" }; + yield return new object[] { new DateTimeOffset(1515671391938001628, TimeSpan.FromSeconds(-17700)), "o", null, "4803-12-22T07:06:33.8001628-04:55" }; + yield return new object[] { new DateTimeOffset(1515671391938001628, TimeSpan.FromSeconds(-17700)), "o", CultureInfo.InvariantCulture, "4803-12-22T07:06:33.8001628-04:55" }; + yield return new object[] { new DateTimeOffset(1675967728676687860, TimeSpan.FromSeconds(-12300)), "D", CultureInfo.InvariantCulture, "Monday, 07 December 5311" }; + yield return new object[] { new DateTimeOffset(2231850043577254012, TimeSpan.FromSeconds(-16260)), "O", null, "7073-06-14T18:32:37.7254012-04:31" }; + yield return new object[] { new DateTimeOffset(2231850043577254012, TimeSpan.FromSeconds(-16260)), "O", CultureInfo.InvariantCulture, "7073-06-14T18:32:37.7254012-04:31" }; + yield return new object[] { new DateTimeOffset(763201213144623488, TimeSpan.FromSeconds(-5760)), "y", CultureInfo.InvariantCulture, "2419 June" }; + yield return new object[] { new DateTimeOffset(1902743697611562976, TimeSpan.FromSeconds(8160)), "R", null, "Mon, 22 Jul 6030 13:20:01 GMT" }; + yield return new object[] { new DateTimeOffset(1902743697611562976, TimeSpan.FromSeconds(8160)), "R", CultureInfo.InvariantCulture, "Mon, 22 Jul 6030 13:20:01 GMT" }; + yield return new object[] { new DateTimeOffset(2170088908634528380, TimeSpan.FromSeconds(6780)), "M", CultureInfo.InvariantCulture, "September 26" }; + yield return new object[] { new DateTimeOffset(2945387119699905100, TimeSpan.FromSeconds(19020)), "u", null, "9334-07-24 15:35:49Z" }; + yield return new object[] { new DateTimeOffset(2945387119699905100, TimeSpan.FromSeconds(19020)), "u", CultureInfo.InvariantCulture, "9334-07-24 15:35:49Z" }; + yield return new object[] { new DateTimeOffset(2210427227472213120, TimeSpan.FromSeconds(13440)), "d", CultureInfo.InvariantCulture, "07/26/7005" }; + yield return new object[] { new DateTimeOffset(1172280359902648716, TimeSpan.FromSeconds(6540)), "r", null, "Wed, 23 Oct 3715 21:30:50 GMT" }; + yield return new object[] { new DateTimeOffset(1172280359902648716, TimeSpan.FromSeconds(6540)), "r", CultureInfo.InvariantCulture, "Wed, 23 Oct 3715 21:30:50 GMT" }; + yield return new object[] { new DateTimeOffset(193781915832174040, TimeSpan.FromSeconds(17880)), "G", CultureInfo.InvariantCulture, "01/27/0615 14:59:43" }; + yield return new object[] { new DateTimeOffset(1434340271653145524, TimeSpan.FromSeconds(19380)), "s", null, "4546-03-31T01:19:25" }; + yield return new object[] { new DateTimeOffset(1434340271653145524, TimeSpan.FromSeconds(19380)), "s", CultureInfo.InvariantCulture, "4546-03-31T01:19:25" }; + yield return new object[] { new DateTimeOffset(2232002475903745148, TimeSpan.FromSeconds(-900)), "R", null, "Mon, 08 Dec 7073 05:01:30 GMT" }; + yield return new object[] { new DateTimeOffset(2232002475903745148, TimeSpan.FromSeconds(-900)), "R", CultureInfo.InvariantCulture, "Mon, 08 Dec 7073 05:01:30 GMT" }; + yield return new object[] { new DateTimeOffset(75862097515405336, TimeSpan.FromSeconds(24600)), "r", null, "Wed, 26 May 0241 01:39:11 GMT" }; + yield return new object[] { new DateTimeOffset(75862097515405336, TimeSpan.FromSeconds(24600)), "r", CultureInfo.InvariantCulture, "Wed, 26 May 0241 01:39:11 GMT" }; + yield return new object[] { new DateTimeOffset(1311882990195785184, TimeSpan.FromSeconds(15840)), "s", null, "4158-03-12T02:10:19" }; + yield return new object[] { new DateTimeOffset(1311882990195785184, TimeSpan.FromSeconds(15840)), "s", CultureInfo.InvariantCulture, "4158-03-12T02:10:19" }; + yield return new object[] { new DateTimeOffset(2492654623258780576, TimeSpan.FromSeconds(9120)), "t", CultureInfo.InvariantCulture, "22:12" }; + yield return new object[] { new DateTimeOffset(3044591719812160368, TimeSpan.FromSeconds(-9360)), "O", null, "9648-12-05T00:13:01.2160368-02:36" }; + yield return new object[] { new DateTimeOffset(3044591719812160368, TimeSpan.FromSeconds(-9360)), "O", CultureInfo.InvariantCulture, "9648-12-05T00:13:01.2160368-02:36" }; + yield return new object[] { new DateTimeOffset(2782989619967598208, TimeSpan.FromSeconds(-24960)), "o", null, "8819-12-11T19:13:16.7598208-06:56" }; + yield return new object[] { new DateTimeOffset(2782989619967598208, TimeSpan.FromSeconds(-24960)), "o", CultureInfo.InvariantCulture, "8819-12-11T19:13:16.7598208-06:56" }; + yield return new object[] { new DateTimeOffset(2691678346098786600, TimeSpan.FromSeconds(16680)), "G", CultureInfo.InvariantCulture, "08/04/8530 10:56:49" }; + yield return new object[] { new DateTimeOffset(2863023557156129544, TimeSpan.FromSeconds(-13560)), "M", CultureInfo.InvariantCulture, "July 24" }; + yield return new object[] { new DateTimeOffset(2141892998746916860, TimeSpan.FromSeconds(-14340)), "o", null, "6788-05-22T19:44:34.6916860-03:59" }; + yield return new object[] { new DateTimeOffset(2141892998746916860, TimeSpan.FromSeconds(-14340)), "o", CultureInfo.InvariantCulture, "6788-05-22T19:44:34.6916860-03:59" }; + yield return new object[] { new DateTimeOffset(1647902714725003280, TimeSpan.FromSeconds(-4080)), "o", null, "5222-12-30T19:24:32.5003280-01:08" }; + yield return new object[] { new DateTimeOffset(1647902714725003280, TimeSpan.FromSeconds(-4080)), "o", CultureInfo.InvariantCulture, "5222-12-30T19:24:32.5003280-01:08" }; + yield return new object[] { new DateTimeOffset(3015783691236408744, TimeSpan.FromSeconds(-600)), "G", CultureInfo.InvariantCulture, "08/22/9557 09:12:03" }; + yield return new object[] { new DateTimeOffset(1723006204695438056, TimeSpan.FromSeconds(25320)), "m", CultureInfo.InvariantCulture, "December 28" }; + yield return new object[] { new DateTimeOffset(205672147887324044, TimeSpan.FromSeconds(-1140)), "m", CultureInfo.InvariantCulture, "October 01" }; + yield return new object[] { new DateTimeOffset(176841286150132116, TimeSpan.FromSeconds(4500)), "d", CultureInfo.InvariantCulture, "05/22/0561" }; + yield return new object[] { new DateTimeOffset(358056122991012336, TimeSpan.FromSeconds(27120)), "f", CultureInfo.InvariantCulture, "Wednesday, 21 August 1135 19:24" }; + yield return new object[] { new DateTimeOffset(504008756218880332, TimeSpan.FromSeconds(-7860)), "m", CultureInfo.InvariantCulture, "February 21" }; + yield return new object[] { new DateTimeOffset(2338734206946518640, TimeSpan.FromSeconds(9840)), "M", CultureInfo.InvariantCulture, "February 27" }; + yield return new object[] { new DateTimeOffset(2514061437370836652, TimeSpan.FromSeconds(9900)), "o", null, "7967-09-30T07:55:37.0836652+02:45" }; + yield return new object[] { new DateTimeOffset(2514061437370836652, TimeSpan.FromSeconds(9900)), "o", CultureInfo.InvariantCulture, "7967-09-30T07:55:37.0836652+02:45" }; + yield return new object[] { new DateTimeOffset(2910141711367555000, TimeSpan.FromSeconds(18360)), "r", null, "Tue, 15 Nov 9222 08:39:36 GMT" }; + yield return new object[] { new DateTimeOffset(2910141711367555000, TimeSpan.FromSeconds(18360)), "r", CultureInfo.InvariantCulture, "Tue, 15 Nov 9222 08:39:36 GMT" }; + yield return new object[] { new DateTimeOffset(2268815489190300608, TimeSpan.FromSeconds(-29760)), "M", CultureInfo.InvariantCulture, "August 04" }; + yield return new object[] { new DateTimeOffset(2270906825242135956, TimeSpan.FromSeconds(19860)), "T", CultureInfo.InvariantCulture, "09:08:44" }; + yield return new object[] { new DateTimeOffset(1257384048216439692, TimeSpan.FromSeconds(-1140)), "D", CultureInfo.InvariantCulture, "Saturday, 29 June 3985" }; + yield return new object[] { new DateTimeOffset(2905726883951199532, TimeSpan.FromSeconds(-15060)), "F", CultureInfo.InvariantCulture, "Tuesday, 18 November 9208 19:39:55" }; + yield return new object[] { new DateTimeOffset(1963041343129029388, TimeSpan.FromSeconds(-29940)), "r", null, "Sun, 19 Aug 6221 22:30:52 GMT" }; + yield return new object[] { new DateTimeOffset(1963041343129029388, TimeSpan.FromSeconds(-29940)), "r", CultureInfo.InvariantCulture, "Sun, 19 Aug 6221 22:30:52 GMT" }; + yield return new object[] { new DateTimeOffset(1344688163288837500, TimeSpan.FromSeconds(-4740)), "G", CultureInfo.InvariantCulture, "02/24/4262 00:58:48" }; + yield return new object[] { new DateTimeOffset(2904241358872885260, TimeSpan.FromSeconds(-18420)), "y", CultureInfo.InvariantCulture, "9204 March" }; + yield return new object[] { new DateTimeOffset(2226716916868971284, TimeSpan.FromSeconds(-1260)), "G", CultureInfo.InvariantCulture, "03/09/7057 15:41:26" }; // Year patterns var enUS = new CultureInfo("en-US"); var thTH = new CultureInfo("th-TH"); yield return new object[] { new DateTimeOffset(new DateTime(1234, 5, 6)), "yy", enUS, "34" }; if (PlatformDetection.IsNotInvariantGlobalization) + { yield return new object[] { DateTimeOffset.MaxValue, "yy", thTH, "42" }; + } for (int i = 3; i < 20; i++) { yield return new object[] { new DateTimeOffset(new DateTime(1234, 5, 6)), new string('y', i), enUS, 1234.ToString("D" + i) }; if (PlatformDetection.IsNotInvariantGlobalization) + { yield return new object[] { DateTimeOffset.MaxValue, new string('y', i), thTH, 10542.ToString("D" + i) }; + } } + + // Non-ASCII in format string + yield return new object[] { new DateTimeOffset(2023, 04, 17, 10, 46, 12, TimeSpan.Zero), "HH\u202dmm", null, "10\u202d46" }; } [Theory] @@ -1473,21 +1573,21 @@ public static void TryFormat_ToString_EqualResults() string expectedString = expected.ToString(); // Just the right amount of space, succeeds - Span actual = new byte[expectedString.Length]; + Span actual = new byte[Encoding.UTF8.GetByteCount(expectedString)]; Assert.True(expected.TryFormat(actual, out int bytesWritten, default, null)); - Assert.Equal(expectedString.Length, bytesWritten); + Assert.Equal(actual.Length, bytesWritten); Assert.Equal(expectedString, Encoding.UTF8.GetString(actual)); // Too little space, fails - actual = new byte[expectedString.Length - 1]; + actual = new byte[Encoding.UTF8.GetByteCount(expectedString) - 1]; Assert.False(expected.TryFormat(actual, out bytesWritten, default, null)); Assert.Equal(0, bytesWritten); // More than enough space, succeeds - actual = new byte[expectedString.Length + 1]; + actual = new byte[Encoding.UTF8.GetByteCount(expectedString) + 1]; Assert.True(expected.TryFormat(actual, out bytesWritten, default, null)); - Assert.Equal(expectedString.Length, bytesWritten); - Assert.Equal(expectedString, Encoding.UTF8.GetString(actual.Slice(0, expectedString.Length))); + Assert.Equal(actual.Length - 1, bytesWritten); + Assert.Equal(expectedString, Encoding.UTF8.GetString(actual.Slice(0, bytesWritten))); Assert.Equal(0, actual[actual.Length - 1]); } } @@ -1510,7 +1610,7 @@ public static void TryFormat_MatchesExpected(DateTimeOffset dateTimeOffset, stri // UTF8 { - var destination = new byte[expected.Length]; + var destination = new byte[Encoding.UTF8.GetByteCount(expected)]; Assert.False(dateTimeOffset.TryFormat(destination.AsSpan(0, destination.Length - 1), out _, format, provider)); diff --git a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs index 716f4139a3c430..0b5504a205f031 100644 --- a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs @@ -2267,77 +2267,172 @@ public static IEnumerable ToString_MatchesExpected_MemberData() { // Randomly generated data on .NET Framework with: // using System; + // using System.Globalization; // class Program // { // static void Main() // { - // var rand = new Random(42); + // var rand = new Random(1); // var bytes = new byte[8]; - // int i = 0; - // while (i < 40) + // for (int i = 0; i < 100; ) // { - // DateTimeKind kind = rand.Next(2) == 0 ? DateTimeKind.Utc : DateTimeKind.Unspecified; - // string format; - // switch (rand.Next(4)) - // { - // case 0: format = "o"; break; - // case 1: format = "O"; break; - // case 2: format = "r"; break; - // default: format = "R"; break; - // } - // + // const string Formats = "dDfFgGmMoOrRstTuUyY"; + // string format = Formats[rand.Next(Formats.Length - 1)].ToString(); + // DateTimeKind kind = format == "U" || rand.Next(2) == 0 ? DateTimeKind.Utc : DateTimeKind.Unspecified; // try // { // rand.NextBytes(bytes); // long seed = BitConverter.ToInt64(bytes, 0); // var dt = new DateTime(seed, kind); - // Console.WriteLine($"yield return new object[] {{ new DateTime({seed}, DateTimeKind.{kind}), \"{format}\", null, \"{dt.ToString(format)}\" }};"); + // if (format[0] is 'o' or 'O' or 'r' or 'R' or 'u' or 's') + // { + // Console.WriteLine($"yield return new object[] {{ new DateTime({seed}, DateTimeKind.{kind}), \"{format}\", null, \"{dt.ToString(format)}\" }};"); + // } + // Console.WriteLine($"yield return new object[] {{ new DateTime({seed}, DateTimeKind.{kind}), \"{format}\", CultureInfo.InvariantCulture, \"{dt.ToString(format, CultureInfo.InvariantCulture)}\" }};"); // i++; // } // catch { } // } // } - //} - yield return new object[] { new DateTime(2688006240964947440, DateTimeKind.Utc), "O", null, "8518-12-15T08:01:36.4947440Z" }; - yield return new object[] { new DateTime(2461197105169450509, DateTimeKind.Utc), "r", null, "Sun, 23 Mar 7800 18:15:16 GMT" }; - yield return new object[] { new DateTime(71363981510699949, DateTimeKind.Unspecified), "R", null, "Fri, 23 Feb 0227 04:49:11 GMT" }; - yield return new object[] { new DateTime(1678426538898407093, DateTimeKind.Unspecified), "R", null, "Fri, 22 Sep 5319 07:24:49 GMT" }; - yield return new object[] { new DateTime(2689041307785948711, DateTimeKind.Utc), "o", null, "8522-03-27T07:52:58.5948711Z" }; - yield return new object[] { new DateTime(996610247053299209, DateTimeKind.Unspecified), "r", null, "Thu, 19 Feb 3159 01:58:25 GMT" }; - yield return new object[] { new DateTime(3105391438361510074, DateTimeKind.Unspecified), "R", null, "Fri, 06 Aug 9841 01:17:16 GMT" }; - yield return new object[] { new DateTime(946433487657072106, DateTimeKind.Utc), "R", null, "Mon, 17 Feb 3000 03:06:05 GMT" }; - yield return new object[] { new DateTime(2521748413631767931, DateTimeKind.Unspecified), "R", null, "Sat, 08 Feb 7992 07:02:43 GMT" }; - yield return new object[] { new DateTime(49349519375012969, DateTimeKind.Utc), "R", null, "Fri, 20 May 0157 11:58:57 GMT" }; - yield return new object[] { new DateTime(796677276139881359, DateTimeKind.Utc), "o", null, "2525-07-28T04:20:13.9881359Z" }; - yield return new object[] { new DateTime(3022911536338429542, DateTimeKind.Unspecified), "R", null, "Mon, 24 Mar 9580 04:53:53 GMT" }; - yield return new object[] { new DateTime(1144652135553351618, DateTimeKind.Utc), "R", null, "Tue, 04 Apr 3628 20:39:15 GMT" }; - yield return new object[] { new DateTime(2570858096011770291, DateTimeKind.Unspecified), "o", null, "8147-09-23T04:53:21.1770291" }; - yield return new object[] { new DateTime(15695724649124585, DateTimeKind.Unspecified), "R", null, "Tue, 27 Sep 0050 08:21:04 GMT" }; - yield return new object[] { new DateTime(1503933934291527034, DateTimeKind.Unspecified), "O", null, "4766-10-12T06:37:09.1527034" }; - yield return new object[] { new DateTime(2688603665097410101, DateTimeKind.Unspecified), "r", null, "Tue, 05 Nov 8520 19:08:29 GMT" }; - yield return new object[] { new DateTime(1310336900529542610, DateTimeKind.Unspecified), "r", null, "Tue, 17 Apr 4153 15:14:12 GMT" }; - yield return new object[] { new DateTime(2313720085584182693, DateTimeKind.Unspecified), "O", null, "7332-11-20T18:22:38.4182693" }; - yield return new object[] { new DateTime(2291958603891779335, DateTimeKind.Unspecified), "o", null, "7263-12-05T20:46:29.1779335" }; - yield return new object[] { new DateTime(262036413643976979, DateTimeKind.Unspecified), "o", null, "0831-05-12T21:16:04.3976979" }; - yield return new object[] { new DateTime(684781207384421044, DateTimeKind.Utc), "O", null, "2170-12-26T20:12:18.4421044Z" }; - yield return new object[] { new DateTime(1444462249169683325, DateTimeKind.Utc), "r", null, "Mon, 27 Apr 4578 07:21:56 GMT" }; - yield return new object[] { new DateTime(1155518137384061537, DateTimeKind.Unspecified), "r", null, "Sun, 10 Sep 3662 06:02:18 GMT" }; - yield return new object[] { new DateTime(2333390479532380569, DateTimeKind.Unspecified), "O", null, "7395-03-22T10:12:33.2380569" }; - yield return new object[] { new DateTime(2217528014591554502, DateTimeKind.Unspecified), "R", null, "Sat, 26 Jan 7028 08:24:19 GMT" }; - yield return new object[] { new DateTime(2764551324904480205, DateTimeKind.Utc), "O", null, "8761-07-08T04:21:30.4480205Z" }; - yield return new object[] { new DateTime(2880903932678729712, DateTimeKind.Utc), "O", null, "9130-03-23T13:14:27.8729712Z" }; - yield return new object[] { new DateTime(507699902578704433, DateTimeKind.Utc), "O", null, "1609-11-02T15:04:17.8704433Z" }; - yield return new object[] { new DateTime(2429953022324426129, DateTimeKind.Utc), "O", null, "7701-03-20T15:03:52.4426129Z" }; - yield return new object[] { new DateTime(603147512164908366, DateTimeKind.Unspecified), "O", null, "1912-04-20T09:33:36.4908366" }; - yield return new object[] { new DateTime(2900400428644841236, DateTimeKind.Utc), "R", null, "Thu, 02 Jan 9192 22:34:24 GMT" }; - yield return new object[] { new DateTime(1710845568474490805, DateTimeKind.Utc), "O", null, "5422-06-16T08:00:47.4490805Z" }; - yield return new object[] { new DateTime(2988999715803714268, DateTimeKind.Utc), "r", null, "Sun, 06 Oct 9472 09:53:00 GMT" }; - yield return new object[] { new DateTime(1068133489112689365, DateTimeKind.Utc), "r", null, "Wed, 12 Oct 3385 14:41:51 GMT" }; - yield return new object[] { new DateTime(798784044525059284, DateTimeKind.Unspecified), "R", null, "Mon, 31 Mar 2532 13:40:52 GMT" }; - yield return new object[] { new DateTime(2561736813034040593, DateTimeKind.Utc), "O", null, "8118-10-28T03:55:03.4040593Z" }; - yield return new object[] { new DateTime(1677975383149674547, DateTimeKind.Utc), "o", null, "5318-04-18T03:18:34.9674547Z" }; - yield return new object[] { new DateTime(1101778442151366156, DateTimeKind.Utc), "O", null, "3492-05-25T12:43:35.1366156Z" }; - yield return new object[] { new DateTime(221550163152616218, DateTimeKind.Utc), "r", null, "Sun, 25 Jan 0703 19:11:55 GMT" }; + // } + yield return new object[] { new DateTime(2512683898575779670, DateTimeKind.Utc), "M", CultureInfo.InvariantCulture, "May 19" }; + yield return new object[] { new DateTime(1418427749603514933, DateTimeKind.Unspecified), "M", CultureInfo.InvariantCulture, "October 26" }; + yield return new object[] { new DateTime(2499951271131650615, DateTimeKind.Unspecified), "F", CultureInfo.InvariantCulture, "Saturday, 13 January 7923 02:51:53" }; + yield return new object[] { new DateTime(710634958044951822, DateTimeKind.Utc), "s", null, "2252-11-30T03:56:44" }; + yield return new object[] { new DateTime(710634958044951822, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "2252-11-30T03:56:44" }; + yield return new object[] { new DateTime(436815073371195206, DateTimeKind.Utc), "u", null, "1385-03-19 00:02:17Z" }; + yield return new object[] { new DateTime(436815073371195206, DateTimeKind.Utc), "u", CultureInfo.InvariantCulture, "1385-03-19 00:02:17Z" }; + yield return new object[] { new DateTime(316446896574206081, DateTimeKind.Unspecified), "R", null, "Thu, 13 Oct 1003 23:34:17 GMT" }; + yield return new object[] { new DateTime(316446896574206081, DateTimeKind.Unspecified), "R", CultureInfo.InvariantCulture, "Thu, 13 Oct 1003 23:34:17 GMT" }; + yield return new object[] { new DateTime(1352087970149786791, DateTimeKind.Unspecified), "s", null, "4285-08-06T15:10:14" }; + yield return new object[] { new DateTime(1352087970149786791, DateTimeKind.Unspecified), "s", CultureInfo.InvariantCulture, "4285-08-06T15:10:14" }; + yield return new object[] { new DateTime(2088191207949098198, DateTimeKind.Unspecified), "s", null, "6618-03-20T23:19:54" }; + yield return new object[] { new DateTime(2088191207949098198, DateTimeKind.Unspecified), "s", CultureInfo.InvariantCulture, "6618-03-20T23:19:54" }; + yield return new object[] { new DateTime(2288235758934952239, DateTimeKind.Unspecified), "r", null, "Sun, 18 Feb 7252 00:24:53 GMT" }; + yield return new object[] { new DateTime(2288235758934952239, DateTimeKind.Unspecified), "r", CultureInfo.InvariantCulture, "Sun, 18 Feb 7252 00:24:53 GMT" }; + yield return new object[] { new DateTime(209108096540236683, DateTimeKind.Unspecified), "o", null, "0663-08-22T06:14:14.0236683" }; + yield return new object[] { new DateTime(209108096540236683, DateTimeKind.Unspecified), "o", CultureInfo.InvariantCulture, "0663-08-22T06:14:14.0236683" }; + yield return new object[] { new DateTime(1316597307220179904, DateTimeKind.Utc), "y", CultureInfo.InvariantCulture, "4173 February" }; + yield return new object[] { new DateTime(751912476142109916, DateTimeKind.Utc), "s", null, "2383-09-20T01:40:14" }; + yield return new object[] { new DateTime(751912476142109916, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "2383-09-20T01:40:14" }; + yield return new object[] { new DateTime(3046050580483567299, DateTimeKind.Unspecified), "F", CultureInfo.InvariantCulture, "Sunday, 20 July 9653 12:07:28" }; + yield return new object[] { new DateTime(3125195716254155533, DateTimeKind.Unspecified), "f", CultureInfo.InvariantCulture, "Monday, 09 May 9904 16:07" }; + yield return new object[] { new DateTime(2164505795082557313, DateTimeKind.Utc), "s", null, "6860-01-18T00:58:28" }; + yield return new object[] { new DateTime(2164505795082557313, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "6860-01-18T00:58:28" }; + yield return new object[] { new DateTime(2018959023098512429, DateTimeKind.Unspecified), "o", null, "6398-10-30T03:05:09.8512429" }; + yield return new object[] { new DateTime(2018959023098512429, DateTimeKind.Unspecified), "o", CultureInfo.InvariantCulture, "6398-10-30T03:05:09.8512429" }; + yield return new object[] { new DateTime(362242523795069450, DateTimeKind.Unspecified), "M", CultureInfo.InvariantCulture, "November 26" }; + yield return new object[] { new DateTime(975348914587607928, DateTimeKind.Unspecified), "R", null, "Mon, 05 Oct 3091 01:24:18 GMT" }; + yield return new object[] { new DateTime(975348914587607928, DateTimeKind.Unspecified), "R", CultureInfo.InvariantCulture, "Mon, 05 Oct 3091 01:24:18 GMT" }; + yield return new object[] { new DateTime(1332077483785455528, DateTimeKind.Utc), "g", CultureInfo.InvariantCulture, "03/10/4222 08:19" }; + yield return new object[] { new DateTime(938000944370428233, DateTimeKind.Unspecified), "F", CultureInfo.InvariantCulture, "Saturday, 29 May 2973 05:47:17" }; + yield return new object[] { new DateTime(102597329933554815, DateTimeKind.Utc), "s", null, "0326-02-13T21:49:53" }; + yield return new object[] { new DateTime(102597329933554815, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "0326-02-13T21:49:53" }; + yield return new object[] { new DateTime(1575336794858529992, DateTimeKind.Utc), "s", null, "4993-01-16T11:24:45" }; + yield return new object[] { new DateTime(1575336794858529992, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "4993-01-16T11:24:45" }; + yield return new object[] { new DateTime(2450361739181076766, DateTimeKind.Unspecified), "R", null, "Wed, 20 Nov 7765 19:51:58 GMT" }; + yield return new object[] { new DateTime(2450361739181076766, DateTimeKind.Unspecified), "R", CultureInfo.InvariantCulture, "Wed, 20 Nov 7765 19:51:58 GMT" }; + yield return new object[] { new DateTime(1831173654073094025, DateTimeKind.Unspecified), "O", null, "5803-10-05T22:50:07.3094025" }; + yield return new object[] { new DateTime(1831173654073094025, DateTimeKind.Unspecified), "O", CultureInfo.InvariantCulture, "5803-10-05T22:50:07.3094025" }; + yield return new object[] { new DateTime(137945581016100484, DateTimeKind.Utc), "F", CultureInfo.InvariantCulture, "Thursday, 18 February 0438 05:41:41" }; + yield return new object[] { new DateTime(525341615994432483, DateTimeKind.Unspecified), "r", null, "Mon, 28 Sep 1665 06:39:59 GMT" }; + yield return new object[] { new DateTime(525341615994432483, DateTimeKind.Unspecified), "r", CultureInfo.InvariantCulture, "Mon, 28 Sep 1665 06:39:59 GMT" }; + yield return new object[] { new DateTime(2613907075018610263, DateTimeKind.Utc), "o", null, "8284-02-22T09:51:41.8610263Z" }; + yield return new object[] { new DateTime(2613907075018610263, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "8284-02-22T09:51:41.8610263Z" }; + yield return new object[] { new DateTime(1712606630201205966, DateTimeKind.Unspecified), "M", CultureInfo.InvariantCulture, "January 14" }; + yield return new object[] { new DateTime(725879962860475783, DateTimeKind.Utc), "f", CultureInfo.InvariantCulture, "Saturday, 23 March 2301 20:18" }; + yield return new object[] { new DateTime(322635236878311096, DateTimeKind.Utc), "u", null, "1023-05-24 09:54:47Z" }; + yield return new object[] { new DateTime(322635236878311096, DateTimeKind.Utc), "u", CultureInfo.InvariantCulture, "1023-05-24 09:54:47Z" }; + yield return new object[] { new DateTime(381748720453740183, DateTimeKind.Unspecified), "D", CultureInfo.InvariantCulture, "Saturday, 18 September 1210" }; + yield return new object[] { new DateTime(42694710897975892, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "04/18/0136 04:11" }; + yield return new object[] { new DateTime(2889335867722033047, DateTimeKind.Unspecified), "t", CultureInfo.InvariantCulture, "17:39" }; + yield return new object[] { new DateTime(2206002659591968158, DateTimeKind.Utc), "s", null, "6991-07-18T19:39:19" }; + yield return new object[] { new DateTime(2206002659591968158, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "6991-07-18T19:39:19" }; + yield return new object[] { new DateTime(500692619528772429, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Thursday, 20 August 1587 08:19:12" }; + yield return new object[] { new DateTime(1252677333999884910, DateTimeKind.Unspecified), "m", CultureInfo.InvariantCulture, "July 31" }; + yield return new object[] { new DateTime(1634887756817883247, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "10/03/5181 04:48" }; + yield return new object[] { new DateTime(2995838620636779202, DateTimeKind.Unspecified), "T", CultureInfo.InvariantCulture, "19:27:43" }; + yield return new object[] { new DateTime(1108255955917223459, DateTimeKind.Unspecified), "u", null, "3512-12-04 15:39:51Z" }; + yield return new object[] { new DateTime(1108255955917223459, DateTimeKind.Unspecified), "u", CultureInfo.InvariantCulture, "3512-12-04 15:39:51Z" }; + yield return new object[] { new DateTime(1345442651123205077, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Saturday, 16 July 4264 06:58:32" }; + yield return new object[] { new DateTime(1683269053145633504, DateTimeKind.Unspecified), "f", CultureInfo.InvariantCulture, "Wednesday, 26 January 5335 01:41" }; + yield return new object[] { new DateTime(261818716531476839, DateTimeKind.Utc), "G", CultureInfo.InvariantCulture, "09/02/0830 22:07:33" }; + yield return new object[] { new DateTime(149735664893692740, DateTimeKind.Unspecified), "m", CultureInfo.InvariantCulture, "June 30" }; + yield return new object[] { new DateTime(1633263998564961778, DateTimeKind.Unspecified), "G", CultureInfo.InvariantCulture, "08/10/5176 20:24:16" }; + yield return new object[] { new DateTime(1850421988142570769, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Monday, 03 October 5864 02:46:54" }; + yield return new object[] { new DateTime(2161519739750829933, DateTimeKind.Utc), "g", CultureInfo.InvariantCulture, "08/01/6850 22:59" }; + yield return new object[] { new DateTime(94926719545582445, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "10/24/0301 21:19" }; + yield return new object[] { new DateTime(95117358366291132, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "06/02/0302 12:50" }; + yield return new object[] { new DateTime(79516486664227528, DateTimeKind.Unspecified), "y", CultureInfo.InvariantCulture, "0252 December" }; + yield return new object[] { new DateTime(2919373514923133815, DateTimeKind.Utc), "F", CultureInfo.InvariantCulture, "Friday, 16 February 9252 12:44:52" }; + yield return new object[] { new DateTime(2841096139227741307, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Sunday, 29 January 9004 17:12:02" }; + yield return new object[] { new DateTime(984728578700999277, DateTimeKind.Unspecified), "s", null, "3121-06-26T03:37:50" }; + yield return new object[] { new DateTime(984728578700999277, DateTimeKind.Unspecified), "s", CultureInfo.InvariantCulture, "3121-06-26T03:37:50" }; + yield return new object[] { new DateTime(1756213541961400682, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "03/22/5566 13:29" }; + yield return new object[] { new DateTime(1610212733937562232, DateTimeKind.Utc), "O", null, "5103-07-26T03:29:53.7562232Z" }; + yield return new object[] { new DateTime(1610212733937562232, DateTimeKind.Utc), "O", CultureInfo.InvariantCulture, "5103-07-26T03:29:53.7562232Z" }; + yield return new object[] { new DateTime(1502152713607918360, DateTimeKind.Utc), "d", CultureInfo.InvariantCulture, "02/18/4761" }; + yield return new object[] { new DateTime(230701341483195296, DateTimeKind.Unspecified), "T", CultureInfo.InvariantCulture, "10:35:48" }; + yield return new object[] { new DateTime(2946266365850485700, DateTimeKind.Utc), "D", CultureInfo.InvariantCulture, "Tuesday, 07 May 9337" }; + yield return new object[] { new DateTime(1177714949170378355, DateTimeKind.Utc), "d", CultureInfo.InvariantCulture, "01/12/3733" }; + yield return new object[] { new DateTime(2431112234795033050, DateTimeKind.Unspecified), "R", null, "Fri, 21 Nov 7704 07:24:39 GMT" }; + yield return new object[] { new DateTime(2431112234795033050, DateTimeKind.Unspecified), "R", CultureInfo.InvariantCulture, "Fri, 21 Nov 7704 07:24:39 GMT" }; + yield return new object[] { new DateTime(1166878226846532954, DateTimeKind.Unspecified), "R", null, "Tue, 09 Sep 3698 12:04:44 GMT" }; + yield return new object[] { new DateTime(1166878226846532954, DateTimeKind.Unspecified), "R", CultureInfo.InvariantCulture, "Tue, 09 Sep 3698 12:04:44 GMT" }; + yield return new object[] { new DateTime(806691560290860158, DateTimeKind.Utc), "T", CultureInfo.InvariantCulture, "18:53:49" }; + yield return new object[] { new DateTime(2329057094873169055, DateTimeKind.Unspecified), "t", CultureInfo.InvariantCulture, "22:24" }; + yield return new object[] { new DateTime(40244582424527696, DateTimeKind.Utc), "m", CultureInfo.InvariantCulture, "July 13" }; + yield return new object[] { new DateTime(754027911411478522, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "06/03/2390 11:45" }; + yield return new object[] { new DateTime(932480534482684875, DateTimeKind.Unspecified), "r", null, "Sun, 30 Nov 2955 21:04:08 GMT" }; + yield return new object[] { new DateTime(932480534482684875, DateTimeKind.Unspecified), "r", CultureInfo.InvariantCulture, "Sun, 30 Nov 2955 21:04:08 GMT" }; + yield return new object[] { new DateTime(794982031942527306, DateTimeKind.Utc), "o", null, "2520-03-14T02:13:14.2527306Z" }; + yield return new object[] { new DateTime(794982031942527306, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "2520-03-14T02:13:14.2527306Z" }; + yield return new object[] { new DateTime(2552572811382202194, DateTimeKind.Unspecified), "D", CultureInfo.InvariantCulture, "Wednesday, 12 October 8089" }; + yield return new object[] { new DateTime(924767003882430526, DateTimeKind.Unspecified), "o", null, "2931-06-22T04:19:48.2430526" }; + yield return new object[] { new DateTime(924767003882430526, DateTimeKind.Unspecified), "o", CultureInfo.InvariantCulture, "2931-06-22T04:19:48.2430526" }; + yield return new object[] { new DateTime(2081361007423859108, DateTimeKind.Utc), "r", null, "Wed, 27 Jul 6596 15:32:22 GMT" }; + yield return new object[] { new DateTime(2081361007423859108, DateTimeKind.Utc), "r", CultureInfo.InvariantCulture, "Wed, 27 Jul 6596 15:32:22 GMT" }; + yield return new object[] { new DateTime(976120464384576984, DateTimeKind.Utc), "m", CultureInfo.InvariantCulture, "March 16" }; + yield return new object[] { new DateTime(2714985378271158548, DateTimeKind.Utc), "D", CultureInfo.InvariantCulture, "Wednesday, 13 June 8604" }; + yield return new object[] { new DateTime(388901633623941264, DateTimeKind.Unspecified), "O", null, "1233-05-19T15:09:22.3941264" }; + yield return new object[] { new DateTime(388901633623941264, DateTimeKind.Unspecified), "O", CultureInfo.InvariantCulture, "1233-05-19T15:09:22.3941264" }; + yield return new object[] { new DateTime(319688581620784322, DateTimeKind.Utc), "d", CultureInfo.InvariantCulture, "01/20/1014" }; + yield return new object[] { new DateTime(1003375214898507843, DateTimeKind.Unspecified), "m", CultureInfo.InvariantCulture, "July 27" }; + yield return new object[] { new DateTime(2731383388115156519, DateTimeKind.Utc), "g", CultureInfo.InvariantCulture, "05/30/8656 08:46" }; + yield return new object[] { new DateTime(520874643765750485, DateTimeKind.Unspecified), "g", CultureInfo.InvariantCulture, "08/03/1651 04:06" }; + yield return new object[] { new DateTime(1817267127527243509, DateTimeKind.Unspecified), "o", null, "5759-09-10T10:25:52.7243509" }; + yield return new object[] { new DateTime(1817267127527243509, DateTimeKind.Unspecified), "o", CultureInfo.InvariantCulture, "5759-09-10T10:25:52.7243509" }; + yield return new object[] { new DateTime(806709007011133014, DateTimeKind.Unspecified), "t", CultureInfo.InvariantCulture, "23:31" }; + yield return new object[] { new DateTime(2916204299343097820, DateTimeKind.Unspecified), "F", CultureInfo.InvariantCulture, "Friday, 31 January 9242 10:58:54" }; + yield return new object[] { new DateTime(2540972632026940446, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Wednesday, 08 January 8053 13:06:42" }; + yield return new object[] { new DateTime(3000408879267760663, DateTimeKind.Unspecified), "F", CultureInfo.InvariantCulture, "Wednesday, 02 December 9508 11:05:26" }; + yield return new object[] { new DateTime(577741049440182530, DateTimeKind.Utc), "d", CultureInfo.InvariantCulture, "10/16/1831" }; + yield return new object[] { new DateTime(2034367617628955170, DateTimeKind.Unspecified), "t", CultureInfo.InvariantCulture, "03:36" }; + yield return new object[] { new DateTime(2403989608406651024, DateTimeKind.Unspecified), "M", CultureInfo.InvariantCulture, "December 10" }; + yield return new object[] { new DateTime(169944266714106250, DateTimeKind.Utc), "s", null, "0539-07-14T18:04:31" }; + yield return new object[] { new DateTime(169944266714106250, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "0539-07-14T18:04:31" }; + yield return new object[] { new DateTime(1496223718294708781, DateTimeKind.Utc), "s", null, "4742-05-07T09:57:09" }; + yield return new object[] { new DateTime(1496223718294708781, DateTimeKind.Utc), "s", CultureInfo.InvariantCulture, "4742-05-07T09:57:09" }; + yield return new object[] { new DateTime(1955185877962687130, DateTimeKind.Utc), "G", CultureInfo.InvariantCulture, "09/26/6196 14:49:56" }; + yield return new object[] { new DateTime(2580142503695336223, DateTimeKind.Utc), "U", CultureInfo.InvariantCulture, "Sunday, 23 February 8177 01:06:09" }; + yield return new object[] { new DateTime(1821923614163252532, DateTimeKind.Unspecified), "s", null, "5774-06-12T21:16:56" }; + yield return new object[] { new DateTime(1821923614163252532, DateTimeKind.Unspecified), "s", CultureInfo.InvariantCulture, "5774-06-12T21:16:56" }; + yield return new object[] { new DateTime(1463554571190465720, DateTimeKind.Utc), "f", CultureInfo.InvariantCulture, "Saturday, 27 October 4638 21:38" }; + yield return new object[] { new DateTime(930216714557251488, DateTimeKind.Unspecified), "D", CultureInfo.InvariantCulture, "Friday, 27 September 2948" }; + yield return new object[] { new DateTime(394960014092996385, DateTimeKind.Utc), "o", null, "1252-07-30T15:30:09.2996385Z" }; + yield return new object[] { new DateTime(394960014092996385, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "1252-07-30T15:30:09.2996385Z" }; + yield return new object[] { new DateTime(315410460953591926, DateTimeKind.Utc), "o", null, "1000-07-01T09:41:35.3591926Z" }; + yield return new object[] { new DateTime(315410460953591926, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "1000-07-01T09:41:35.3591926Z" }; + yield return new object[] { new DateTime(632402741486587776, DateTimeKind.Unspecified), "f", CultureInfo.InvariantCulture, "Sunday, 02 January 2005 14:49" }; + yield return new object[] { new DateTime(2476889938089063320, DateTimeKind.Utc), "f", CultureInfo.InvariantCulture, "Friday, 14 December 7849 18:16" }; + yield return new object[] { new DateTime(1461010624152234289, DateTimeKind.Unspecified), "d", CultureInfo.InvariantCulture, "10/05/4630" }; + yield return new object[] { new DateTime(628703264608901490, DateTimeKind.Utc), "o", null, "1993-04-13T19:34:20.8901490Z" }; + yield return new object[] { new DateTime(628703264608901490, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "1993-04-13T19:34:20.8901490Z" }; + yield return new object[] { new DateTime(681957838388530050, DateTimeKind.Utc), "O", null, "2162-01-15T01:17:18.8530050Z" }; + yield return new object[] { new DateTime(681957838388530050, DateTimeKind.Utc), "O", CultureInfo.InvariantCulture, "2162-01-15T01:17:18.8530050Z" }; + yield return new object[] { new DateTime(788041760058691059, DateTimeKind.Unspecified), "d", CultureInfo.InvariantCulture, "03/16/2498" }; + yield return new object[] { new DateTime(2146466025818766443, DateTimeKind.Utc), "o", null, "6802-11-18T16:16:21.8766443Z" }; + yield return new object[] { new DateTime(2146466025818766443, DateTimeKind.Utc), "o", CultureInfo.InvariantCulture, "6802-11-18T16:16:21.8766443Z" }; // Year patterns if (PlatformDetection.IsNotInvariantGlobalization) @@ -2362,6 +2457,9 @@ public static IEnumerable ToString_MatchesExpected_MemberData() yield return new object[] { new DateTime(1234, 5, 6), new string('y', i), invariant, 1234.ToString("D" + i) }; } } + + // Non-ASCII in format string + yield return new object[] { new DateTime(2023, 04, 17, 10, 46, 12, DateTimeKind.Utc), "HH\u202dmm", null, "10\u202d46" }; } [Theory] @@ -2663,21 +2761,21 @@ public static void TryFormat_MatchesToString(string format) // UTF8 { // Just the right length, succeeds - Span dest = new byte[expected.Length]; + Span dest = new byte[Encoding.UTF8.GetByteCount(expected)]; Assert.True(dt.TryFormat(dest, out int bytesWritten, format)); - Assert.Equal(expected.Length, bytesWritten); + Assert.Equal(dest.Length, bytesWritten); Assert.Equal(expected, Encoding.UTF8.GetString(dest)); // Too short, fails - dest = new byte[expected.Length - 1]; + dest = new byte[Encoding.UTF8.GetByteCount(expected) - 1]; Assert.False(dt.TryFormat(dest, out bytesWritten, format)); Assert.Equal(0, bytesWritten); // Longer than needed, succeeds - dest = new byte[expected.Length + 1]; + dest = new byte[Encoding.UTF8.GetByteCount(expected) + 1]; Assert.True(dt.TryFormat(dest, out bytesWritten, format)); - Assert.Equal(expected.Length, bytesWritten); - Assert.Equal(expected, Encoding.UTF8.GetString(dest.Slice(0, expected.Length))); + Assert.Equal(dest.Length - 1, bytesWritten); + Assert.Equal(expected, Encoding.UTF8.GetString(dest.Slice(0, bytesWritten))); Assert.Equal(0, dest[dest.Length - 1]); } } @@ -2700,7 +2798,7 @@ public static void TryFormat_MatchesExpected(DateTime dateTime, string format, I // UTF8 { - var destination = new byte[expected.Length]; + var destination = new byte[Encoding.UTF8.GetByteCount(expected)]; Assert.False(dateTime.TryFormat(destination.AsSpan(0, destination.Length - 1), out _, format, provider));