Skip to content

Commit

Permalink
[release/8.0-staging] [HybridGlobalization]Pass non-breaking space / …
Browse files Browse the repository at this point in the history
…narrow non-breaking space characters (#103647)

* Backport of #103226 to release/8.0-staging
  • Loading branch information
mkhamoyan authored Jun 25, 2024
1 parent db4baaa commit 4ef94c1
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,5 +253,19 @@ public void ShortTimePattern_SetReadOnly_ThrowsInvalidOperationException()
{
Assert.Throws<InvalidOperationException>(() => 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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> 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<char>(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
span = span.Slice(0, span.IndexOf('\0'));
}

var span = new ReadOnlySpan<char>(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ namespace System.Globalization
{
internal sealed partial class CultureData
{
private const int LOC_FULLNAME_CAPACITY = 157; // max size of locale name

/// <summary>
/// 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.
Expand Down Expand Up @@ -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<char> 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();
}
}
}

0 comments on commit 4ef94c1

Please sign in to comment.