|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
namespace System.Globalization
{
/// <summary>
/// List of culture data
/// Note the we cache overrides.
/// Note that localized names (resource names) aren't available from here.
/// </summary>
/// <remarks>
/// Our names are a tad confusing.
///
/// sWindowsName -- The name that windows thinks this culture is, ie:
/// en-US if you pass in en-US
/// de-DE_phoneb if you pass in de-DE_phoneb
/// fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
/// fj if you pass in fj (neutral, post-Windows 7 machine)
///
/// sRealName -- The name you used to construct the culture, in pretty form
/// en-US if you pass in EN-us
/// en if you pass in en
/// de-DE_phoneb if you pass in de-DE_phoneb
///
/// sSpecificCulture -- The specific culture for this culture
/// en-US for en-US
/// en-US for en
/// de-DE_phoneb for alt sort
/// fj-FJ for fj (neutral)
///
/// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
/// en-US if you pass in en-US
/// en if you pass in en
/// de-DE if you pass in de-DE_phoneb
/// </remarks>
internal sealed partial class CultureData
{
private const int LocaleNameMaxLength = 85;
private const int undef = -1;
// Override flag
private string _sRealName = null!; // Name you passed in (ie: en-US, en, or de-DE_phoneb). Initialized by helper called during initialization.
private string? _sWindowsName; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
// Identity
private string? _sName; // locale name (ie: en-us, NO sort info, but could be neutral)
private string? _sParent; // Parent name (which may be a custom locale/culture)
private string? _sEnglishDisplayName; // English pretty name for this locale
private string? _sNativeDisplayName; // Native pretty name for this locale
private string? _sSpecificCulture; // The culture name to be used in CultureInfo.CreateSpecificCulture(), en-US form if neutral, sort name if sort
// Language
private string? _sISO639Language; // ISO 639 Language Name
private string? _sISO639Language2; // ISO 639 Language Name
private string? _sEnglishLanguage; // English name for this language
private string? _sNativeLanguage; // Native name of this language
private string? _sAbbrevLang; // abbreviated language name (Windows Language Name) ex: ENU
private string? _sConsoleFallbackName; // The culture name for the console fallback UI culture
private int _iInputLanguageHandle = undef; // input language handle
// Region
private string? _sRegionName; // (RegionInfo)
private string? _sLocalizedCountry; // localized country name
private string? _sEnglishCountry; // english country name (RegionInfo)
private string? _sNativeCountry; // native country name
private string? _sISO3166CountryName; // ISO 3166 (RegionInfo), ie: US
private string? _sISO3166CountryName2; // 3 char ISO 3166 country name 2 2(RegionInfo) ex: USA (ISO)
private int _iGeoId = undef; // GeoId
// Numbers
private string? _sPositiveSign; // (user can override) positive sign
private string? _sNegativeSign; // (user can override) negative sign
// (nfi populates these 5, don't have to be = undef)
private int _iDigits; // (user can override) number of fractional digits
private int _iNegativeNumber; // (user can override) negative number format
private int[]? _waGrouping; // (user can override) grouping of digits
private string? _sDecimalSeparator; // (user can override) decimal separator
private string? _sThousandSeparator; // (user can override) thousands separator
private string? _sNaN; // Not a Number
private string? _sPositiveInfinity; // + Infinity
private string? _sNegativeInfinity; // - Infinity
// Percent
private int _iNegativePercent = undef; // Negative Percent (0-3)
private int _iPositivePercent = undef; // Positive Percent (0-11)
private string? _sPercent; // Percent (%) symbol
private string? _sPerMille; // PerMille symbol
// Currency
private string? _sCurrency; // (user can override) local monetary symbol
private string? _sIntlMonetarySymbol; // international monetary symbol (RegionInfo)
private string? _sEnglishCurrency; // English name for this currency
private string? _sNativeCurrency; // Native name for this currency
// (nfi populates these 4, don't have to be = undef)
private int _iCurrencyDigits; // (user can override) # local monetary fractional digits
private int _iCurrency; // (user can override) positive currency format
private int _iNegativeCurrency; // (user can override) negative currency format
private int[]? _waMonetaryGrouping; // (user can override) monetary grouping of digits
private string? _sMonetaryDecimal; // (user can override) monetary decimal separator
private string? _sMonetaryThousand; // (user can override) monetary thousands separator
// Misc
private int _iMeasure = undef; // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
private string? _sListSeparator; // (user can override) list separator
// Time
private string? _sAM1159; // (user can override) AM designator
private string? _sPM2359; // (user can override) PM designator
private string? _sTimeSeparator;
private string[]? _saLongTimes; // (user can override) time format
private string[]? _saShortTimes; // (user can override) short time format
// Calendar specific data
private int _iFirstDayOfWeek = undef; // (user can override) first day of week (gregorian really)
private int _iFirstWeekOfYear = undef; // (user can override) first week of year (gregorian really)
private CalendarId[]? _waCalendars; // all available calendar type(s). The first one is the default calendar
// Store for specific data about each calendar
private CalendarData?[]? _calendars; // Store for specific calendar data
// Text information
private int _iReadingLayout = undef; // Reading layout data
// 0 - Left to right (eg en-US)
// 1 - Right to left (eg arabic locales)
// 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
// 3 - Vertical top to bottom with columns proceeding to the right
// CoreCLR depends on this even though its not exposed publicly.
private int _iDefaultAnsiCodePage = undef; // default ansi code page ID (ACP)
private int _iDefaultOemCodePage = undef; // default oem code page ID (OCP or OEM)
private int _iDefaultMacCodePage = undef; // default macintosh code page
private int _iDefaultEbcdicCodePage = undef; // default EBCDIC code page
private int _iLanguage; // locale ID (0409) - NO sort information
private bool _bUseOverrides; // use user overrides? this depends on user setting and if is user default locale.
private bool _bUseOverridesUserSetting; // the setting the user requested for.
private bool _bNeutral; // Flags for the culture (ie: neutral or not right now)
/// <summary>
/// Region Name to Culture Name mapping table
/// </summary>
/// <remarks>
/// Using a property so we avoid creating the dictionary until we need it
/// </remarks>
private static Dictionary<string, string> RegionNames =>
s_regionNames ??=
new Dictionary<string, string>(255, StringComparer.OrdinalIgnoreCase)
{
{ "001", "en-001" },
{ "029", "en-029" },
{ "150", "en-150" },
{ "419", "es-419" },
{ "AD", "ca-AD" },
{ "AE", "ar-AE" },
{ "AF", "prs-AF" },
{ "AG", "en-AG" },
{ "AI", "en-AI" },
{ "AL", "sq-AL" },
{ "AM", "hy-AM" },
{ "AO", "pt-AO" },
{ "AQ", "en-AQ" },
{ "AR", "es-AR" },
{ "AS", "en-AS" },
{ "AT", "de-AT" },
{ "AU", "en-AU" },
{ "AW", "nl-AW" },
{ "AX", "sv-AX" },
{ "AZ", "az-Cyrl-AZ" },
{ "BA", "bs-Latn-BA" },
{ "BB", "en-BB" },
{ "BD", "bn-BD" },
{ "BE", "nl-BE" },
{ "BF", "fr-BF" },
{ "BG", "bg-BG" },
{ "BH", "ar-BH" },
{ "BI", "rn-BI" },
{ "BJ", "fr-BJ" },
{ "BL", "fr-BL" },
{ "BM", "en-BM" },
{ "BN", "ms-BN" },
{ "BO", "es-BO" },
{ "BQ", "nl-BQ" },
{ "BR", "pt-BR" },
{ "BS", "en-BS" },
{ "BT", "dz-BT" },
{ "BV", "nb-BV" },
{ "BW", "en-BW" },
{ "BY", "be-BY" },
{ "BZ", "en-BZ" },
{ "CA", "en-CA" },
{ "CC", "en-CC" },
{ "CD", "fr-CD" },
{ "CF", "sg-CF" },
{ "CG", "fr-CG" },
{ "CH", "it-CH" },
{ "CI", "fr-CI" },
{ "CK", "en-CK" },
{ "CL", "es-CL" },
{ "CM", "fr-CM" },
{ "CN", "zh-CN" },
{ "CO", "es-CO" },
{ "CR", "es-CR" },
{ "CS", "sr-Cyrl-CS" },
{ "CU", "es-CU" },
{ "CV", "pt-CV" },
{ "CW", "nl-CW" },
{ "CX", "en-CX" },
{ "CY", "el-CY" },
{ "CZ", "cs-CZ" },
{ "DE", "de-DE" },
{ "DJ", "fr-DJ" },
{ "DK", "da-DK" },
{ "DM", "en-DM" },
{ "DO", "es-DO" },
{ "DZ", "ar-DZ" },
{ "EC", "es-EC" },
{ "EE", "et-EE" },
{ "EG", "ar-EG" },
{ "ER", "tig-ER" },
{ "ES", "es-ES" },
{ "ET", "am-ET" },
{ "FI", "fi-FI" },
{ "FJ", "en-FJ" },
{ "FK", "en-FK" },
{ "FM", "en-FM" },
{ "FO", "fo-FO" },
{ "FR", "fr-FR" },
{ "GA", "fr-GA" },
{ "GB", "en-GB" },
{ "GD", "en-GD" },
{ "GE", "ka-GE" },
{ "GF", "fr-GF" },
{ "GG", "en-GG" },
{ "GH", "en-GH" },
{ "GI", "en-GI" },
{ "GL", "kl-GL" },
{ "GM", "en-GM" },
{ "GN", "fr-GN" },
{ "GP", "fr-GP" },
{ "GQ", "es-GQ" },
{ "GR", "el-GR" },
{ "GS", "en-GS" },
{ "GT", "es-GT" },
{ "GU", "en-GU" },
{ "GW", "pt-GW" },
{ "GY", "en-GY" },
{ "HK", "zh-HK" },
{ "HM", "en-HM" },
{ "HN", "es-HN" },
{ "HR", "hr-HR" },
{ "HT", "fr-HT" },
{ "HU", "hu-HU" },
{ "ID", "id-ID" },
{ "IE", "en-IE" },
{ "IL", "he-IL" },
{ "IM", "gv-IM" },
{ "IN", "hi-IN" },
{ "IO", "en-IO" },
{ "IQ", "ar-IQ" },
{ "IR", "fa-IR" },
{ "IS", "is-IS" },
{ "IT", "it-IT" },
{ "IV", "" },
{ "JE", "en-JE" },
{ "JM", "en-JM" },
{ "JO", "ar-JO" },
{ "JP", "ja-JP" },
{ "KE", "sw-KE" },
{ "KG", "ky-KG" },
{ "KH", "km-KH" },
{ "KI", "en-KI" },
{ "KM", "ar-KM" },
{ "KN", "en-KN" },
{ "KP", "ko-KP" },
{ "KR", "ko-KR" },
{ "KW", "ar-KW" },
{ "KY", "en-KY" },
{ "KZ", "kk-KZ" },
{ "LA", "lo-LA" },
{ "LB", "ar-LB" },
{ "LC", "en-LC" },
{ "LI", "de-LI" },
{ "LK", "si-LK" },
{ "LR", "en-LR" },
{ "LS", "st-LS" },
{ "LT", "lt-LT" },
{ "LU", "lb-LU" },
{ "LV", "lv-LV" },
{ "LY", "ar-LY" },
{ "MA", "ar-MA" },
{ "MC", "fr-MC" },
{ "MD", "ro-MD" },
{ "ME", "sr-Latn-ME" },
{ "MF", "fr-MF" },
{ "MG", "mg-MG" },
{ "MH", "en-MH" },
{ "MK", "mk-MK" },
{ "ML", "fr-ML" },
{ "MM", "my-MM" },
{ "MN", "mn-MN" },
{ "MO", "zh-MO" },
{ "MP", "en-MP" },
{ "MQ", "fr-MQ" },
{ "MR", "ar-MR" },
{ "MS", "en-MS" },
{ "MT", "mt-MT" },
{ "MU", "en-MU" },
{ "MV", "dv-MV" },
{ "MW", "en-MW" },
{ "MX", "es-MX" },
{ "MY", "ms-MY" },
{ "MZ", "pt-MZ" },
{ "NA", "en-NA" },
{ "NC", "fr-NC" },
{ "NE", "fr-NE" },
{ "NF", "en-NF" },
{ "NG", "ig-NG" },
{ "NI", "es-NI" },
{ "NL", "nl-NL" },
{ "NO", "nn-NO" },
{ "NP", "ne-NP" },
{ "NR", "en-NR" },
{ "NU", "en-NU" },
{ "NZ", "en-NZ" },
{ "OM", "ar-OM" },
{ "PA", "es-PA" },
{ "PE", "es-PE" },
{ "PF", "fr-PF" },
{ "PG", "en-PG" },
{ "PH", "en-PH" },
{ "PK", "ur-PK" },
{ "PL", "pl-PL" },
{ "PM", "fr-PM" },
{ "PN", "en-PN" },
{ "PR", "es-PR" },
{ "PS", "ar-PS" },
{ "PT", "pt-PT" },
{ "PW", "en-PW" },
{ "PY", "es-PY" },
{ "QA", "ar-QA" },
{ "RE", "fr-RE" },
{ "RO", "ro-RO" },
{ "RS", "sr-Latn-RS" },
{ "RU", "ru-RU" },
{ "RW", "rw-RW" },
{ "SA", "ar-SA" },
{ "SB", "en-SB" },
{ "SC", "fr-SC" },
{ "SD", "ar-SD" },
{ "SE", "sv-SE" },
{ "SG", "zh-SG" },
{ "SH", "en-SH" },
{ "SI", "sl-SI" },
{ "SJ", "nb-SJ" },
{ "SK", "sk-SK" },
{ "SL", "en-SL" },
{ "SM", "it-SM" },
{ "SN", "wo-SN" },
{ "SO", "so-SO" },
{ "SR", "nl-SR" },
{ "SS", "en-SS" },
{ "ST", "pt-ST" },
{ "SV", "es-SV" },
{ "SX", "nl-SX" },
{ "SY", "ar-SY" },
{ "SZ", "ss-SZ" },
{ "TC", "en-TC" },
{ "TD", "fr-TD" },
{ "TF", "fr-TF" },
{ "TG", "fr-TG" },
{ "TH", "th-TH" },
{ "TJ", "tg-Cyrl-TJ" },
{ "TK", "en-TK" },
{ "TL", "pt-TL" },
{ "TM", "tk-TM" },
{ "TN", "ar-TN" },
{ "TO", "to-TO" },
{ "TR", "tr-TR" },
{ "TT", "en-TT" },
{ "TV", "en-TV" },
{ "TW", "zh-TW" },
{ "TZ", "sw-TZ" },
{ "UA", "uk-UA" },
{ "UG", "sw-UG" },
{ "UM", "en-UM" },
{ "US", "en-US" },
{ "UY", "es-UY" },
{ "UZ", "uz-Cyrl-UZ" },
{ "VA", "it-VA" },
{ "VC", "en-VC" },
{ "VE", "es-VE" },
{ "VG", "en-VG" },
{ "VI", "en-VI" },
{ "VN", "vi-VN" },
{ "VU", "fr-VU" },
{ "WF", "fr-WF" },
{ "WS", "en-WS" },
{ "XK", "sq-XK" },
{ "YE", "ar-YE" },
{ "YT", "fr-YT" },
{ "ZA", "af-ZA" },
{ "ZM", "en-ZM" },
{ "ZW", "en-ZW" }
};
// Cache of regions we've already looked up
private static volatile Dictionary<string, CultureData>? s_cachedRegions;
private static Dictionary<string, string>? s_regionNames;
/// <summary>
/// The culture name to use to interop with the underlying native globalization libraries like ICU or Windows NLS APIs.
/// For example, we can have the name de_DE@collation=phonebook when using ICU for the German culture de-DE with the phonebook sorting behavior.
/// </summary>
internal string? InteropName => _sWindowsName;
internal static CultureData? GetCultureDataForRegion(string? cultureName, bool useUserOverride)
{
// First do a shortcut for Invariant
if (string.IsNullOrEmpty(cultureName))
{
return Invariant;
}
// First check if GetCultureData() can find it (ie: its a real culture)
CultureData? retVal = GetCultureData(cultureName, useUserOverride);
if (retVal != null && !retVal.IsNeutralCulture)
{
return retVal;
}
// If it was neutral remember that so that RegionInfo() can throw the right exception
CultureData? neutral = retVal;
// Try the hash table next
string hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
Dictionary<string, CultureData>? tempHashTable = s_cachedRegions;
if (tempHashTable == null)
{
// No table yet, make a new one
var newTable = new Dictionary<string, CultureData>();
tempHashTable = Interlocked.CompareExchange(ref s_cachedRegions, newTable, null) ?? newTable;
}
else
{
// Check the hash table
lock (tempHashTable)
{
tempHashTable.TryGetValue(hashName, out retVal);
}
if (retVal != null)
{
return retVal;
}
}
// Not found in the hash table, look it up the hard way
// If not a valid mapping from the registry we'll have to try the hard coded table
if (retVal == null || retVal.IsNeutralCulture)
{
// Not a valid mapping, try the hard coded table
if (RegionNames.TryGetValue(cultureName, out string? name))
{
// Make sure we can get culture data for it
retVal = GetCultureData(name, useUserOverride);
}
}
// If not found in the hard coded table we'll have to find a culture that works for us
if (!GlobalizationMode.Invariant && (retVal == null || retVal.IsNeutralCulture))
{
retVal = GlobalizationMode.UseNls ? NlsGetCultureDataFromRegionName(cultureName) : IcuGetCultureDataFromRegionName();
}
// If we found one we can use, then cache it for next time
if (retVal != null && !retVal.IsNeutralCulture)
{
// first add it to the cache
lock (tempHashTable)
{
tempHashTable[hashName] = retVal;
}
}
else
{
// Unable to find a matching culture/region, return null or neutral
// (regionInfo throws a more specific exception on neutrals)
retVal = neutral;
}
// Return the found culture to use, null, or the neutral culture.
return retVal;
}
// Clear our internal caches
internal static void ClearCachedData()
{
s_cachedCultures = null;
s_cachedRegions = null;
}
internal static CultureInfo[] GetCultures(CultureTypes types)
{
// Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
#pragma warning disable 618
// Validate flags
if ((int)types <= 0 || ((int)types & (int)~(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures |
CultureTypes.InstalledWin32Cultures | CultureTypes.UserCustomCulture |
CultureTypes.ReplacementCultures | CultureTypes.WindowsOnlyCultures |
CultureTypes.FrameworkCultures)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(types),
SR.Format(SR.ArgumentOutOfRange_Range, CultureTypes.NeutralCultures, CultureTypes.FrameworkCultures));
}
// We have deprecated CultureTypes.FrameworkCultures.
// When this enum is used, we will enumerate Whidbey framework cultures (for compatibility).
// We have deprecated CultureTypes.WindowsOnlyCultures.
// When this enum is used, we will return an empty array for this enum.
if ((types & CultureTypes.WindowsOnlyCultures) != 0)
{
// Remove the enum as it is an no-op.
types &= (~CultureTypes.WindowsOnlyCultures);
}
if (GlobalizationMode.Invariant)
{
// in invariant mode we always return invariant culture only from the enumeration
return [new CultureInfo("")];
}
#pragma warning restore 618
return GlobalizationMode.UseNls ? NlsEnumCultures(types) : IcuEnumCultures(types);
}
private static CultureData CreateCultureWithInvariantData()
{
// Make a new culturedata
CultureData invariant = new CultureData();
// Basics
// Note that we override the resources since this IS NOT supposed to change (by definition)
invariant._bUseOverrides = false;
invariant._bUseOverridesUserSetting = false;
invariant._sRealName = ""; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
invariant._sWindowsName = ""; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
// Identity
invariant._sName = ""; // locale name (ie: en-us)
invariant._sParent = ""; // Parent name (which may be a custom locale/culture)
invariant._bNeutral = false; // Flags for the culture (ie: neutral or not right now)
invariant._sEnglishDisplayName = "Invariant Language (Invariant Country)"; // English pretty name for this locale
invariant._sNativeDisplayName = "Invariant Language (Invariant Country)"; // Native pretty name for this locale
invariant._sSpecificCulture = ""; // The culture name to be used in CultureInfo.CreateSpecificCulture()
// Language
invariant._sISO639Language = "iv"; // ISO 639 Language Name
invariant._sISO639Language2 = "ivl"; // 3 char ISO 639 lang name 2
invariant._sEnglishLanguage = "Invariant Language"; // English name for this language
invariant._sNativeLanguage = "Invariant Language"; // Native name of this language
invariant._sAbbrevLang = "IVL"; // abbreviated language name (Windows Language Name)
invariant._sConsoleFallbackName = ""; // The culture name for the console fallback UI culture
invariant._iInputLanguageHandle = 0x07F; // input language handle
// Region
invariant._sRegionName = "IV"; // (RegionInfo)
invariant._sEnglishCountry = "Invariant Country"; // english country name (RegionInfo)
invariant._sNativeCountry = "Invariant Country"; // native country name (Windows Only)
invariant._sISO3166CountryName = "IV"; // (RegionInfo), ie: US
invariant._sISO3166CountryName2 = "ivc"; // 3 char ISO 3166 country name 2 2(RegionInfo)
invariant._iGeoId = 244; // GeoId (Windows Only)
// Numbers
invariant._sPositiveSign = "+"; // positive sign
invariant._sNegativeSign = "-"; // negative sign
invariant._iDigits = 2; // number of fractional digits
invariant._iNegativeNumber = 1; // negative number format
invariant._waGrouping = [3]; // grouping of digits
invariant._sDecimalSeparator = "."; // decimal separator
invariant._sThousandSeparator = ","; // thousands separator
invariant._sNaN = "NaN"; // Not a Number
invariant._sPositiveInfinity = "Infinity"; // + Infinity
invariant._sNegativeInfinity = "-Infinity"; // - Infinity
// Percent
invariant._iNegativePercent = 0; // Negative Percent (0-3)
invariant._iPositivePercent = 0; // Positive Percent (0-11)
invariant._sPercent = "%"; // Percent (%) symbol
invariant._sPerMille = "\x2030"; // PerMille symbol
// Currency
invariant._sCurrency = "\x00a4"; // local monetary symbol: for international monetary symbol
invariant._sIntlMonetarySymbol = "XDR"; // international monetary symbol (RegionInfo)
invariant._sEnglishCurrency = "International Monetary Fund"; // English name for this currency (Windows Only)
invariant._sNativeCurrency = "International Monetary Fund"; // Native name for this currency (Windows Only)
invariant._iCurrencyDigits = 2; // # local monetary fractional digits
invariant._iCurrency = 0; // positive currency format
invariant._iNegativeCurrency = 0; // negative currency format
invariant._waMonetaryGrouping = [3]; // monetary grouping of digits
invariant._sMonetaryDecimal = "."; // monetary decimal separator
invariant._sMonetaryThousand = ","; // monetary thousands separator
// Misc
invariant._iMeasure = 0; // system of measurement 0=metric, 1=US (RegionInfo)
invariant._sListSeparator = ","; // list separator
// Time
invariant._sTimeSeparator = ":";
invariant._sAM1159 = "AM"; // AM designator
invariant._sPM2359 = "PM"; // PM designator
invariant._saLongTimes = ["HH:mm:ss"]; // time format
invariant._saShortTimes = ["HH:mm", "hh:mm tt", "H:mm", "h:mm tt"]; // short time format
// Calendar specific data
invariant._iFirstDayOfWeek = 0; // first day of week
invariant._iFirstWeekOfYear = 0; // first week of year
// Calendar information is expensive size-wise, especially for AOT.
// CalendarData.Invariant is special cased in _waCalendars and _calendars
// accessors and gets initialized lazily.
// invariant._waCalendars
// invariant._calendars
// Text information
invariant._iReadingLayout = 0;
// These are .NET Framework only, not coreclr
invariant._iLanguage = CultureInfo.LOCALE_INVARIANT; // locale ID (0409) - NO sort information
invariant._iDefaultAnsiCodePage = 1252; // default ansi code page ID (ACP)
invariant._iDefaultOemCodePage = 437; // default oem code page ID (OCP or OEM)
invariant._iDefaultMacCodePage = 10000; // default macintosh code page
invariant._iDefaultEbcdicCodePage = 037; // default EBCDIC code page
if (GlobalizationMode.InvariantNoLoad)
{
invariant._sLocalizedCountry = invariant._sNativeCountry;
}
return invariant;
}
/// <summary>
/// Build our invariant information
/// We need an invariant instance, which we build hard-coded
/// </summary>
internal static CultureData Invariant => field ??= CreateCultureWithInvariantData();
// Cache of cultures we've already looked up
private static volatile Dictionary<string, CultureData>? s_cachedCultures;
internal static CultureData? GetCultureData(string? cultureName, bool useUserOverride)
{
// The undetermined culture name "und" is not a real culture, but it resolves to the invariant culture because ICU typically normalizes it to an empty string.
const string UndeterminedCultureName = "und";
// First do a shortcut for Invariant
if (string.IsNullOrEmpty(cultureName) || cultureName.Equals(UndeterminedCultureName, StringComparison.OrdinalIgnoreCase))
{
return Invariant;
}
if (GlobalizationMode.PredefinedCulturesOnly)
{
if (GlobalizationMode.Invariant || (GlobalizationMode.UseNls ? !NlsIsEnsurePredefinedLocaleName(cultureName) : !IcuIsEnsurePredefinedLocaleName(cultureName)))
return null;
}
// Try the hash table first
string hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
Dictionary<string, CultureData>? tempHashTable = s_cachedCultures;
if (tempHashTable == null)
{
// No table yet, make a new one
var newTable = new Dictionary<string, CultureData>();
tempHashTable = Interlocked.CompareExchange(ref s_cachedCultures, newTable, null) ?? newTable;
}
else
{
// Check the hash table
bool ret;
CultureData? retVal;
lock (tempHashTable)
{
ret = tempHashTable.TryGetValue(hashName, out retVal);
}
if (ret && retVal != null)
{
return retVal;
}
}
// Not found in the hash table, need to see if we can build one that works for us
CultureData? culture = CreateCultureData(cultureName, useUserOverride);
if (culture == null)
{
return null;
}
// Found one, add it to the cache
lock (tempHashTable)
{
tempHashTable[hashName] = culture;
}
return culture;
}
private static string NormalizeCultureName(string name, out bool isNeutralName)
{
isNeutralName = true;
int i = 0;
if (name.Length > LocaleNameMaxLength)
{
// Theoretically we shouldn't hit this exception.
throw new ArgumentException(SR.Format(SR.Argument_InvalidId, nameof(name)));
}
Span<char> normalizedName = stackalloc char[name.Length];
bool changed = false;
while (i < name.Length && name[i] != '-' && name[i] != '_')
{
if (char.IsAsciiLetterUpper(name[i]))
{
// lowercase characters before '-'
normalizedName[i] = (char)(((int)name[i]) + 0x20);
changed = true;
}
else
{
normalizedName[i] = name[i];
}
i++;
}
if (i < name.Length)
{
// this is not perfect to detect the non neutral cultures but it is good enough when we are running in invariant mode
isNeutralName = false;
}
while (i < name.Length)
{
if (char.IsAsciiLetterLower(name[i]))
{
normalizedName[i] = (char)(((int)name[i]) - 0x20);
changed = true;
}
else
{
normalizedName[i] = name[i];
}
i++;
}
if (changed)
{
return new string(normalizedName);
}
return name;
}
private static CultureData? CreateCultureData(string cultureName, bool useUserOverride)
{
if (GlobalizationMode.Invariant)
{
if (cultureName.Length > LocaleNameMaxLength || !CultureInfo.VerifyCultureName(cultureName, false))
{
return null;
}
CultureData cd = CreateCultureWithInvariantData();
cd._sName = NormalizeCultureName(cultureName, out cd._bNeutral);
cd._bUseOverridesUserSetting = useUserOverride;
cd._sRealName = cd._sName;
cd._sWindowsName = cd._sName;
cd._iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
return cd;
}
if (cultureName.Length == 1 && (cultureName[0] == 'C' || cultureName[0] == 'c'))
{
// Always map the "C" locale to Invariant to avoid mapping it to en_US_POSIX on Linux because POSIX
// locale collation doesn't support case insensitive comparisons.
// We do the same mapping on Windows for the sake of consistency.
return Invariant;
}
CultureData culture = new CultureData();
culture._sRealName = cultureName;
culture._bUseOverridesUserSetting = useUserOverride;
// Ask native code if that one's real, populate _sWindowsName
if (!culture.InitCultureDataCore() && !culture.InitCompatibilityCultureData())
{
return null;
}
#if TARGET_BROWSER
culture.JSInitLocaleInfo();
#endif
// We need _sWindowsName to be initialized to know if we're using overrides.
culture.InitUserOverride(useUserOverride);
return culture;
}
private bool InitCompatibilityCultureData()
{
// for compatibility handle the deprecated ids: zh-chs, zh-cht
string cultureName = _sRealName;
string fallbackCultureName;
string realCultureName;
switch (AnsiToLower(cultureName))
{
case "zh-chs":
fallbackCultureName = "zh-Hans";
realCultureName = "zh-CHS";
break;
case "zh-cht":
fallbackCultureName = "zh-Hant";
realCultureName = "zh-CHT";
break;
default:
return false;
}
_sRealName = fallbackCultureName;
if (!InitCultureDataCore())
{
return false;
}
// fixup our data
_sName = realCultureName; // the name that goes back to the user
_sParent = fallbackCultureName;
return true;
}
/// We'd rather people use the named version since this doesn't allow custom locales
internal static CultureData GetCultureData(int culture, bool bUseUserOverride)
{
CultureData? retVal = null;
if (culture == CultureInfo.LOCALE_INVARIANT)
{
return Invariant;
}
if (GlobalizationMode.Invariant)
{
if (!GlobalizationMode.PredefinedCulturesOnly)
{
if (culture is < 1 or > 0xf_ffff)
{
throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
}
return Invariant;
}
// LCID is not supported in the InvariantMode
throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupportedInInvariantMode);
}
// Convert the lcid to a name, then use that
string? localeName = LCIDToLocaleName(culture);
if (!string.IsNullOrEmpty(localeName))
{
// Valid name, use it
retVal = GetCultureData(localeName, bUseUserOverride);
}
// If not successful, throw
if (retVal == null)
{
throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
}
// Return the one we found
return retVal;
}
/// <summary>
/// The real name used to construct the locale (ie: de-DE_phoneb)
/// </summary>
internal string CultureName
{
get
{
Debug.Assert(_sRealName != null, "[CultureData.CultureName] Expected _sRealName to be populated by already");
// since windows doesn't know about zh-CHS and zh-CHT,
// we leave sRealName == zh-Hanx but we still need to
// pretend that it was zh-CHX.
return _sName is "zh-CHS" or "zh-CHT" ? _sName : _sRealName;
}
}
/// <summary>
/// Did the user request to use overrides?
/// </summary>
internal bool UseUserOverride => _bUseOverridesUserSetting;
/// <summary>
/// locale name (ie: de-DE, NO sort information)
/// </summary>
internal string Name => _sName ?? string.Empty;
// Parent name (which may be a custom locale/culture)
// Ask using the real name, so that we get parents of neutrals
internal string ParentName => _sParent ??= GetLocaleInfoCore(_sRealName, LocaleStringData.ParentName);
// Localized pretty name for this locale (ie: Inglis (estados Unitos))
internal string DisplayName
{
get
{
string? localizedDisplayName = NativeName;
if (!GlobalizationMode.Invariant && Name.Length > 0)
{
try
{
localizedDisplayName = GetLanguageDisplayNameCore(
Name.Equals("zh-CHT", StringComparison.OrdinalIgnoreCase) ? "zh-Hant" :
Name.Equals("zh-CHS", StringComparison.OrdinalIgnoreCase) ? "zh-Hans" :
Name);
}
catch
{
// do nothing
}
// If it hasn't been found (Windows 8 and up), fallback to the system
if (string.IsNullOrEmpty(localizedDisplayName) && IsNeutralCulture)
{
localizedDisplayName = LocalizedLanguageName;
}
}
return localizedDisplayName;
}
}
private string GetLanguageDisplayNameCore(string cultureName) => GlobalizationMode.UseNls ?
NlsGetLanguageDisplayName(cultureName) :
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
GlobalizationMode.Hybrid ?
GetLocaleInfoNative(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name) :
#endif
IcuGetLanguageDisplayName(cultureName);
/// <summary>
/// English pretty name for this locale (ie: English (United States))
/// </summary>
internal string EnglishName
{
get
{
string? englishDisplayName = _sEnglishDisplayName;
if (englishDisplayName == null && !GlobalizationMode.Invariant)
{
// If its neutral use the language name
if (IsNeutralCulture)
{
englishDisplayName = GetLocaleInfoCore(LocaleStringData.EnglishDisplayName);
if (string.IsNullOrEmpty(englishDisplayName))
{
englishDisplayName = EnglishLanguageName;
}
// differentiate the legacy display names
switch (_sName)
{
case "zh-CHS":
case "zh-CHT":
englishDisplayName += " Legacy";
break;
}
}
else
{
englishDisplayName = GetLocaleInfoCore(LocaleStringData.EnglishDisplayName);
// if it isn't found build one:
if (string.IsNullOrEmpty(englishDisplayName))
{
// Our existing names mostly look like:
// "English" + "United States" -> "English (United States)"
// "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
if (EnglishLanguageName.EndsWith(')'))
{
// "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
englishDisplayName = string.Concat(
EnglishLanguageName.AsSpan(0, _sEnglishLanguage!.Length - 1),
", ",
EnglishCountryName,
")");
}
else
{
// "English" + "United States" -> "English (United States)"
englishDisplayName = EnglishLanguageName + " (" + EnglishCountryName + ")";
}
}
}
_sEnglishDisplayName = englishDisplayName;
}
return englishDisplayName!;
}
}
/// <summary>
/// Native pretty name for this locale (ie: Deutsch (Deutschland))
/// </summary>
internal string NativeName
{
get
{
string? nativeDisplayName = _sNativeDisplayName;
if (nativeDisplayName == null && !GlobalizationMode.Invariant)
{
// If its neutral use the language name
if (IsNeutralCulture)
{
nativeDisplayName = GetLocaleInfoCore(LocaleStringData.NativeDisplayName);
if (string.IsNullOrEmpty(nativeDisplayName))
{
nativeDisplayName = NativeLanguageName;
}
// differentiate the legacy display names
switch (_sName)
{
case "zh-CHS":
nativeDisplayName += " \u65E7\u7248";
break;
case "zh-CHT":
nativeDisplayName += " \u820A\u7248";
break;
}
}
else
{
nativeDisplayName = GetLocaleInfoCore(LocaleStringData.NativeDisplayName);
// if it isn't found build one:
if (string.IsNullOrEmpty(nativeDisplayName))
{
// These should primarily be "Deutsch (Deutschland)" type names
nativeDisplayName = NativeLanguageName + " (" + NativeCountryName + ")";
}
}
_sNativeDisplayName = nativeDisplayName;
}
return nativeDisplayName!;
}
}
/// <summary>
/// The culture name to be used in CultureInfo.CreateSpecificCulture()
/// </summary>
internal string SpecificCultureName
{
get
{
// This got populated during the culture initialization
Debug.Assert(_sSpecificCulture != null, "[CultureData.SpecificCultureName] Expected this.sSpecificCulture to be populated by culture data initialization already");
return _sSpecificCulture;
}
}
/// <summary>
/// iso 639 language name, ie: en
/// </summary>
internal string TwoLetterISOLanguageName => _sISO639Language ??= GetLocaleInfoCore(LocaleStringData.Iso639LanguageTwoLetterName);
/// <summary>
/// iso 639 language name, ie: eng
/// </summary>
internal string ThreeLetterISOLanguageName => _sISO639Language2 ??= GetLocaleInfoCore(LocaleStringData.Iso639LanguageThreeLetterName);
/// <summary>
/// abbreviated windows language name (ie: enu) (non-standard, avoid this)
/// </summary>
internal string ThreeLetterWindowsLanguageName => _sAbbrevLang ??= GlobalizationMode.UseNls ?
NlsGetThreeLetterWindowsLanguageName(_sRealName) :
IcuGetThreeLetterWindowsLanguageName(_sRealName);
/// <summary>
/// Localized name for this language
/// </summary>
private string LocalizedLanguageName
{
get
{
string localizedLanguage = NativeLanguageName;
if (!GlobalizationMode.Invariant && Name.Length > 0)
{
// If ICU is enabled we call it anyway. If NLS, we call it only if the Windows UI language match the Current UI language
if (!GlobalizationMode.UseNls || CultureInfo.UserDefaultUICulture?.Name == CultureInfo.CurrentUICulture.Name)
{
localizedLanguage = GetLocaleInfoCore(LocaleStringData.LocalizedLanguageName, CultureInfo.CurrentUICulture.Name);
}
}
return localizedLanguage;
}
}
/// <summary>
/// English name for this language (Windows Only) ie: German
/// </summary>
private string EnglishLanguageName => _sEnglishLanguage ??= GetLocaleInfoCore(LocaleStringData.EnglishLanguageName);
/// <summary>
/// Native name of this language (Windows Only) ie: Deutsch
/// </summary>
private string NativeLanguageName => _sNativeLanguage ??= GetLocaleInfoCore(LocaleStringData.NativeLanguageName);
/// <summary>
/// region name (eg US)
/// </summary>
internal string RegionName => _sRegionName ??= GetLocaleInfoCore(LocaleStringData.Iso3166CountryName);
internal int GeoId
{
get
{
if (_iGeoId == undef && !GlobalizationMode.Invariant)
{
_iGeoId = GlobalizationMode.UseNls ? NlsGetLocaleInfo(LocaleNumberData.GeoId) : IcuGetGeoId(_sRealName);
}
return _iGeoId;
}
}
/// <summary>
/// localized name for the country
/// </summary>
internal string LocalizedCountryName
{
get
{
string? localizedCountry = _sLocalizedCountry;
if (localizedCountry == null && !GlobalizationMode.Invariant)
{
try
{
localizedCountry = GlobalizationMode.UseNls ? NlsGetRegionDisplayName() : IcuGetRegionDisplayName();
}
catch
{
// do nothing. we'll fallback
}
localizedCountry ??= NativeCountryName;
_sLocalizedCountry = localizedCountry;
}
return localizedCountry!;
}
}
/// <summary>
/// english country name (RegionInfo) ie: Germany
/// </summary>
internal string EnglishCountryName => _sEnglishCountry ??= GetLocaleInfoCore(LocaleStringData.EnglishCountryName);
/// <summary>
/// native country name (RegionInfo) ie: Deutschland
/// </summary>
internal string NativeCountryName => _sNativeCountry ??= GetLocaleInfoCore(LocaleStringData.NativeCountryName);
/// <summary>
/// ISO 3166 Country Name
/// </summary>
internal string TwoLetterISOCountryName => _sISO3166CountryName ??= GetLocaleInfoCore(LocaleStringData.Iso3166CountryName);
/// <summary>
/// 3 letter ISO 3166 country code
/// </summary>
internal string ThreeLetterISOCountryName => _sISO3166CountryName2 ??= GetLocaleInfoCore(LocaleStringData.Iso3166CountryName2);
internal int KeyboardLayoutId
{
get
{
if (_iInputLanguageHandle == undef)
{
if (IsSupplementalCustomCulture)
{
_iInputLanguageHandle = 0x0409;
}
else
{
// Input Language is same as LCID for built-in cultures
_iInputLanguageHandle = LCID;
}
}
return _iInputLanguageHandle;
}
}
/// <summary>
/// Console fallback name (ie: locale to use for console apps for unicode-only locales)
/// </summary>
internal string SCONSOLEFALLBACKNAME => _sConsoleFallbackName ??= GlobalizationMode.UseNls ?
NlsGetConsoleFallbackName(_sRealName) :
IcuGetConsoleFallbackName(_sRealName);
/// <summary>
/// grouping of digits
/// (user can override)
/// </summary>
internal int[] NumberGroupSizes => _waGrouping ??= GetLocaleInfoCoreUserOverride(LocaleGroupingData.Digit);
/// <summary>
/// Not a Number
/// </summary>
private string NaNSymbol => _sNaN ??= GetLocaleInfoCore(LocaleStringData.NaNSymbol);
/// <summary>
/// + Infinity
/// </summary>
private string PositiveInfinitySymbol => _sPositiveInfinity ??= GetLocaleInfoCore(LocaleStringData.PositiveInfinitySymbol);
/// <summary>
/// - Infinity
/// </summary>
private string NegativeInfinitySymbol => _sNegativeInfinity ??= GetLocaleInfoCore(LocaleStringData.NegativeInfinitySymbol);
/// <summary>
/// Negative Percent (0-3)
/// </summary>
private int PercentNegativePattern
{
get
{
if (_iNegativePercent == undef)
{
// Note that <= Windows Vista this is synthesized by native code
_iNegativePercent = GetLocaleInfoCore(LocaleNumberData.NegativePercentFormat);
}
return _iNegativePercent;
}
}
/// <summary>
/// Positive Percent (0-11)
/// </summary>
private int PercentPositivePattern
{
get
{
if (_iPositivePercent == undef)
{
// Note that <= Windows Vista this is synthesized by native code
_iPositivePercent = GetLocaleInfoCore(LocaleNumberData.PositivePercentFormat);
}
return _iPositivePercent;
}
}
/// <summary>
/// Percent (%) symbol
/// </summary>
private string PercentSymbol => _sPercent ??= GetLocaleInfoCore(LocaleStringData.PercentSymbol);
/// <summary>
/// PerMille symbol
/// </summary>
private string PerMilleSymbol => _sPerMille ??= GetLocaleInfoCore(LocaleStringData.PerMilleSymbol);
/// <summary>
/// local monetary symbol, eg: $
/// (user can override)
/// </summary>
internal string CurrencySymbol => _sCurrency ??= GetLocaleInfoCoreUserOverride(LocaleStringData.MonetarySymbol);
/// <summary>
/// international monetary symbol (RegionInfo), eg: USD
/// </summary>
internal string ISOCurrencySymbol => _sIntlMonetarySymbol ??= GetLocaleInfoCore(LocaleStringData.Iso4217MonetarySymbol);
/// <summary>
/// English name for this currency (RegionInfo), eg: US Dollar
/// </summary>
internal string CurrencyEnglishName => _sEnglishCurrency ??= GetLocaleInfoCore(LocaleStringData.CurrencyEnglishName);
/// <summary>
/// Native name for this currency (RegionInfo), eg: Schweiz Frank
/// </summary>
internal string CurrencyNativeName => _sNativeCurrency ??= GetLocaleInfoCore(LocaleStringData.CurrencyNativeName);
/// <summary>
/// monetary grouping of digits
/// (user can override)
/// </summary>
internal int[] CurrencyGroupSizes => _waMonetaryGrouping ??= GetLocaleInfoCoreUserOverride(LocaleGroupingData.Monetary);
/// <summary>
/// system of measurement 0=metric, 1=US (RegionInfo)
/// (user can override)
/// </summary>
internal int MeasurementSystem
{
get
{
if (_iMeasure == undef)
{
_iMeasure = GetLocaleInfoCoreUserOverride(LocaleNumberData.MeasurementSystem);
}
return _iMeasure;
}
}
/// <summary>
/// list Separator
/// (user can override)
/// </summary>
internal string ListSeparator => _sListSeparator ??= ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(LocaleStringData.ListSeparator) : IcuGetListSeparator(_sWindowsName);
/// <summary>
/// AM designator
/// (user can override)
/// </summary>
internal string AMDesignator => _sAM1159 ??= GetLocaleInfoCoreUserOverride(LocaleStringData.AMDesignator);
/// <summary>
/// PM designator
/// (user can override)
/// </summary>
internal string PMDesignator => _sPM2359 ??= GetLocaleInfoCoreUserOverride(LocaleStringData.PMDesignator);
/// <summary>
/// time format
/// (user can override)
/// </summary>
internal string[] LongTimes
{
get
{
if (_saLongTimes == null && !GlobalizationMode.Invariant)
{
string[]? longTimes = GetTimeFormatsCore(shortFormat: false);
_saLongTimes = longTimes != null && longTimes.Length != 0 ? longTimes : Invariant._saLongTimes!;
}
return _saLongTimes!;
}
}
/// <summary>
/// Short times (derived from long times format)
/// (user can override)
/// </summary>
internal string[] ShortTimes
{
get
{
if (_saShortTimes == null && !GlobalizationMode.Invariant)
{
// Try to get the short times from the OS/culture.dll
// If we couldn't find short times, then compute them from long times
// (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
string[]? shortTimes = GetTimeFormatsCore(shortFormat: true);
_saShortTimes = shortTimes != null && shortTimes.Length != 0 ? shortTimes : DeriveShortTimesFromLong();
}
return _saShortTimes!;
}
}
private string[] DeriveShortTimesFromLong()
{
// Our logic is to look for h,H,m,s,t. If we find an s, then we check the string
// between it and the previous marker, if any. If its a short, unescaped separator,
// then we don't retain that part.
// We then check after the ss and remove anything before the next h,H,m,t...
string[] longTimes = LongTimes;
string[] shortTimes = new string[longTimes.Length];
for (int i = 0; i < longTimes.Length; i++)
{
shortTimes[i] = StripSecondsFromPattern(longTimes[i]);
}
return shortTimes;
}
private static string StripSecondsFromPattern(string time)
{
bool bEscape = false;
int iLastToken = -1;
// Find the seconds
for (int j = 0; j < time.Length; j++)
{
// Change escape mode?
if (time[j] == '\'')
{
// Continue
bEscape = !bEscape;
continue;
}
// See if there was a single \
if (time[j] == '\\')
{
// Skip next char
j++;
continue;
}
if (bEscape)
{
continue;
}
switch (time[j])
{
// Check for seconds
case 's':
// Found seconds, see if there was something unescaped and short between
// the last marker and the seconds. Windows says separator can be a
// maximum of three characters (without null)
// If 1st or last characters were ', then ignore it
if ((j - iLastToken) <= 4 && (j - iLastToken) > 1 &&
(time[iLastToken + 1] != '\'') &&
(time[j - 1] != '\''))
{
// There was something there we want to remember
if (iLastToken >= 0)
{
j = iLastToken + 1;
}
}
bool containsSpace;
int endIndex = GetIndexOfNextTokenAfterSeconds(time, j, out containsSpace);
string sep;
if (containsSpace)
{
sep = " ";
}
else
{
sep = "";
}
time = string.Concat(time.AsSpan(0, j), sep, time.AsSpan(endIndex));
break;
case 'm':
case 'H':
case 'h':
iLastToken = j;
break;
}
}
return time;
}
private static int GetIndexOfNextTokenAfterSeconds(string time, int index, out bool containsSpace)
{
bool shouldEscape = false;
containsSpace = false;
for (; index < time.Length; index++)
{
switch (time[index])
{
case '\'':
shouldEscape = !shouldEscape;
continue;
case '\\':
index++;
if (time[index] == ' ')
{
containsSpace = true;
}
continue;
case ' ':
containsSpace = true;
break;
case 't':
case 'm':
case 'H':
case 'h':
if (shouldEscape)
{
continue;
}
return index;
}
}
containsSpace = false;
return index;
}
/// <summary>
/// first day of week
/// (user can override)
/// </summary>
internal int FirstDayOfWeek
{
get
{
if (_iFirstDayOfWeek == undef && !GlobalizationMode.Invariant)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (GlobalizationMode.Hybrid)
{
_iFirstDayOfWeek = GetLocaleInfoNative(LocaleNumberData.FirstDayOfWeek);
}
else
#endif
{
_iFirstDayOfWeek = ShouldUseUserOverrideNlsData ? NlsGetFirstDayOfWeek() : IcuGetLocaleInfo(LocaleNumberData.FirstDayOfWeek);
}
}
return _iFirstDayOfWeek;
}
}
/// <summary>
/// first week of year
/// (user can override)
/// </summary>
internal int CalendarWeekRule
{
get
{
if (_iFirstWeekOfYear == undef)
{
_iFirstWeekOfYear = GetLocaleInfoCoreUserOverride(LocaleNumberData.FirstWeekOfYear);
}
return _iFirstWeekOfYear;
}
}
/// <summary>
/// (user can override default only) short date format
/// </summary>
internal string[] ShortDates(CalendarId calendarId)
{
return GetCalendar(calendarId).saShortDates;
}
/// <summary>
/// long date format
/// (user can override default only)
/// </summary>
internal string[] LongDates(CalendarId calendarId)
{
return GetCalendar(calendarId).saLongDates;
}
/// <summary>
/// date year/month format.
/// (user can override default only)
/// </summary>
internal string[] YearMonths(CalendarId calendarId)
{
return GetCalendar(calendarId).saYearMonths;
}
internal string[] DayNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saDayNames;
}
internal string[] AbbreviatedDayNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saAbbrevDayNames;
}
internal string[] SuperShortDayNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saSuperShortDayNames;
}
internal string[] MonthNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saMonthNames;
}
internal string[] GenitiveMonthNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saMonthGenitiveNames;
}
internal string[] AbbreviatedMonthNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saAbbrevMonthNames;
}
internal string[] AbbreviatedGenitiveMonthNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
}
/// <remarks>>
/// Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
/// the non-leap names skip the 7th name in the normal month name array
/// </remarks>
internal string[] LeapYearMonthNames(CalendarId calendarId)
{
return GetCalendar(calendarId).saLeapYearMonthNames;
}
internal string MonthDay(CalendarId calendarId)
{
return GetCalendar(calendarId).sMonthDay;
}
/// <summary>
/// All available calendar type(s). The first one is the default calendar.
/// </summary>
internal CalendarId[] CalendarIds
{
get
{
if (_waCalendars == null)
{
if (GlobalizationMode.Invariant || IsInvariantCulture)
{
// We do this lazily as opposed in CreateCultureWithInvariantData to avoid introducing
// enum arrays into apps that otherwise wouldn't have them. This helps with size in AOT.
_waCalendars = [CalendarId.GREGORIAN];
}
else
{
// We pass in an array of ints, and native side fills it up with count calendars.
// We then have to copy that list to a new array of the right size.
// Default calendar should be first
CalendarId[] calendars = new CalendarId[23];
Debug.Assert(_sWindowsName != null, "[CultureData.CalendarIds] Expected _sWindowsName to be populated by already");
int count = CalendarData.GetCalendarsCore(_sWindowsName, _bUseOverrides, calendars);
// See if we had a calendar to add.
if (count == 0)
{
// Failed for some reason, just use Gregorian
_waCalendars = Invariant.CalendarIds;
}
else
{
// The OS may not return calendar 4 for zh-TW, but we've always allowed it.
// TODO: Is this hack necessary long-term?
if (_sWindowsName == "zh-TW")
{
bool found = false;
// Do we need to insert calendar 4?
for (int i = 0; i < count; i++)
{
// Stop if we found calendar four
if (calendars[i] == CalendarId.TAIWAN)
{
found = true;
break;
}
}
// If not found then insert it
if (!found)
{
// Insert it as the 2nd calendar
count++;
// Copy them from the 2nd position to the end, -1 for skipping 1st, -1 for one being added.
Array.Copy(calendars, 1, calendars, 2, 23 - 1 - 1);
calendars[1] = CalendarId.TAIWAN;
}
}
// It worked, remember the list
CalendarId[] temp = new CalendarId[count];
Array.Copy(calendars, temp, count);
_waCalendars = temp;
}
}
}
return _waCalendars;
}
}
/// <summary>
/// Native calendar names. Index of optional calendar - 1, empty if
/// no optional calendar at that number
/// </summary>
internal string CalendarName(CalendarId calendarId)
{
return GetCalendar(calendarId).sNativeName;
}
internal CalendarData GetCalendar(CalendarId calendarId)
{
if (GlobalizationMode.Invariant)
{
return CalendarData.Invariant;
}
Debug.Assert(calendarId > 0 && calendarId <= CalendarId.LAST_CALENDAR,
"[CultureData.GetCalendar] Expect calendarId to be in a valid range");
// arrays are 0 based, calendarIds are 1 based
int calendarIndex = (int)calendarId - 1;
// Have to have calendars
_calendars ??= new CalendarData[CalendarData.MAX_CALENDARS];
// we need the following local variable to avoid returning null
// when another thread creates a new array of CalendarData (above)
// right after we insert the newly created CalendarData (below)
CalendarData? calendarData = _calendars[calendarIndex];
// Make sure that calendar has data
if (calendarData == null)
{
if (calendarIndex == 0 && IsInvariantCulture)
{
// We do this lazily as opposed in CreateCultureWithInvariantData to avoid introducing
// invariant calendar data into apps that don't even need calendar.
calendarData = CalendarData.Invariant;
}
else
{
Debug.Assert(_sWindowsName != null, "[CultureData.GetCalendar] Expected _sWindowsName to be populated by already");
calendarData = new CalendarData(_sWindowsName, calendarId, _bUseOverrides);
}
_calendars[calendarIndex] = calendarData;
}
return calendarData;
}
internal bool IsRightToLeft =>
// Returns one of the following 4 reading layout values:
// 0 - Left to right (eg en-US)
// 1 - Right to left (eg arabic locales)
// 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
// 3 - Vertical top to bottom with columns proceeding to the right
ReadingLayout == 1;
/// <summary>
/// Returns one of the following 4 reading layout values:
/// 0 - Left to right (eg en-US)
/// 1 - Right to left (eg arabic locales)
/// 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
/// 3 - Vertical top to bottom with columns proceeding to the right
/// </summary>
private int ReadingLayout
{
get
{
if (_iReadingLayout == undef && !GlobalizationMode.Invariant)
{
Debug.Assert(_sRealName != null, "[CultureData.IsRightToLeft] Expected _sRealName to be populated by already");
_iReadingLayout = GetLocaleInfoCore(LocaleNumberData.ReadingLayout);
}
return _iReadingLayout;
}
}
/// <summary>
/// // Text info name to use for text information
/// The TextInfo name never includes that alternate sort and is always specific
/// For customs, it uses the SortLocale (since the textinfo is not exposed in Win7)
/// en -> en-US
/// en-US -> en-US
/// fj (custom neutral) -> en-US (assuming that en-US is the sort locale for fj)
/// fj_FJ (custom specific) -> en-US (assuming that en-US is the sort locale for fj-FJ)
/// es-ES_tradnl -> es-ES
/// </summary>
internal string TextInfoName
{
get
{
// Note: Custom cultures might point at another culture's textinfo, however windows knows how
// to redirect it to the desired textinfo culture, so this is OK.
Debug.Assert(_sRealName != null, "[CultureData.TextInfoName] Expected _sRealName to be populated by already");
return _sRealName;
}
}
/// <summary>
/// Compare info name (including sorting key) to use if custom
/// </summary>
internal string SortName
{
get
{
Debug.Assert(_sRealName != null, "[CultureData.SortName] Expected _sRealName to be populated by already");
return _sRealName;
}
}
internal bool IsSupplementalCustomCulture => IsCustomCultureId(LCID);
/// <summary>
/// Default ansi code page ID (ACP)
/// </summary>
internal int ANSICodePage
{
get
{
if (_iDefaultAnsiCodePage == undef && !GlobalizationMode.Invariant)
{
_iDefaultAnsiCodePage = GetAnsiCodePage(_sRealName);
}
return _iDefaultAnsiCodePage;
}
}
/// <summary>
/// Default oem code page ID (OCP or OEM).
/// </summary>
internal int OEMCodePage
{
get
{
if (_iDefaultOemCodePage == undef && !GlobalizationMode.Invariant)
{
_iDefaultOemCodePage = GetOemCodePage(_sRealName);
}
return _iDefaultOemCodePage;
}
}
/// <summary>
/// Default macintosh code page.
/// </summary>
internal int MacCodePage
{
get
{
if (_iDefaultMacCodePage == undef && !GlobalizationMode.Invariant)
{
_iDefaultMacCodePage = GetMacCodePage(_sRealName);
}
return _iDefaultMacCodePage;
}
}
/// <summary>
/// Default EBCDIC code page.
/// </summary>
internal int EBCDICCodePage
{
get
{
if (_iDefaultEbcdicCodePage == undef && !GlobalizationMode.Invariant)
{
_iDefaultEbcdicCodePage = GetEbcdicCodePage(_sRealName);
}
return _iDefaultEbcdicCodePage;
}
}
internal int LCID
{
get
{
if (_iLanguage == 0 && !GlobalizationMode.Invariant)
{
Debug.Assert(_sRealName != null, "[CultureData.LCID] Expected this.sRealName to be populated already");
_iLanguage = GlobalizationMode.UseNls ? NlsLocaleNameToLCID(_sRealName) : IcuLocaleNameToLCID(_sRealName);
}
return _iLanguage;
}
}
internal bool IsNeutralCulture =>
// InitCultureData told us if we're neutral or not
_bNeutral;
internal bool IsInvariantCulture => string.IsNullOrEmpty(Name);
internal bool IsReplacementCulture => GlobalizationMode.UseNls ? NlsIsReplacementCulture : false;
/// <summary>
/// Get an instance of our default calendar
/// </summary>
internal Calendar DefaultCalendar
{
get
{
if (GlobalizationMode.Invariant)
{
return new GregorianCalendar();
}
CalendarId defaultCalId = (CalendarId)GetLocaleInfoCore(LocaleNumberData.CalendarType);
if (defaultCalId == 0)
{
defaultCalId = CalendarIds[0];
}
return CultureInfo.GetCalendarInstance(defaultCalId);
}
}
/// <summary>
/// All of our era names
/// </summary>
internal string[] EraNames(CalendarId calendarId)
{
Debug.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
return GetCalendar(calendarId).saEraNames;
}
internal string[] AbbrevEraNames(CalendarId calendarId)
{
Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
return GetCalendar(calendarId).saAbbrevEraNames;
}
internal string[] AbbreviatedEnglishEraNames(CalendarId calendarId)
{
Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
return GetCalendar(calendarId).saAbbrevEnglishEraNames;
}
/// <summary>
/// Time separator (derived from time format)
/// </summary>
internal string TimeSeparator
{
get
{
if (_sTimeSeparator == null && !GlobalizationMode.Invariant)
{
// fr-CA culture uses time format as "HH 'h' mm 'min' ss 's'" which we cannot derive the time separator from such pattern.
// We special case such culture and force ':' as time separator.
if (_sName == "fr-CA")
{
_sTimeSeparator = ":";
}
else
{
string? longTimeFormat = ShouldUseUserOverrideNlsData ? NlsGetTimeFormatString() : IcuGetTimeFormatString();
if (string.IsNullOrEmpty(longTimeFormat))
{
longTimeFormat = LongTimes[0];
}
// Compute STIME from time format
_sTimeSeparator = GetTimeSeparator(longTimeFormat);
}
}
return _sTimeSeparator!;
}
}
/// <summary>
/// Date separator (derived from short date format)
/// </summary>
internal string DateSeparator(CalendarId calendarId)
{
if (GlobalizationMode.Invariant)
{
return "/";
}
if (calendarId == CalendarId.JAPAN && !LocalAppContextSwitches.EnforceLegacyJapaneseDateParsing)
{
// The date separator is derived from the default short date pattern. So far this pattern is using
// '/' as date separator when using the Japanese calendar which make the formatting and parsing work fine.
// changing the default pattern is likely will happen in the near future which can easily break formatting
// and parsing.
// We are forcing here the date separator to '/' to ensure the parsing is not going to break when changing
// the default short date pattern. The application still can override this in the code by DateTimeFormatInfo.DateSeparator.
return "/";
}
return GetDateSeparator(ShortDates(calendarId)[0]);
}
/// <summary>
/// Unescape a NLS style quote string
///
/// This removes single quotes:
/// 'fred' -> fred
/// 'fred -> fred
/// fred' -> fred
/// fred's -> freds
///
/// This removes the first \ of escaped characters:
/// fred\'s -> fred's
/// a\\b -> a\b
/// a\b -> ab
///
/// We don't build the stringbuilder unless we find a ' or a \. If we find a ' or a \, we
/// always build a stringbuilder because we need to remove the ' or \.
/// </summary>
private static string UnescapeNlsString(string str, int start, int end)
{
Debug.Assert(str != null);
Debug.Assert(start >= 0);
Debug.Assert(end >= 0);
StringBuilder? result = null;
for (int i = start; i < str.Length && i <= end; i++)
{
switch (str[i])
{
case '\'':
result ??= new StringBuilder(str, start, i - start, str.Length);
break;
case '\\':
result ??= new StringBuilder(str, start, i - start, str.Length);
++i;
if (i < str.Length)
{
result.Append(str[i]);
}
break;
default:
result?.Append(str[i]);
break;
}
}
if (result == null)
{
return str.Substring(start, end - start + 1);
}
return result.ToString();
}
/// <summary>
/// Time format separator (ie: : in 12:39:00)
/// We calculate this from the provided time format
/// </summary>
private static string GetTimeSeparator(string format)
{
// Find the time separator so that we can pretend we know TimeSeparator.
return GetSeparator(format, "Hhms");
}
/// <summary>
/// Date format separator (ie: / in 9/1/03)
/// We calculate this from the provided short date
/// </summary>
private static string GetDateSeparator(string format)
{
// Find the date separator so that we can pretend we know DateSeparator.
return GetSeparator(format, "dyM");
}
private static string GetSeparator(string format, string timeParts)
{
int index = IndexOfTimePart(format, 0, timeParts);
if (index != -1)
{
// Found a time part, find out when it changes
char cTimePart = format[index];
do
{
index++;
} while (index < format.Length && format[index] == cTimePart);
int separatorStart = index;
// Now we need to find the end of the separator
if (separatorStart < format.Length)
{
int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
if (separatorEnd != -1)
{
// From [separatorStart, count) is our string, except we need to unescape
return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
}
}
}
return string.Empty;
}
private static int IndexOfTimePart(string format, int startIndex, string timeParts)
{
Debug.Assert(startIndex >= 0, "startIndex cannot be negative");
Debug.Assert(!timeParts.AsSpan().ContainsAny('\'', '\\'), "timeParts cannot include quote characters");
bool inQuote = false;
for (int i = startIndex; i < format.Length; ++i)
{
// See if we have a time Part
if (!inQuote && timeParts.Contains(format[i]))
{
return i;
}
switch (format[i])
{
case '\\':
if (i + 1 < format.Length)
{
++i;
switch (format[i])
{
case '\'':
case '\\':
break;
default:
--i; // backup since we will move over this next
break;
}
}
break;
case '\'':
inQuote = !inQuote;
break;
}
}
return -1;
}
internal static bool IsCustomCultureId(int cultureId)
{
return cultureId == CultureInfo.LOCALE_CUSTOM_DEFAULT || cultureId == CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
}
private string[] GetNativeDigits()
{
string[] result = NumberFormatInfo.s_asciiDigits;
// NLS LOCALE_SNATIVEDIGITS (array of 10 single character strings). In case of ICU, the buffer can be longer.
string digits = GetLocaleInfoCoreUserOverride(LocaleStringData.Digits);
// if digits.Length < NumberFormatInfo.s_asciiDigits.Length means the native digits setting is messed up in the host machine.
// Instead of throwing IndexOutOfRangeException that will be hard to diagnose after the fact, we'll fall back to use the ASCII digits instead.
if (digits.Length < NumberFormatInfo.s_asciiDigits.Length)
{
return result;
}
// In ICU we separate the digits with the '\uFFFF' character
if (digits.StartsWith("0\uFFFF1\uFFFF2\uFFFF3\uFFFF4\uFFFF5\uFFFF6\uFFFF7\uFFFF8\uFFFF9\uFFFF", StringComparison.Ordinal) || // ICU common cases
digits.StartsWith("0123456789", StringComparison.Ordinal)) // NLS common cases
{
return result;
}
// Non-ASCII digits
// Check if values coming from ICU separated by 0xFFFF
int ffffPos = digits.IndexOf('\uFFFF');
result = new string[10];
if (ffffPos < 0) // NLS case
{
for (int i = 0; i < result.Length; i++)
{
result[i] = char.ToString(digits[i]);
}
return result;
}
// ICU case
int start = 0;
int index = 0;
do
{
result[index++] = digits.Substring(start, ffffPos - start);
start = ++ffffPos;
while ((uint)ffffPos < (uint)digits.Length && digits[ffffPos] != '\uFFFF')
{
ffffPos++;
}
} while (ffffPos < digits.Length && index < 10);
Debug.Assert(index >= 10, $"Couldn't read native digits for '{_sWindowsName}' successfully.");
return index < 10 ? NumberFormatInfo.s_asciiDigits : result;
}
internal void GetNFIValues(NumberFormatInfo nfi)
{
if (GlobalizationMode.InvariantNoLoad || IsInvariantCulture)
{
nfi._positiveSign = _sPositiveSign!;
nfi._negativeSign = _sNegativeSign!;
nfi._numberGroupSeparator = _sThousandSeparator!;
nfi._numberDecimalSeparator = _sDecimalSeparator!;
nfi._numberDecimalDigits = _iDigits;
nfi._numberNegativePattern = _iNegativeNumber;
nfi._currencySymbol = _sCurrency!;
nfi._currencyGroupSeparator = _sMonetaryThousand!;
nfi._currencyDecimalSeparator = _sMonetaryDecimal!;
nfi._currencyDecimalDigits = _iCurrencyDigits;
nfi._currencyNegativePattern = _iNegativeCurrency;
nfi._currencyPositivePattern = _iCurrency;
}
else
{
Debug.Assert(_sWindowsName != null, "[CultureData.GetNFIValues] Expected _sWindowsName to be populated by already");
// String values
nfi._positiveSign = GetLocaleInfoCoreUserOverride(LocaleStringData.PositiveSign);
nfi._negativeSign = GetLocaleInfoCoreUserOverride(LocaleStringData.NegativeSign);
nfi._numberDecimalSeparator = GetLocaleInfoCoreUserOverride(LocaleStringData.DecimalSeparator);
nfi._numberGroupSeparator = GetLocaleInfoCoreUserOverride(LocaleStringData.ThousandSeparator);
nfi._currencyGroupSeparator = GetLocaleInfoCoreUserOverride(LocaleStringData.MonetaryThousandSeparator);
nfi._currencyDecimalSeparator = GetLocaleInfoCoreUserOverride(LocaleStringData.MonetaryDecimalSeparator);
nfi._currencySymbol = GetLocaleInfoCoreUserOverride(LocaleStringData.MonetarySymbol);
// Numeric values
nfi._numberDecimalDigits = GetLocaleInfoCoreUserOverride(LocaleNumberData.FractionalDigitsCount);
nfi._currencyDecimalDigits = GetLocaleInfoCoreUserOverride(LocaleNumberData.MonetaryFractionalDigitsCount);
nfi._currencyPositivePattern = GetLocaleInfoCoreUserOverride(LocaleNumberData.PositiveMonetaryNumberFormat);
nfi._currencyNegativePattern = GetLocaleInfoCoreUserOverride(LocaleNumberData.NegativeMonetaryNumberFormat);
nfi._numberNegativePattern = GetLocaleInfoCoreUserOverride(LocaleNumberData.NegativeNumberFormat);
nfi._nativeDigits = GetNativeDigits();
Debug.Assert(_sRealName != null);
nfi._digitSubstitution = ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(LocaleNumberData.DigitSubstitution) : IcuGetDigitSubstitution(_sRealName);
}
// Gather additional data
nfi._numberGroupSizes = NumberGroupSizes;
nfi._currencyGroupSizes = CurrencyGroupSizes;
// prefer the cached value since these do not have user overrides
nfi._percentNegativePattern = PercentNegativePattern;
nfi._percentPositivePattern = PercentPositivePattern;
nfi._percentSymbol = PercentSymbol;
nfi._perMilleSymbol = PerMilleSymbol;
nfi._negativeInfinitySymbol = NegativeInfinitySymbol;
nfi._positiveInfinitySymbol = PositiveInfinitySymbol;
nfi._nanSymbol = NaNSymbol;
// We don't have percent values, so use the number values
nfi._percentDecimalDigits = nfi._numberDecimalDigits;
nfi._percentDecimalSeparator = nfi._numberDecimalSeparator;
nfi._percentGroupSizes = nfi._numberGroupSizes;
nfi._percentGroupSeparator = nfi._numberGroupSeparator;
// Clean up a few odd values
// Windows usually returns an empty positive sign, but we like it to be "+"
if (string.IsNullOrEmpty(nfi._positiveSign))
{
nfi._positiveSign = "+";
}
// Special case for Italian. The currency decimal separator in the control panel is the empty string. When the user
// specifies C4 as the currency format, this results in the number apparently getting multiplied by 10000 because the
// decimal point doesn't show up. We'll just hack this here because our default currency format will never use nfi.
if (string.IsNullOrEmpty(nfi._currencyDecimalSeparator))
{
nfi._currencyDecimalSeparator = nfi._numberDecimalSeparator;
}
}
/// <remarks>
/// This is ONLY used for caching names and shouldn't be used for anything else
/// </remarks>
internal static string AnsiToLower(string testString) => TextInfo.ToLowerAsciiInvariant(testString);
private int GetLocaleInfoCore(LocaleNumberData type)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return 0;
#if 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_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)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return null!;
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}
private string GetLocaleInfoCore(LocaleStringData type, string? uiCultureName = null)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return null!;
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type, uiCultureName) : IcuGetLocaleInfo(type, uiCultureName);
#else
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type, uiCultureName);
#endif
}
private string GetLocaleInfoCore(string localeName, LocaleStringData type, string? uiCultureName = null)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return null!;
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(localeName, type, uiCultureName) : IcuGetLocaleInfo(localeName, type, uiCultureName);
#else
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(localeName, type) : IcuGetLocaleInfo(localeName, type, uiCultureName);
#endif
}
private int[] GetLocaleInfoCoreUserOverride(LocaleGroupingData type)
{
// This is never reached but helps illinker statically remove dependencies
if (GlobalizationMode.Invariant)
return null!;
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}
/// <remarks>
/// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
/// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
/// </remarks>
private enum LocaleStringData : uint
{
/// <summary>localized name of locale, eg "German (Germany)" in UI language (corresponds to LOCALE_SLOCALIZEDDISPLAYNAME)</summary>
LocalizedDisplayName = 0x00000002,
/// <summary>Display name (language + country usually) in English, eg "German (Germany)" (corresponds to LOCALE_SENGLISHDISPLAYNAME)</summary>
EnglishDisplayName = 0x00000072,
/// <summary>Display name in native locale language, eg "Deutsch (Deutschland) (corresponds to LOCALE_SNATIVEDISPLAYNAME)</summary>
NativeDisplayName = 0x00000073,
/// <summary>Language Display Name for a language, eg "German" in UI language (corresponds to LOCALE_SLOCALIZEDLANGUAGENAME)</summary>
LocalizedLanguageName = 0x0000006f,
/// <summary>English name of language, eg "German" (corresponds to LOCALE_SENGLISHLANGUAGENAME)</summary>
EnglishLanguageName = 0x00001001,
/// <summary>native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME)</summary>
NativeLanguageName = 0x00000004,
/// <summary>localized name of country, eg "Germany" in UI language (corresponds to LOCALE_SLOCALIZEDCOUNTRYNAME)</summary>
LocalizedCountryName = 0x00000006,
/// <summary>English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME)</summary>
EnglishCountryName = 0x00001002,
/// <summary>native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME)</summary>
NativeCountryName = 0x00000008,
/// <summary>abbreviated language name (corresponds to LOCALE_SABBREVLANGNAME)</summary>
AbbreviatedWindowsLanguageName = 0x00000003,
/// <summary>list item separator (corresponds to LOCALE_SLIST)</summary>
ListSeparator = 0x0000000C,
/// <summary>decimal separator (corresponds to LOCALE_SDECIMAL)</summary>
DecimalSeparator = 0x0000000E,
/// <summary>thousand separator (corresponds to LOCALE_STHOUSAND)</summary>
ThousandSeparator = 0x0000000F,
/// <summary>native digits for 0-9, eg "0123456789" (corresponds to LOCALE_SNATIVEDIGITS)</summary>
Digits = 0x00000013,
/// <summary>local monetary symbol (corresponds to LOCALE_SCURRENCY)</summary>
MonetarySymbol = 0x00000014,
/// <summary>English currency name (corresponds to LOCALE_SENGCURRNAME)</summary>
CurrencyEnglishName = 0x00001007,
/// <summary>Native currency name (corresponds to LOCALE_SNATIVECURRNAME)</summary>
CurrencyNativeName = 0x00001008,
/// <summary>uintl monetary symbol (corresponds to LOCALE_SINTLSYMBOL)</summary>
Iso4217MonetarySymbol = 0x00000015,
/// <summary>monetary decimal separator (corresponds to LOCALE_SMONDECIMALSEP)</summary>
MonetaryDecimalSeparator = 0x00000016,
/// <summary>monetary thousand separator (corresponds to LOCALE_SMONTHOUSANDSEP)</summary>
MonetaryThousandSeparator = 0x00000017,
/// <summary>AM designator (corresponds to LOCALE_S1159)</summary>
AMDesignator = 0x00000028,
/// <summary>PM designator (corresponds to LOCALE_S2359)</summary>
PMDesignator = 0x00000029,
/// <summary>positive sign (corresponds to LOCALE_SPOSITIVESIGN)</summary>
PositiveSign = 0x00000050,
/// <summary>negative sign (corresponds to LOCALE_SNEGATIVESIGN)</summary>
NegativeSign = 0x00000051,
/// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
Iso639LanguageTwoLetterName = 0x00000059,
/// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO639LANGNAME2)</summary>
Iso639LanguageThreeLetterName = 0x00000067,
/// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
Iso639LanguageName = 0x00000059,
/// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO3166CTRYNAME)</summary>
Iso3166CountryName = 0x0000005A,
/// <summary>3 letter ISO country code (corresponds to LOCALE_SISO3166CTRYNAME2)</summary>
Iso3166CountryName2 = 0x00000068, // 3 character ISO country name
/// <summary>Not a Number (corresponds to LOCALE_SNAN)</summary>
NaNSymbol = 0x00000069,
/// <summary>+ Infinity (corresponds to LOCALE_SPOSINFINITY)</summary>
PositiveInfinitySymbol = 0x0000006a,
/// <summary>- Infinity (corresponds to LOCALE_SNEGINFINITY)</summary>
NegativeInfinitySymbol = 0x0000006b,
/// <summary>Fallback name for resources (corresponds to LOCALE_SPARENT)</summary>
ParentName = 0x0000006d,
/// <summary>Fallback name for within the console (corresponds to LOCALE_SCONSOLEFALLBACKNAME)</summary>
ConsoleFallbackName = 0x0000006e,
/// <summary>Returns the percent symbol (corresponds to LOCALE_SPERCENT)</summary>
PercentSymbol = 0x00000076,
/// <summary>Returns the permille (U+2030) symbol (corresponds to LOCALE_SPERMILLE)</summary>
PerMilleSymbol = 0x00000077
}
/// <remarks>
/// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
/// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
/// </remarks>
private enum LocaleGroupingData : uint
{
/// <summary>digit grouping (corresponds to LOCALE_SGROUPING)</summary>
Digit = 0x00000010,
/// <summary>monetary grouping (corresponds to LOCALE_SMONGROUPING)</summary>
Monetary = 0x00000018,
}
/// <remarks>
/// The numeric values of the enum members match their Win32 counterparts. The CultureData Win32 PAL implementation
/// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
/// </remarks>
private enum LocaleNumberData : uint
{
/// <summary>language id (corresponds to LOCALE_ILANGUAGE)</summary>
LanguageId = 0x00000001,
/// <summary>geographical location id, (corresponds to LOCALE_IGEOID)</summary>
GeoId = 0x0000005B,
/// <summary>0 = context, 1 = none, 2 = national (corresponds to LOCALE_IDIGITSUBSTITUTION)</summary>
DigitSubstitution = 0x00001014,
/// <summary>0 = metric, 1 = US (corresponds to LOCALE_IMEASURE)</summary>
MeasurementSystem = 0x0000000D,
/// <summary>number of fractional digits (corresponds to LOCALE_IDIGITS)</summary>
FractionalDigitsCount = 0x00000011,
/// <summary>negative number mode (corresponds to LOCALE_INEGNUMBER)</summary>
NegativeNumberFormat = 0x00001010,
/// <summary># local monetary digits (corresponds to LOCALE_ICURRDIGITS)</summary>
MonetaryFractionalDigitsCount = 0x00000019,
/// <summary>positive currency mode (corresponds to LOCALE_ICURRENCY)</summary>
PositiveMonetaryNumberFormat = 0x0000001B,
/// <summary>negative currency mode (corresponds to LOCALE_INEGCURR)</summary>
NegativeMonetaryNumberFormat = 0x0000001C,
/// <summary>type of calendar specifier (corresponds to LOCALE_ICALENDARTYPE)</summary>
CalendarType = 0x00001009,
/// <summary>first day of week specifier (corresponds to LOCALE_IFIRSTDAYOFWEEK)</summary>
FirstDayOfWeek = 0x0000100C,
/// <summary>first week of year specifier (corresponds to LOCALE_IFIRSTWEEKOFYEAR)</summary>
FirstWeekOfYear = 0x0000100D,
/// <summary>
/// Returns one of the following 4 reading layout values:
/// 0 - Left to right (eg en-US)
/// 1 - Right to left (eg arabic locales)
/// 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
/// 3 - Vertical top to bottom with columns proceeding to the right
/// (corresponds to LOCALE_IREADINGLAYOUT)
/// </summary>
ReadingLayout = 0x00000070,
/// <summary>Returns 0-11 for the negative percent format (corresponds to LOCALE_INEGATIVEPERCENT)</summary>
NegativePercentFormat = 0x00000074,
/// <summary>Returns 0-3 for the positive percent format (corresponds to LOCALE_IPOSITIVEPERCENT)</summary>
PositivePercentFormat = 0x00000075,
/// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTCODEPAGE)</summary>
OemCodePage = 0x0000000B,
/// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTANSICODEPAGE)</summary>
AnsiCodePage = 0x00001004,
/// <summary>default mac code page (corresponds to LOCALE_IDEFAULTMACCODEPAGE)</summary>
MacCodePage = 0x00001011,
/// <summary>default ebcdic code page (corresponds to LOCALE_IDEFAULTEBCDICCODEPAGE)</summary>
EbcdicCodePage = 0x00001012,
}
}
}
|