Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OSX] HybridGlobalization implement locale native functions #84417

Merged
merged 18 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Locale.OSX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,17 @@ internal static partial class Globalization

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoStringNative", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial string GetLocaleInfoStringNative(string localeName, uint localeStringData);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoIntNative", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetLocaleInfoIntNative(string localeName, uint localeNumberData);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetLocaleInfoPrimaryGroupingSizeNative(string localeName, uint localeGroupingData);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetLocaleInfoSecondaryGroupingSizeNative(string localeName, uint localeGroupingData);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleTimeFormatNative", StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial string GetLocaleTimeFormatNative(string localeName, [MarshalAs(UnmanagedType.Bool)] bool shortFormat);
}
}
83 changes: 83 additions & 0 deletions src/libraries/System.Globalization/tests/Hybrid/HybridMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,88 @@ public void TwoLetterISOLanguageName(string name, string expected)
{
Assert.Equal(expected, new CultureInfo(name).TwoLetterISOLanguageName);
}

/*public static IEnumerable<object[]> FirstDayOfWeek_Get_TestData()
{
yield return new object[] { DateTimeFormatInfo.InvariantInfo, DayOfWeek.Sunday };
yield return new object[] { new CultureInfo("en-US", false).DateTimeFormat, DayOfWeek.Sunday };
yield return new object[] { new CultureInfo("fr-FR", false).DateTimeFormat, DayOfWeek.Monday };
}

[Theory]
[MemberData(nameof(FirstDayOfWeek_Get_TestData))]
public void FirstDayOfWeek(DateTimeFormatInfo format, DayOfWeek expected)
{
Assert.Equal(expected, format.FirstDayOfWeek);
}*/

[Theory]
[InlineData(DayOfWeek.Sunday)]
[InlineData(DayOfWeek.Monday)]
[InlineData(DayOfWeek.Tuesday)]
[InlineData(DayOfWeek.Wednesday)]
[InlineData(DayOfWeek.Thursday)]
[InlineData(DayOfWeek.Friday)]
[InlineData(DayOfWeek.Saturday)]
public void FirstDayOfWeek_Set_GetReturnsExpected(DayOfWeek value)
{
var format = new DateTimeFormatInfo();
format.FirstDayOfWeek = value;
Assert.Equal(value, format.FirstDayOfWeek);
}

public static IEnumerable<object[]> CurrencyNegativePatternTestLocales()
{
yield return new object[] { "en-US" };
yield return new object[] { "en-CA" };
yield return new object[] { "fa-IR" };
yield return new object[] { "fr-CD" };
yield return new object[] { "fr-CA" };
}

[Theory]
[MemberData(nameof(CurrencyNegativePatternTestLocales))]
public void CurrencyNegativePattern_Get_ReturnsExpected_ByLocale(string locale)
{
CultureInfo culture;
try
{
culture = CultureInfo.GetCultureInfo(locale);
}
catch (CultureNotFoundException)
{
return; // ignore unsupported culture
}

NumberFormatInfo format = culture.NumberFormat;
Assert.Contains(format.CurrencyNegativePattern, GetCurrencyNegativePatterns(locale));
}

internal static int[] GetCurrencyNegativePatterns(string localeName)
{
// CentOS uses an older ICU than Ubuntu, which means the "Linux" values need to allow for
// multiple values, since we can't tell which version of ICU we are using, or whether we are
// on CentOS or Ubuntu.
// When multiple values are returned, the "older" ICU value is returned last.

switch (localeName)
{
case "en-US":
return new int[] { 1, 0 };
case "en-CA":
return new int[] { 1, 0 };
case "fa-IR":
return new int[] { 1, 0 };
case "fr-CD":
return new int[] { 8, 15 };
case "as":
return new int[] { 9 };
case "fr-CA":
return new int[] { 8, 15 };
}

return new int[] { 0 };
//throw DateTimeFormatInfoData.GetCultureNotSupportedException(CultureInfo.GetCultureInfo(localeName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private bool InitNativeCultureDataCore()
return true;
}

internal static unsafe string GetLocaleNameNative(string localeName)
internal static string GetLocaleNameNative(string localeName)
{
return Interop.Globalization.GetLocaleNameNative(localeName);
}
Expand All @@ -36,11 +36,107 @@ private string GetLocaleInfoNative(LocaleStringData type)

// For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
// "windows" name, which can be specific for downlevel (< windows 7) os's.
private static unsafe string GetLocaleInfoNative(string localeName, LocaleStringData type)
private static string GetLocaleInfoNative(string localeName, LocaleStringData type)
{
Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoNative] Expected localeName to be not be null");

return Interop.Globalization.GetLocaleInfoStringNative(localeName, (uint)type);
}

private int GetLocaleInfoNative(LocaleNumberData type)
{
Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfoNative(LocaleNumberData)] Expected _sWindowsName to be populated already");

switch (type)
{
case LocaleNumberData.CalendarType:
// returning 0 will cause the first supported calendar to be returned, which is the preferred calendar
return 0;
}


return Interop.Globalization.GetLocaleInfoIntNative(_sWindowsName, (uint)type);
}

private int[] GetLocaleInfoNative(LocaleGroupingData type)
{
Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfoNative(LocaleGroupingData)] Expected _sWindowsName to be populated already");

int primaryGroupingSize = Interop.Globalization.GetLocaleInfoPrimaryGroupingSizeNative(_sWindowsName, (uint)type);
int secondaryGroupingSize = Interop.Globalization.GetLocaleInfoSecondaryGroupingSizeNative(_sWindowsName, (uint)type);

if (secondaryGroupingSize == 0)
{
return new int[] { primaryGroupingSize };
}

return new int[] { primaryGroupingSize, secondaryGroupingSize };
}

private string GetTimeFormatStringNative() => GetTimeFormatStringNative(shortFormat: false);

private unsafe 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 ConvertIcuTimeFormatString(result);
}

private static string ConvertNativeTimeFormatString(string nativeFormatString)
{
Span<char> result = stackalloc char[ICU_ULOC_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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ internal sealed partial class CultureData

private string[]? GetTimeFormatsCore(bool shortFormat)
{
#if TARGET_OSX || 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 @@ -1541,7 +1541,11 @@ internal int FirstDayOfWeek
{
if (_iFirstDayOfWeek == undef && !GlobalizationMode.Invariant)
{
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
_iFirstDayOfWeek = GlobalizationMode.Hybrid ? GetLocaleInfoNative(LocaleNumberData.FirstDayOfWeek) : IcuGetLocaleInfo(LocaleNumberData.FirstDayOfWeek);
#else
_iFirstDayOfWeek = ShouldUseUserOverrideNlsData ? NlsGetFirstDayOfWeek() : IcuGetLocaleInfo(LocaleNumberData.FirstDayOfWeek);
#endif
}
return _iFirstDayOfWeek;
}
Expand Down Expand Up @@ -1949,7 +1953,11 @@ internal string TimeSeparator
}
else
{
#if TARGET_OSX || 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 Expand Up @@ -2285,17 +2293,23 @@ private int GetLocaleInfoCore(LocaleNumberData type)
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return 0;

#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}

private int GetLocaleInfoCoreUserOverride(LocaleNumberData type)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return 0;

#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}

private string GetLocaleInfoCoreUserOverride(LocaleStringData type)
Expand Down Expand Up @@ -2342,8 +2356,12 @@ private int[] GetLocaleInfoCoreUserOverride(LocaleGroupingData type)
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return null!;

// TODO hybrid mode implement groupingdata
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}

/// <remarks>
Expand Down
4 changes: 4 additions & 0 deletions src/native/libs/System.Globalization.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ static const Entry s_globalizationNative[] =
#ifdef __APPLE__
DllImportEntry(GlobalizationNative_GetLocaleNameNative)
DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative)
DllImportEntry(GlobalizationNative_GetLocaleInfoIntNative)
DllImportEntry(GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative)
DllImportEntry(GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative)
DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative)
#endif
};

Expand Down
Loading