From ad0361177d895fa0f0faf74808082f2fb4b89c2c Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Tue, 18 Jun 2024 17:22:56 +0200 Subject: [PATCH 1/3] Backport of #103226 to release/8.0-staging --- .../DateTimeFormatInfoLongTimePattern.cs | 8 ++ .../DateTimeFormatInfoShortTimePattern.cs | 8 ++ .../System/Globalization/CultureData.Icu.cs | 29 +++++-- .../System/Globalization/CultureData.Unix.cs | 4 - .../src/System/Globalization/CultureData.cs | 4 - .../System/Globalization/CultureData.iOS.cs | 82 ------------------- 6 files changed, 36 insertions(+), 99 deletions(-) diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs index 5992624b1e10f..68afbde983ae1 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs @@ -282,5 +282,13 @@ public void LongTimePattern_CheckReadingTimeFormatWithSingleQuotes_ICU() } } } + + [Fact] + public void LongTimePattern_CheckTimeFormatWithSpaces() + { + var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15); + var culture = new CultureInfo("en-US"); + Assert.Equal("3:15:00 PM", date.ToString("T", culture)); + } } } diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs index 6afd8293248e8..5d157c1ba5a6f 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs @@ -253,5 +253,13 @@ public void ShortTimePattern_SetReadOnly_ThrowsInvalidOperationException() { Assert.Throws(() => DateTimeFormatInfo.InvariantInfo.ShortTimePattern = "HH:mm"); } + + [Fact] + public void ShortTimePattern_CheckTimeFormatWithSpaces() + { + var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15); + var culture = new CultureInfo("en-US"); + Assert.Equal("3:15 PM", date.ToString("t", culture)); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 12af791d392e2..6004e4bdac747 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -262,18 +262,29 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat) Debug.Assert(!GlobalizationMode.UseNls); Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already"); - char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY]; - - bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); - if (!result) + ReadOnlySpan span; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) { - // Failed, just use empty string - Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); - return string.Empty; + string res = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat); + span = res != null ? new ReadOnlySpan(ref res.GetRawStringData(), res.Length) : ReadOnlySpan.Empty; + } + else +#endif + { + char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY]; + bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + if (!result) + { + // Failed, just use empty string + Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); + return string.Empty; + } + span = new ReadOnlySpan(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + span = span.Slice(0, span.IndexOf('\0')); } - var span = new ReadOnlySpan(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); - return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0'))); + return ConvertIcuTimeFormatString(span); } // no support to lookup by region name, other than the hard-coded list in CultureData diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs index 533e97b3127d3..39a8e73b7f131 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs @@ -25,11 +25,7 @@ private bool InitCultureDataCore() => private string[]? GetTimeFormatsCore(bool shortFormat) { -#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - string format = GlobalizationMode.Hybrid ? GetTimeFormatStringNative(shortFormat) : IcuGetTimeFormatString(shortFormat); -#else string format = IcuGetTimeFormatString(shortFormat); -#endif return new string[] { format }; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 5628bf35fc506..63db5cdf58c12 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -1992,11 +1992,7 @@ internal string TimeSeparator } else { -#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - string? longTimeFormat = GlobalizationMode.Hybrid ? GetTimeFormatStringNative() : IcuGetTimeFormatString(); -#else string? longTimeFormat = ShouldUseUserOverrideNlsData ? NlsGetTimeFormatString() : IcuGetTimeFormatString(); -#endif if (string.IsNullOrEmpty(longTimeFormat)) { longTimeFormat = LongTimes[0]; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs index cdbdd65a81426..2f1cb954400db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs @@ -7,22 +7,6 @@ namespace System.Globalization { internal sealed partial class CultureData { - private const int LOC_FULLNAME_CAPACITY = 157; // max size of locale name - - /// - /// This method uses the sRealName field (which is initialized by the constructor before this is called) to - /// initialize the rest of the state of CultureData based on the underlying OS globalization library. - /// - private bool InitAppleCultureDataCore() - { - Debug.Assert(_sRealName != null); - Debug.Assert(!GlobalizationMode.Invariant); - string realNameBuffer = _sRealName; - - _sWindowsName = _sName = _sRealName = GetLocaleNameNative(realNameBuffer); - return true; - } - internal static string GetLocaleNameNative(string localeName) { return Interop.Globalization.GetLocaleNameNative(localeName); @@ -78,71 +62,5 @@ private int[] GetLocaleInfoNative(LocaleGroupingData type) return new int[] { primaryGroupingSize, secondaryGroupingSize }; } - - private string GetTimeFormatStringNative() => GetTimeFormatStringNative(shortFormat: false); - - private string GetTimeFormatStringNative(bool shortFormat) - { - Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatStringNative(bool shortFormat)] Expected _sWindowsName to be populated already"); - - string result = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat); - - return ConvertNativeTimeFormatString(result); - } - - private static string ConvertNativeTimeFormatString(string nativeFormatString) - { - Span result = stackalloc char[LOC_FULLNAME_CAPACITY]; - - bool amPmAdded = false; - int resultPos = 0; - - for (int i = 0; i < nativeFormatString.Length; i++) - { - switch (nativeFormatString[i]) - { - case '\'': - result[resultPos++] = nativeFormatString[i++]; - while (i < nativeFormatString.Length) - { - char current = nativeFormatString[i]; - result[resultPos++] = current; - if (current == '\'') - { - break; - } - i++; - } - break; - - case ':': - case '.': - case 'H': - case 'h': - case 'm': - case 's': - result[resultPos++] = nativeFormatString[i]; - break; - - case ' ': - case '\u00A0': - // Convert nonbreaking spaces into regular spaces - result[resultPos++] = ' '; - break; - - case 'a': // AM/PM - if (!amPmAdded) - { - amPmAdded = true; - result[resultPos++] = 't'; - result[resultPos++] = 't'; - } - break; - - } - } - - return result.Slice(0, resultPos).ToString(); - } } } From 6bf6f01b1e13fbe6219c5af434951e539f5ff51c Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Wed, 19 Jun 2024 10:19:33 +0200 Subject: [PATCH 2/3] Fix wrong merge --- .../src/System/Globalization/CultureData.iOS.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs index 2f1cb954400db..a62496b7727be 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs @@ -7,6 +7,20 @@ namespace System.Globalization { internal sealed partial class CultureData { + /// + /// This method uses the sRealName field (which is initialized by the constructor before this is called) to + /// initialize the rest of the state of CultureData based on the underlying OS globalization library. + /// + private bool InitAppleCultureDataCore() + { + Debug.Assert(_sRealName != null); + Debug.Assert(!GlobalizationMode.Invariant); + string realNameBuffer = _sRealName; + + _sWindowsName = _sName = _sRealName = GetLocaleNameNative(realNameBuffer); + return true; + } + internal static string GetLocaleNameNative(string localeName) { return Interop.Globalization.GetLocaleNameNative(localeName); From c2e80de3abee22a98ec052abb4bd3b77619d1bb3 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Wed, 19 Jun 2024 11:51:34 +0200 Subject: [PATCH 3/3] Fix merge errors --- .../DateTimeFormatInfoLongTimePattern.cs | 8 +++++++- .../DateTimeFormatInfoShortTimePattern.cs | 8 +++++++- .../src/System/Globalization/CultureData.Icu.cs | 7 ++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs index 68afbde983ae1..7d0fc185519b3 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs @@ -288,7 +288,13 @@ public void LongTimePattern_CheckTimeFormatWithSpaces() { var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15); var culture = new CultureInfo("en-US"); - Assert.Equal("3:15:00 PM", date.ToString("T", culture)); + string formattedDate = date.ToString("t", culture); + bool containsSpace = formattedDate.Contains(' '); + bool containsNoBreakSpace = formattedDate.Contains('\u00A0'); + bool containsNarrowNoBreakSpace = formattedDate.Contains('\u202F'); + + Assert.True(containsSpace || containsNoBreakSpace || containsNarrowNoBreakSpace, + $"Formatted date string '{formattedDate}' does not contain any of the specified spaces."); } } } diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs index 5d157c1ba5a6f..2c81f9c27744b 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs @@ -259,7 +259,13 @@ public void ShortTimePattern_CheckTimeFormatWithSpaces() { var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15); var culture = new CultureInfo("en-US"); - Assert.Equal("3:15 PM", date.ToString("t", culture)); + string formattedDate = date.ToString("t", culture); + bool containsSpace = formattedDate.Contains(' '); + bool containsNoBreakSpace = formattedDate.Contains('\u00A0'); + bool containsNarrowNoBreakSpace = formattedDate.Contains('\u202F'); + + Assert.True(containsSpace || containsNoBreakSpace || containsNarrowNoBreakSpace, + $"Formatted date string '{formattedDate}' does not contain any of the specified spaces."); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 6004e4bdac747..7b90feefea1f3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -267,7 +267,12 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat) if (GlobalizationMode.Hybrid) { string res = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat); - span = res != null ? new ReadOnlySpan(ref res.GetRawStringData(), res.Length) : ReadOnlySpan.Empty; + if (string.IsNullOrEmpty(res)) + { + Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); + return string.Empty; + } + span = res.AsSpan(); } else #endif