Skip to content

Commit 5dbd6f6

Browse files
authored
[HybridGlobalization] Pass non-breaking space / narrow non-breaking space characters (#103226)
Pass non-breaking, narrow non-breaking space as they are
1 parent a189e83 commit 5dbd6f6

File tree

9 files changed

+57
-101
lines changed

9 files changed

+57
-101
lines changed

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

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

294-
char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY];
295-
296-
bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
297-
if (!result)
294+
ReadOnlySpan<char> span;
295+
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
296+
if (GlobalizationMode.Hybrid)
298297
{
299-
// Failed, just use empty string
300-
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
301-
return string.Empty;
298+
string res = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat);
299+
if (string.IsNullOrEmpty(res))
300+
{
301+
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
302+
return string.Empty;
303+
}
304+
span = res.AsSpan();
305+
}
306+
else
307+
#endif
308+
{
309+
char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY];
310+
bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
311+
if (!result)
312+
{
313+
// Failed, just use empty string
314+
Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed");
315+
return string.Empty;
316+
}
317+
span = new ReadOnlySpan<char>(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
318+
span = span.Slice(0, span.IndexOf('\0'));
302319
}
303320

304-
var span = new ReadOnlySpan<char>(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
305-
return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0')));
321+
return ConvertIcuTimeFormatString(span);
306322
}
307323

308324
// no support to lookup by region name, other than the hard-coded list in CultureData
@@ -373,7 +389,6 @@ private static string ConvertIcuTimeFormatString(ReadOnlySpan<char> icuFormatStr
373389
case '\u202F': // narrow no-break space
374390
result[resultPos++] = current;
375391
break;
376-
377392
case 'a': // AM/PM
378393
if (!amPmAdded)
379394
{

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
@@ -20,11 +20,7 @@ internal sealed partial class CultureData
2020

2121
private string[]? GetTimeFormatsCore(bool shortFormat)
2222
{
23-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
24-
string format = GlobalizationMode.Hybrid ? GetTimeFormatStringNative(shortFormat) : IcuGetTimeFormatString(shortFormat);
25-
#else
2623
string format = IcuGetTimeFormatString(shortFormat);
27-
#endif
2824
return new string[] { format };
2925
}
3026

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,11 +1978,7 @@ internal string TimeSeparator
19781978
}
19791979
else
19801980
{
1981-
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
1982-
string? longTimeFormat = GlobalizationMode.Hybrid ? GetTimeFormatStringNative() : IcuGetTimeFormatString();
1983-
#else
19841981
string? longTimeFormat = ShouldUseUserOverrideNlsData ? NlsGetTimeFormatString() : IcuGetTimeFormatString();
1985-
#endif
19861982
if (string.IsNullOrEmpty(longTimeFormat))
19871983
{
19881984
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
internal static string GetLocaleNameNative(string localeName)
1311
{
1412
return Interop.Globalization.GetLocaleNameNative(localeName);
@@ -64,71 +62,5 @@ private int[] GetLocaleInfoNative(LocaleGroupingData type)
6462

6563
return new int[] { primaryGroupingSize, secondaryGroupingSize };
6664
}
67-
68-
private string GetTimeFormatStringNative() => GetTimeFormatStringNative(shortFormat: false);
69-
70-
private string GetTimeFormatStringNative(bool shortFormat)
71-
{
72-
Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatStringNative(bool shortFormat)] Expected _sWindowsName to be populated already");
73-
74-
string result = Interop.Globalization.GetLocaleTimeFormatNative(_sWindowsName, shortFormat);
75-
76-
return ConvertNativeTimeFormatString(result);
77-
}
78-
79-
private static string ConvertNativeTimeFormatString(string nativeFormatString)
80-
{
81-
Span<char> result = stackalloc char[LOC_FULLNAME_CAPACITY];
82-
83-
bool amPmAdded = false;
84-
int resultPos = 0;
85-
86-
for (int i = 0; i < nativeFormatString.Length; i++)
87-
{
88-
switch (nativeFormatString[i])
89-
{
90-
case '\'':
91-
result[resultPos++] = nativeFormatString[i++];
92-
while (i < nativeFormatString.Length)
93-
{
94-
char current = nativeFormatString[i];
95-
result[resultPos++] = current;
96-
if (current == '\'')
97-
{
98-
break;
99-
}
100-
i++;
101-
}
102-
break;
103-
104-
case ':':
105-
case '.':
106-
case 'H':
107-
case 'h':
108-
case 'm':
109-
case 's':
110-
result[resultPos++] = nativeFormatString[i];
111-
break;
112-
113-
case ' ':
114-
case '\u00A0':
115-
// Convert nonbreaking spaces into regular spaces
116-
result[resultPos++] = ' ';
117-
break;
118-
119-
case 'a': // AM/PM
120-
if (!amPmAdded)
121-
{
122-
amPmAdded = true;
123-
result[resultPos++] = 't';
124-
result[resultPos++] = 't';
125-
}
126-
break;
127-
128-
}
129-
}
130-
131-
return result.Slice(0, resultPos).ToString();
132-
}
13365
}
13466
}

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

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

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

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

src/mono/browser/runtime/hybrid-globalization/calendar.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { VoidPtrNull } from "../types/internal";
66
import { runtimeHelpers } from "./module-exports";
77
import { Int32Ptr, VoidPtr } from "../types/emscripten";
8-
import { INNER_SEPARATOR, OUTER_SEPARATOR, normalizeSpaces } from "./helpers";
8+
import { INNER_SEPARATOR, OUTER_SEPARATOR } from "./helpers";
99

1010
const MONTH_CODE = "MMMM";
1111
const YEAR_CODE = "yyyy";
@@ -96,7 +96,6 @@ function getMonthYearPattern (locale: string | undefined, date: Date): string {
9696
pattern = pattern.replace("999", YEAR_CODE);
9797
// sometimes the number is localized and the above does not have an effect
9898
const yearStr = date.toLocaleDateString(locale, { year: "numeric" });
99-
pattern = normalizeSpaces(pattern);
10099
return pattern.replace(yearStr, YEAR_CODE);
101100
}
102101

@@ -165,7 +164,7 @@ function getShortDatePattern (locale: string | undefined): string {
165164
const localizedDayCode = dayStr.length == 1 ? "d" : "dd";
166165
pattern = pattern.replace(dayStr, localizedDayCode);
167166
}
168-
return normalizeSpaces(pattern);
167+
return pattern;
169168
}
170169

171170
function getLongDatePattern (locale: string | undefined, date: Date): string {
@@ -196,7 +195,6 @@ function getLongDatePattern (locale: string | undefined, date: Date): string {
196195
pattern = pattern.replace(replacedWeekday, "dddd");
197196
pattern = pattern.replace("22", DAY_CODE);
198197
const dayStr = date.toLocaleDateString(locale, { day: "numeric" }); // should we replace it for localized digits?
199-
pattern = normalizeSpaces(pattern);
200198
return pattern.replace(dayStr, DAY_CODE);
201199
}
202200

src/mono/browser/runtime/hybrid-globalization/culture-info.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { VoidPtrNull } from "../types/internal";
55
import { runtimeHelpers } from "./module-exports";
66
import { Int32Ptr, VoidPtr } from "../types/emscripten";
7-
import { OUTER_SEPARATOR, normalizeLocale, normalizeSpaces } from "./helpers";
7+
import { OUTER_SEPARATOR, normalizeLocale } from "./helpers";
88

99
export function mono_wasm_get_culture_info (culture: number, cultureLength: number, dst: number, dstMaxLength: number, dstLength: Int32Ptr): VoidPtr {
1010
try {
@@ -91,7 +91,7 @@ function getLongTimePattern (locale: string | undefined, designators: any): stri
9191
hourPattern = hasPrefix ? "hh" : "h";
9292
pattern = pattern.replace(hasPrefix ? hour12WithPrefix : localizedHour12, hourPattern);
9393
}
94-
return normalizeSpaces(pattern);
94+
return pattern;
9595
}
9696

9797
function getShortTimePattern (pattern: string): string {

src/mono/browser/runtime/hybrid-globalization/helpers.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,6 @@ export function normalizeLocale (locale: string | null) {
2626
}
2727
}
2828

29-
export function normalizeSpaces (pattern: string) {
30-
if (!pattern.includes("\u202F"))
31-
return pattern;
32-
33-
// if U+202F present, replace them with spaces
34-
return pattern.replace("\u202F", "\u0020");
35-
}
36-
37-
3829
export function isSurrogate (str: string, startIdx: number): boolean {
3930
return SURROGATE_HIGHER_START <= str[startIdx] &&
4031
str[startIdx] <= SURROGATE_HIGHER_END &&

0 commit comments

Comments
 (0)