Skip to content

Commit 4ef94c1

Browse files
authored
[release/8.0-staging] [HybridGlobalization]Pass non-breaking space / narrow non-breaking space characters (#103647)
* Backport of #103226 to release/8.0-staging
1 parent db4baaa commit 4ef94c1

File tree

6 files changed

+53
-85
lines changed

6 files changed

+53
-85
lines changed

src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,5 +282,19 @@ public void LongTimePattern_CheckReadingTimeFormatWithSingleQuotes_ICU()
282282
}
283283
}
284284
}
285+
286+
[Fact]
287+
public void LongTimePattern_CheckTimeFormatWithSpaces()
288+
{
289+
var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15);
290+
var culture = new CultureInfo("en-US");
291+
string formattedDate = date.ToString("t", culture);
292+
bool containsSpace = formattedDate.Contains(' ');
293+
bool containsNoBreakSpace = formattedDate.Contains('\u00A0');
294+
bool containsNarrowNoBreakSpace = formattedDate.Contains('\u202F');
295+
296+
Assert.True(containsSpace || containsNoBreakSpace || containsNarrowNoBreakSpace,
297+
$"Formatted date string '{formattedDate}' does not contain any of the specified spaces.");
298+
}
285299
}
286300
}

src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,5 +253,19 @@ public void ShortTimePattern_SetReadOnly_ThrowsInvalidOperationException()
253253
{
254254
Assert.Throws<InvalidOperationException>(() => DateTimeFormatInfo.InvariantInfo.ShortTimePattern = "HH:mm");
255255
}
256+
257+
[Fact]
258+
public void ShortTimePattern_CheckTimeFormatWithSpaces()
259+
{
260+
var date = DateTime.Today + TimeSpan.FromHours(15) + TimeSpan.FromMinutes(15);
261+
var culture = new CultureInfo("en-US");
262+
string formattedDate = date.ToString("t", culture);
263+
bool containsSpace = formattedDate.Contains(' ');
264+
bool containsNoBreakSpace = formattedDate.Contains('\u00A0');
265+
bool containsNarrowNoBreakSpace = formattedDate.Contains('\u202F');
266+
267+
Assert.True(containsSpace || containsNoBreakSpace || containsNarrowNoBreakSpace,
268+
$"Formatted date string '{formattedDate}' does not contain any of the specified spaces.");
269+
}
256270
}
257271
}

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,18 +262,34 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat)
262262
Debug.Assert(!GlobalizationMode.UseNls);
263263
Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already");
264264

265-
char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY];
266-
267-
bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
268-
if (!result)
265+
ReadOnlySpan<char> span;
266+
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
267+
if (GlobalizationMode.Hybrid)
269268
{
270-
// Failed, just use empty string
271-
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
272-
return string.Empty;
269+
string res = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat);
270+
if (string.IsNullOrEmpty(res))
271+
{
272+
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
273+
return string.Empty;
274+
}
275+
span = res.AsSpan();
276+
}
277+
else
278+
#endif
279+
{
280+
char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY];
281+
bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
282+
if (!result)
283+
{
284+
// Failed, just use empty string
285+
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
286+
return string.Empty;
287+
}
288+
span = new ReadOnlySpan<char>(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
289+
span = span.Slice(0, span.IndexOf('\0'));
273290
}
274291

275-
var span = new ReadOnlySpan<char>(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
276-
return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0')));
292+
return ConvertIcuTimeFormatString(span);
277293
}
278294

279295
// no support to lookup by region name, other than the hard-coded list in CultureData

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Unix.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ private bool InitCultureDataCore() =>
2525

2626
private string[]? GetTimeFormatsCore(bool shortFormat)
2727
{
28-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
29-
string format = GlobalizationMode.Hybrid ? GetTimeFormatStringNative(shortFormat) : IcuGetTimeFormatString(shortFormat);
30-
#else
3128
string format = IcuGetTimeFormatString(shortFormat);
32-
#endif
3329
return new string[] { format };
3430
}
3531

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,11 +1992,7 @@ internal string TimeSeparator
19921992
}
19931993
else
19941994
{
1995-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
1996-
string? longTimeFormat = GlobalizationMode.Hybrid ? GetTimeFormatStringNative() : IcuGetTimeFormatString();
1997-
#else
19981995
string? longTimeFormat = ShouldUseUserOverrideNlsData ? NlsGetTimeFormatString() : IcuGetTimeFormatString();
1999-
#endif
20001996
if (string.IsNullOrEmpty(longTimeFormat))
20011997
{
20021998
longTimeFormat = LongTimes[0];

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.iOS.cs

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ namespace System.Globalization
77
{
88
internal sealed partial class CultureData
99
{
10-
private const int LOC_FULLNAME_CAPACITY = 157; // max size of locale name
11-
1210
/// <summary>
1311
/// This method uses the sRealName field (which is initialized by the constructor before this is called) to
1412
/// initialize the rest of the state of CultureData based on the underlying OS globalization library.
@@ -78,71 +76,5 @@ private int[] GetLocaleInfoNative(LocaleGroupingData type)
7876

7977
return new int[] { primaryGroupingSize, secondaryGroupingSize };
8078
}
81-
82-
private string GetTimeFormatStringNative() => GetTimeFormatStringNative(shortFormat: false);
83-
84-
private string GetTimeFormatStringNative(bool shortFormat)
85-
{
86-
Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatStringNative(bool shortFormat)] Expected _sWindowsName to be populated already");
87-
88-
string result = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat);
89-
90-
return ConvertNativeTimeFormatString(result);
91-
}
92-
93-
private static string ConvertNativeTimeFormatString(string nativeFormatString)
94-
{
95-
Span<char> result = stackalloc char[LOC_FULLNAME_CAPACITY];
96-
97-
bool amPmAdded = false;
98-
int resultPos = 0;
99-
100-
for (int i = 0; i < nativeFormatString.Length; i++)
101-
{
102-
switch (nativeFormatString[i])
103-
{
104-
case '\'':
105-
result[resultPos++] = nativeFormatString[i++];
106-
while (i < nativeFormatString.Length)
107-
{
108-
char current = nativeFormatString[i];
109-
result[resultPos++] = current;
110-
if (current == '\'')
111-
{
112-
break;
113-
}
114-
i++;
115-
}
116-
break;
117-
118-
case ':':
119-
case '.':
120-
case 'H':
121-
case 'h':
122-
case 'm':
123-
case 's':
124-
result[resultPos++] = nativeFormatString[i];
125-
break;
126-
127-
case ' ':
128-
case '\u00A0':
129-
// Convert nonbreaking spaces into regular spaces
130-
result[resultPos++] = ' ';
131-
break;
132-
133-
case 'a': // AM/PM
134-
if (!amPmAdded)
135-
{
136-
amPmAdded = true;
137-
result[resultPos++] = 't';
138-
result[resultPos++] = 't';
139-
}
140-
break;
141-
142-
}
143-
}
144-
145-
return result.Slice(0, resultPos).ToString();
146-
}
14779
}
14880
}

0 commit comments

Comments
 (0)