From 4ef94c1a9e534e99e9c9eeb6d2f927f37bfcd47d Mon Sep 17 00:00:00 2001 From: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:03:37 +0200 Subject: [PATCH] [release/8.0-staging] [HybridGlobalization]Pass non-breaking space / narrow non-breaking space characters (#103647) * Backport of #103226 to release/8.0-staging --- .../DateTimeFormatInfoLongTimePattern.cs | 14 ++++ .../DateTimeFormatInfoShortTimePattern.cs | 14 ++++ .../System/Globalization/CultureData.Icu.cs | 34 +++++++--- .../System/Globalization/CultureData.Unix.cs | 4 -- .../src/System/Globalization/CultureData.cs | 4 -- .../System/Globalization/CultureData.iOS.cs | 68 ------------------- 6 files changed, 53 insertions(+), 85 deletions(-) diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs index 5992624b1e10f..7d0fc185519b3 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs @@ -282,5 +282,19 @@ 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"); + 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 6afd8293248e8..2c81f9c27744b 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs @@ -253,5 +253,19 @@ 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"); + 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 12af791d392e2..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 @@ -262,18 +262,34 @@ 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); + if (string.IsNullOrEmpty(res)) + { + Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); + return string.Empty; + } + span = res.AsSpan(); + } + 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..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,8 +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. @@ -78,71 +76,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(); - } } }