// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; using System.Text; namespace System.Globalization { internal partial class CultureData { // ICU constants private const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value private const int ICU_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name private const string ICU_COLLATION_KEYWORD = "@collation="; /// /// 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. /// private unsafe bool InitCultureData() { Debug.Assert(_sRealName != null); Debug.Assert(!GlobalizationMode.Invariant); string realNameBuffer = _sRealName; // Basic validation if (realNameBuffer.Contains('@')) { return false; // don't allow ICU variants to come in directly } // Replace _ (alternate sort) with @collation= for ICU ReadOnlySpan alternateSortName = default; int index = realNameBuffer.IndexOf('_'); if (index > 0) { if (index >= (realNameBuffer.Length - 1) // must have characters after _ || realNameBuffer.IndexOf('_', index + 1) >= 0) // only one _ allowed { return false; // fail } alternateSortName = realNameBuffer.AsSpan(index + 1); realNameBuffer = string.Concat(realNameBuffer.AsSpan(0, index), ICU_COLLATION_KEYWORD, alternateSortName); } // Get the locale name from ICU if (!GetLocaleName(realNameBuffer, out _sWindowsName)) { return false; // fail } // Replace the ICU collation keyword with an _ Debug.Assert(_sWindowsName != null); index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal); if (index >= 0) { _sName = string.Concat(_sWindowsName.AsSpan(0, index), "_", alternateSortName); } else { _sName = _sWindowsName; } _sRealName = _sName; _iLanguage = LCID; if (_iLanguage == 0) { _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED; } _bNeutral = TwoLetterISOCountryName.Length == 0; _sSpecificCulture = _bNeutral ? LocaleData.GetSpecificCultureName(_sRealName) : _sRealName; // Remove the sort from sName unless custom culture if (index > 0 && !_bNeutral && !IsCustomCultureId(_iLanguage)) { _sName = _sWindowsName.Substring(0, index); } return true; } internal static unsafe bool GetLocaleName(string localeName, out string? windowsName) { // Get the locale name from ICU char* buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; if (!Interop.Globalization.GetLocaleName(localeName, buffer, ICU_ULOC_FULLNAME_CAPACITY)) { windowsName = null; return false; // fail } // Success - use the locale name returned which may be different than realNameBuffer (casing) windowsName = new string(buffer); // the name passed to subsequent ICU calls return true; } internal static unsafe bool GetDefaultLocaleName(out string? windowsName) { // Get the default (system) locale name from ICU char* buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; if (!Interop.Globalization.GetDefaultLocaleName(buffer, ICU_ULOC_FULLNAME_CAPACITY)) { windowsName = null; return false; // fail } // Success - use the locale name returned which may be different than realNameBuffer (casing) windowsName = new string(buffer); // the name passed to subsequent ICU calls return true; } private string GetLocaleInfo(LocaleStringData type) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already"); return GetLocaleInfo(_sWindowsName, 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 unsafe string GetLocaleInfo(string localeName, LocaleStringData type) { Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null"); switch (type) { case LocaleStringData.NegativeInfinitySymbol: // not an equivalent in ICU; prefix the PositiveInfinitySymbol with NegativeSign return GetLocaleInfo(localeName, LocaleStringData.NegativeSign) + GetLocaleInfo(localeName, LocaleStringData.PositiveInfinitySymbol); } char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY]; bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); if (!result) { // Failed, just use empty string Debug.Fail("[CultureData.GetLocaleInfo(LocaleStringData)] Failed"); return string.Empty; } return new string(buffer); } private int GetLocaleInfo(LocaleNumberData type) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(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; } int value = 0; bool result = Interop.Globalization.GetLocaleInfoInt(_sWindowsName, (uint)type, ref value); if (!result) { // Failed, just use 0 Debug.Fail("[CultureData.GetLocaleInfo(LocaleNumberData)] failed"); } return value; } private int[] GetLocaleInfo(LocaleGroupingData type) { Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already"); int primaryGroupingSize = 0; int secondaryGroupingSize = 0; bool result = Interop.Globalization.GetLocaleInfoGroupingSizes(_sWindowsName, (uint)type, ref primaryGroupingSize, ref secondaryGroupingSize); if (!result) { Debug.Fail("[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed"); } if (secondaryGroupingSize == 0) { return new int[] { primaryGroupingSize }; } return new int[] { primaryGroupingSize, secondaryGroupingSize }; } private string GetTimeFormatString() => GetTimeFormatString(shortFormat: false); private unsafe string GetTimeFormatString(bool shortFormat) { 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) { // Failed, just use empty string Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); return string.Empty; } var span = new ReadOnlySpan(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0'))); } private int GetFirstDayOfWeek() => GetLocaleInfo(LocaleNumberData.FirstDayOfWeek); private string[] GetTimeFormats() { string format = GetTimeFormatString(false); return new string[] { format }; } private string[] GetShortTimeFormats() { string format = GetTimeFormatString(true); return new string[] { format }; } private static CultureData? GetCultureDataFromRegionName(string? regionName) { // no support to lookup by region name, other than the hard-coded list in CultureData return null; } private static string GetLanguageDisplayName(string cultureName) { return new CultureInfo(cultureName)._cultureData.GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName); } private static string? GetRegionDisplayName() { // use the fallback which is to return NativeName return null; } private static string ConvertIcuTimeFormatString(ReadOnlySpan icuFormatString) { Debug.Assert(icuFormatString.Length < ICU_ULOC_FULLNAME_CAPACITY); Span result = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; bool amPmAdded = false; int resultPos = 0; for (int i = 0; i < icuFormatString.Length; i++) { switch (icuFormatString[i]) { case '\'': result[resultPos++] = icuFormatString[i++]; while (i < icuFormatString.Length) { char current = icuFormatString[i++]; result[resultPos++] = current; if (current == '\'') { break; } } break; case ':': case '.': case 'H': case 'h': case 'm': case 's': result[resultPos++] = icuFormatString[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(); } private static string? LCIDToLocaleName(int culture) { Debug.Assert(!GlobalizationMode.Invariant); return LocaleData.LCIDToLocaleName(culture); } private static int LocaleNameToLCID(string cultureName) { Debug.Assert(!GlobalizationMode.Invariant); int lcid = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.Lcid); return lcid == -1 ? CultureInfo.LOCALE_CUSTOM_UNSPECIFIED : lcid; } private static int GetAnsiCodePage(string cultureName) { int ansiCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.AnsiCodePage); return ansiCodePage == -1 ? CultureData.Invariant.ANSICodePage : ansiCodePage; } private static int GetOemCodePage(string cultureName) { int oemCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.OemCodePage); return oemCodePage == -1 ? CultureData.Invariant.OEMCodePage : oemCodePage; } private static int GetMacCodePage(string cultureName) { int macCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.MacCodePage); return macCodePage == -1 ? CultureData.Invariant.MacCodePage : macCodePage; } private static int GetEbcdicCodePage(string cultureName) { int ebcdicCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.EbcdicCodePage); return ebcdicCodePage == -1 ? CultureData.Invariant.EBCDICCodePage : ebcdicCodePage; } private static int GetGeoId(string cultureName) { int geoId = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.GeoId); return geoId == -1 ? CultureData.Invariant.GeoId : geoId; } private static int GetDigitSubstitution(string cultureName) { int digitSubstitution = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.DigitSubstitution); return digitSubstitution == -1 ? (int) DigitShapes.None : digitSubstitution; } private static string GetThreeLetterWindowsLanguageName(string cultureName) { return LocaleData.GetThreeLetterWindowsLanguageName(cultureName) ?? "ZZZ" /* default lang name */; } private static CultureInfo[] EnumCultures(CultureTypes types) { Debug.Assert(!GlobalizationMode.Invariant); if ((types & (CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)) == 0) { return Array.Empty(); } int bufferLength = Interop.Globalization.GetLocales(null, 0); if (bufferLength <= 0) { return Array.Empty(); } char [] chars = new char[bufferLength]; bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); if (bufferLength <= 0) { return Array.Empty(); } bool enumNeutrals = (types & CultureTypes.NeutralCultures) != 0; bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0; List list = new List(); if (enumNeutrals) { list.Add(CultureInfo.InvariantCulture); } int index = 0; while (index < bufferLength) { int length = (int) chars[index++]; if (index + length <= bufferLength) { CultureInfo ci = CultureInfo.GetCultureInfo(new string(chars, index, length)); if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture)) { list.Add(ci); } } index += length; } return list.ToArray(); } private static string GetConsoleFallbackName(string cultureName) { return LocaleData.GetConsoleUICulture(cultureName); } internal bool IsWin32Installed => false; internal bool IsReplacementCulture => false; } }