// 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.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Internal.Runtime.CompilerServices; #if ENABLE_WINRT using Internal.Runtime.Augments; #endif namespace System.Globalization { internal partial class CultureData { private const uint LOCALE_NOUSEROVERRIDE = 0x80000000; private const uint LOCALE_RETURN_NUMBER = 0x20000000; private const uint LOCALE_SISO3166CTRYNAME = 0x0000005A; private const uint TIME_NOSECONDS = 0x00000002; /// /// Check with the OS to see if this is a valid culture. /// If so we populate a limited number of fields. If its not valid we return false. /// /// The fields we populate: /// /// 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 /// /// bNeutral -- TRUE if it is a neutral locale /// /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the /// windows locale that's going to provide data for us. /// private unsafe bool InitCultureData() { Debug.Assert(!GlobalizationMode.Invariant); const uint LOCALE_ILANGUAGE = 0x00000001; const uint LOCALE_INEUTRAL = 0x00000071; const uint LOCALE_SNAME = 0x0000005c; int result; string realNameBuffer = _sRealName; char* pBuffer = stackalloc char[LOCALE_NAME_MAX_LENGTH]; result = GetLocaleInfoEx(realNameBuffer, LOCALE_SNAME, pBuffer, LOCALE_NAME_MAX_LENGTH); // Did it fail? if (result == 0) { return false; } // It worked, note that the name is the locale name, so use that (even for neutrals) // We need to clean up our "real" name, which should look like the windows name right now // so overwrite the input with the cleaned up name _sRealName = new string(pBuffer, 0, result - 1); realNameBuffer = _sRealName; // Check for neutrality, don't expect to fail // (buffer has our name in it, so we don't have to do the gc. stuff) result = GetLocaleInfoEx(realNameBuffer, LOCALE_INEUTRAL | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char)); if (result == 0) { return false; } // Remember our neutrality _bNeutral = *((uint*)pBuffer) != 0; // Note: Parents will be set dynamically // Start by assuming the windows name will be the same as the specific name since windows knows // about specifics on all versions. Only for downlevel Neutral locales does this have to change. _sWindowsName = realNameBuffer; // Neutrals and non-neutrals are slightly different if (_bNeutral) { // Neutral Locale // IETF name looks like neutral name _sName = realNameBuffer; // Specific locale name is whatever ResolveLocaleName (win7+) returns. // (Buffer has our name in it, and we can recycle that because windows resolves it before writing to the buffer) result = Interop.Kernel32.ResolveLocaleName(realNameBuffer, pBuffer, LOCALE_NAME_MAX_LENGTH); // 0 is failure, 1 is invariant (""), which we expect if (result < 1) { return false; } // We found a locale name, so use it. // In vista this should look like a sort name (de-DE_phoneb) or a specific culture (en-US) and be in the "pretty" form _sSpecificCulture = new string(pBuffer, 0, result - 1); } else { // Specific Locale // Specific culture's the same as the locale name since we know its not neutral // On mac we'll use this as well, even for neutrals. There's no obvious specific // culture to use and this isn't exposed, but behaviorally this is correct on mac. // Note that specifics include the sort name (de-DE_phoneb) _sSpecificCulture = realNameBuffer; _sName = realNameBuffer; // We need the IETF name (sname) // If we aren't an alt sort locale then this is the same as the windows name. // If we are an alt sort locale then this is the same as the part before the _ in the windows name // This is for like de-DE_phoneb and es-ES_tradnl that hsouldn't have the _ part result = GetLocaleInfoEx(realNameBuffer, LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char)); if (result == 0) { return false; } _iLanguage = *((int*)pBuffer); if (!IsCustomCultureId(_iLanguage)) { // not custom locale int index = realNameBuffer.IndexOf('_'); if (index > 0 && index < realNameBuffer.Length) { _sName = realNameBuffer.Substring(0, index); } } } // It succeeded. return true; } // Wrappers around the GetLocaleInfoEx APIs which handle marshalling the returned // data as either and Int or string. internal static unsafe string GetLocaleInfoEx(string localeName, uint field) { // REVIEW: Determine the maximum size for the buffer const int BUFFER_SIZE = 530; char* pBuffer = stackalloc char[BUFFER_SIZE]; int resultCode = GetLocaleInfoEx(localeName, field, pBuffer, BUFFER_SIZE); if (resultCode > 0) { return new string(pBuffer); } return null; } internal static unsafe int GetLocaleInfoExInt(string localeName, uint field) { const uint LOCALE_RETURN_NUMBER = 0x20000000; field |= LOCALE_RETURN_NUMBER; int value = 0; GetLocaleInfoEx(localeName, field, (char*) &value, sizeof(int)); return value; } internal static unsafe int GetLocaleInfoEx(string lpLocaleName, uint lcType, char* lpLCData, int cchData) { Debug.Assert(!GlobalizationMode.Invariant); return Interop.Kernel32.GetLocaleInfoEx(lpLocaleName, lcType, lpLCData, cchData); } private string GetLocaleInfo(LocaleStringData type) { Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected _sWindowsName to be populated by 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 string GetLocaleInfo(string localeName, LocaleStringData type) { uint lctype = (uint)type; return GetLocaleInfoFromLCType(localeName, lctype, UseUserOverride); } private int GetLocaleInfo(LocaleNumberData type) { uint lctype = (uint)type; // Fix lctype if we don't want overrides if (!UseUserOverride) { lctype |= LOCALE_NOUSEROVERRIDE; } // Ask OS for data, note that we presume it returns success, so we have to know that // sWindowsName is valid before calling. Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); return GetLocaleInfoExInt(_sWindowsName, lctype); } private int[] GetLocaleInfo(LocaleGroupingData type) { return ConvertWin32GroupString(GetLocaleInfoFromLCType(_sWindowsName, (uint)type, UseUserOverride)); } private string GetTimeFormatString() { const uint LOCALE_STIMEFORMAT = 0x00001003; return ReescapeWin32String(GetLocaleInfoFromLCType(_sWindowsName, LOCALE_STIMEFORMAT, UseUserOverride)); } private int GetFirstDayOfWeek() { Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); const uint LOCALE_IFIRSTDAYOFWEEK = 0x0000100C; int result = GetLocaleInfoExInt(_sWindowsName, LOCALE_IFIRSTDAYOFWEEK | (!UseUserOverride ? LOCALE_NOUSEROVERRIDE : 0)); // Win32 and .NET disagree on the numbering for days of the week, so we have to convert. return ConvertFirstDayOfWeekMonToSun(result); } private string[] GetTimeFormats() { // Note that this gets overrides for us all the time Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected _sWindowsName to be populated by already"); string[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, 0, UseUserOverride)); return result; } private string[] GetShortTimeFormats() { // Note that this gets overrides for us all the time Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected _sWindowsName to be populated by already"); string[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, TIME_NOSECONDS, UseUserOverride)); return result; } // Enumerate all system cultures and then try to find out which culture has // region name match the requested region name private static CultureData GetCultureDataFromRegionName(string regionName) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(regionName != null); const uint LOCALE_SUPPLEMENTAL = 0x00000002; const uint LOCALE_SPECIFICDATA = 0x00000020; EnumLocaleData context = new EnumLocaleData(); context.cultureName = null; context.regionName = regionName; unsafe { Interop.Kernel32.EnumSystemLocalesEx(EnumSystemLocalesProc, LOCALE_SPECIFICDATA | LOCALE_SUPPLEMENTAL, Unsafe.AsPointer(ref context), IntPtr.Zero); } if (context.cultureName != null) { // we got a matched culture return GetCultureData(context.cultureName, true); } return null; } private string GetLanguageDisplayName(string cultureName) { #if ENABLE_WINRT return WinRTInterop.Callbacks.GetLanguageDisplayName(cultureName); #else // Usually the UI culture shouldn't be different than what we got from WinRT except // if DefaultThreadCurrentUICulture was set CultureInfo ci; if (CultureInfo.DefaultThreadCurrentUICulture != null && ((ci = GetUserDefaultCulture()) != null) && !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name)) { return SNATIVEDISPLAYNAME; } else { return GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName); } #endif // ENABLE_WINRT } private string GetRegionDisplayName(string isoCountryCode) { #if ENABLE_WINRT return WinRTInterop.Callbacks.GetRegionDisplayName(isoCountryCode); #else // If the current UI culture matching the OS UI language, we'll get the display name from the OS. // otherwise, we use the native name as we don't carry resources for the region display names anyway. if (CultureInfo.CurrentUICulture.Name.Equals(CultureInfo.UserDefaultUICulture.Name)) { return GetLocaleInfo(LocaleStringData.LocalizedCountryName); } return SNATIVECOUNTRY; #endif // ENABLE_WINRT } private static CultureInfo GetUserDefaultCulture() { #if ENABLE_WINRT return (CultureInfo)WinRTInterop.Callbacks.GetUserDefaultCulture(); #else return CultureInfo.GetUserDefaultCulture(); #endif // ENABLE_WINRT } // PAL methods end here. private static string GetLocaleInfoFromLCType(string localeName, uint lctype, bool useUserOveride) { Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoFromLCType] Expected localeName to be not be null"); // Fix lctype if we don't want overrides if (!useUserOveride) { lctype |= LOCALE_NOUSEROVERRIDE; } // Ask OS for data string result = GetLocaleInfoEx(localeName, lctype); if (result == null) { // Failed, just use empty string result = string.Empty; } return result; } //////////////////////////////////////////////////////////////////////////// // // Reescape a Win32 style quote string as a NLS+ style quoted string // // This is also the escaping style used by custom culture data files // // NLS+ uses \ to escape the next character, whether in a quoted string or // not, so we always have to change \ to \\. // // NLS+ uses \' to escape a quote inside a quoted string so we have to change // '' to \' (if inside a quoted string) // // We don't build the stringbuilder unless we find something to change //////////////////////////////////////////////////////////////////////////// internal static string ReescapeWin32String(string str) { // If we don't have data, then don't try anything if (str == null) return null; StringBuilder result = null; bool inQuote = false; for (int i = 0; i < str.Length; i++) { // Look for quote if (str[i] == '\'') { // Already in quote? if (inQuote) { // See another single quote. Is this '' of 'fred''s' or '''', or is it an ending quote? if (i + 1 < str.Length && str[i + 1] == '\'') { // Found another ', so we have ''. Need to add \' instead. // 1st make sure we have our stringbuilder if (result == null) result = new StringBuilder(str, 0, i, str.Length * 2); // Append a \' and keep going (so we don't turn off quote mode) result.Append("\\'"); i++; continue; } // Turning off quote mode, fall through to add it inQuote = false; } else { // Found beginning quote, fall through to add it inQuote = true; } } // Is there a single \ character? else if (str[i] == '\\') { // Found a \, need to change it to \\ // 1st make sure we have our stringbuilder if (result == null) result = new StringBuilder(str, 0, i, str.Length * 2); // Append our \\ to the string & continue result.Append("\\\\"); continue; } // If we have a builder we need to add our character if (result != null) result.Append(str[i]); } // Unchanged string? , just return input string if (result == null) return str; // String changed, need to use the builder return result.ToString(); } internal static string[] ReescapeWin32Strings(string[] array) { if (array != null) { for (int i = 0; i < array.Length; i++) { array[i] = ReescapeWin32String(array[i]); } } return array; } // If we get a group from windows, then its in 3;0 format with the 0 backwards // of how NLS+ uses it (ie: if the string has a 0, then the int[] shouldn't and vice versa) // EXCEPT in the case where the list only contains 0 in which NLS and NLS+ have the same meaning. private static int[] ConvertWin32GroupString(string win32Str) { // None of these cases make any sense if (win32Str == null || win32Str.Length == 0) { return (new int[] { 3 }); } if (win32Str[0] == '0') { return (new int[] { 0 }); } // Since its in n;n;n;n;n format, we can always get the length quickly int[] values; if (win32Str[win32Str.Length - 1] == '0') { // Trailing 0 gets dropped. 1;0 -> 1 values = new int[(win32Str.Length / 2)]; } else { // Need extra space for trailing zero 1 -> 1;0 values = new int[(win32Str.Length / 2) + 2]; values[values.Length - 1] = 0; } int i; int j; for (i = 0, j = 0; i < win32Str.Length && j < values.Length; i += 2, j++) { // Note that this # shouldn't ever be zero, 'cause 0 is only at end // But we'll test because its registry that could be anything if (win32Str[i] < '1' || win32Str[i] > '9') return new int[] { 3 }; values[j] = (int)(win32Str[i] - '0'); } return (values); } private static int ConvertFirstDayOfWeekMonToSun(int iTemp) { // Convert Mon-Sun to Sun-Sat format iTemp++; if (iTemp > 6) { // Wrap Sunday and convert invalid data to Sunday iTemp = 0; } return iTemp; } // Context for EnumCalendarInfoExEx callback. private struct EnumLocaleData { public string regionName; public string cultureName; } // EnumSystemLocaleEx callback. // [NativeCallable(CallingConvention = CallingConvention.StdCall)] private static unsafe Interop.BOOL EnumSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle) { ref EnumLocaleData context = ref Unsafe.As(ref *(byte*)contextHandle); try { string cultureName = new string(lpLocaleString); string regionName = GetLocaleInfoEx(cultureName, LOCALE_SISO3166CTRYNAME); if (regionName != null && regionName.Equals(context.regionName, StringComparison.OrdinalIgnoreCase)) { context.cultureName = cultureName; return Interop.BOOL.FALSE; // we found a match, then stop the enumeration } return Interop.BOOL.TRUE; } catch (Exception) { return Interop.BOOL.FALSE; } } // EnumSystemLocaleEx callback. // [NativeCallable(CallingConvention = CallingConvention.StdCall)] private static unsafe Interop.BOOL EnumAllSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle) { ref EnumData context = ref Unsafe.As(ref *(byte*)contextHandle); try { context.strings.Add(new string(lpLocaleString)); return Interop.BOOL.TRUE; } catch (Exception) { return Interop.BOOL.FALSE; } } // Context for EnumTimeFormatsEx callback. private struct EnumData { public List strings; } // EnumTimeFormatsEx callback itself. // [NativeCallable(CallingConvention = CallingConvention.StdCall)] private static unsafe Interop.BOOL EnumTimeCallback(char* lpTimeFormatString, void* lParam) { ref EnumData context = ref Unsafe.As(ref *(byte*)lParam); try { context.strings.Add(new string(lpTimeFormatString)); return Interop.BOOL.TRUE; } catch (Exception) { return Interop.BOOL.FALSE; } } private static unsafe string[] nativeEnumTimeFormats(string localeName, uint dwFlags, bool useUserOverride) { const uint LOCALE_SSHORTTIME = 0x00000079; const uint LOCALE_STIMEFORMAT = 0x00001003; EnumData data = new EnumData(); data.strings = new List(); // Now call the enumeration API. Work is done by our callback function Interop.Kernel32.EnumTimeFormatsEx(EnumTimeCallback, localeName, (uint)dwFlags, Unsafe.AsPointer(ref data)); if (data.strings.Count > 0) { // Now we need to allocate our stringarray and populate it string[] results = data.strings.ToArray(); if (!useUserOverride && data.strings.Count > 1) { // Since there is no "NoUserOverride" aware EnumTimeFormatsEx, we always get an override // The override is the first entry if it is overriden. // We can check if we have overrides by checking the GetLocaleInfo with no override // If we do have an override, we don't know if it is a user defined override or if the // user has just selected one of the predefined formats so we can't just remove it // but we can move it down. uint lcType = (dwFlags == TIME_NOSECONDS) ? LOCALE_SSHORTTIME : LOCALE_STIMEFORMAT; string timeFormatNoUserOverride = GetLocaleInfoFromLCType(localeName, lcType, useUserOverride); if (timeFormatNoUserOverride != "") { string firstTimeFormat = results[0]; if (timeFormatNoUserOverride != firstTimeFormat) { results[0] = results[1]; results[1] = firstTimeFormat; } } } return results; } return null; } private static int LocaleNameToLCID(string cultureName) { Debug.Assert(!GlobalizationMode.Invariant); return Interop.Kernel32.LocaleNameToLCID(cultureName, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES); } private static unsafe string LCIDToLocaleName(int culture) { Debug.Assert(!GlobalizationMode.Invariant); char *pBuffer = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1]; // +1 for the null termination int length = Interop.Kernel32.LCIDToLocaleName(culture, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES); if (length > 0) { return new string(pBuffer); } return null; } private int GetAnsiCodePage(string cultureName) { return GetLocaleInfo(LocaleNumberData.AnsiCodePage); } private int GetOemCodePage(string cultureName) { return GetLocaleInfo(LocaleNumberData.OemCodePage); } private int GetMacCodePage(string cultureName) { return GetLocaleInfo(LocaleNumberData.MacCodePage); } private int GetEbcdicCodePage(string cultureName) { return GetLocaleInfo(LocaleNumberData.EbcdicCodePage); } private int GetGeoId(string cultureName) { return GetLocaleInfo(LocaleNumberData.GeoId); } private int GetDigitSubstitution(string cultureName) { return GetLocaleInfo(LocaleNumberData.DigitSubstitution); } private string GetThreeLetterWindowsLanguageName(string cultureName) { return GetLocaleInfo(cultureName, LocaleStringData.AbbreviatedWindowsLanguageName); } private static CultureInfo[] EnumCultures(CultureTypes types) { Debug.Assert(!GlobalizationMode.Invariant); uint flags = 0; #pragma warning disable 618 if ((types & (CultureTypes.FrameworkCultures | CultureTypes.InstalledWin32Cultures | CultureTypes.ReplacementCultures)) != 0) { flags |= Interop.Kernel32.LOCALE_NEUTRALDATA | Interop.Kernel32.LOCALE_SPECIFICDATA; } #pragma warning restore 618 if ((types & CultureTypes.NeutralCultures) != 0) { flags |= Interop.Kernel32.LOCALE_NEUTRALDATA; } if ((types & CultureTypes.SpecificCultures) != 0) { flags |= Interop.Kernel32.LOCALE_SPECIFICDATA; } if ((types & CultureTypes.UserCustomCulture) != 0) { flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL; } if ((types & CultureTypes.ReplacementCultures) != 0) { flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL; } EnumData context = new EnumData(); context.strings = new List(); unsafe { Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, flags, Unsafe.AsPointer(ref context), IntPtr.Zero); } CultureInfo [] cultures = new CultureInfo[context.strings.Count]; for (int i = 0; i < cultures.Length; i++) { cultures[i] = new CultureInfo(context.strings[i]); } return cultures; } private string GetConsoleFallbackName(string cultureName) { return GetLocaleInfo(cultureName, LocaleStringData.ConsoleFallbackName); } internal bool IsFramework { get { return false; } } internal bool IsWin32Installed { get { return true; } } internal bool IsReplacementCulture { get { EnumData context = new EnumData(); context.strings = new List(); unsafe { Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, Interop.Kernel32.LOCALE_REPLACEMENT, Unsafe.AsPointer(ref context), IntPtr.Zero); } for (int i=0; i