1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
10 using Internal.Runtime.CompilerServices;
13 using Internal.Runtime.Augments;
16 namespace System.Globalization
19 using StringList = List<string>;
21 using StringList = LowLevelList<string>;
24 internal partial class CultureData
26 private const uint LOCALE_NOUSEROVERRIDE = 0x80000000;
27 private const uint LOCALE_RETURN_NUMBER = 0x20000000;
28 private const uint LOCALE_SISO3166CTRYNAME = 0x0000005A;
30 private const uint TIME_NOSECONDS = 0x00000002;
33 /// Check with the OS to see if this is a valid culture.
34 /// If so we populate a limited number of fields. If its not valid we return false.
36 /// The fields we populate:
38 /// sWindowsName -- The name that windows thinks this culture is, ie:
39 /// en-US if you pass in en-US
40 /// de-DE_phoneb if you pass in de-DE_phoneb
41 /// fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
42 /// fj if you pass in fj (neutral, post-Windows 7 machine)
44 /// sRealName -- The name you used to construct the culture, in pretty form
45 /// en-US if you pass in EN-us
46 /// en if you pass in en
47 /// de-DE_phoneb if you pass in de-DE_phoneb
49 /// sSpecificCulture -- The specific culture for this culture
52 /// de-DE_phoneb for alt sort
53 /// fj-FJ for fj (neutral)
55 /// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
56 /// en-US if you pass in en-US
57 /// en if you pass in en
58 /// de-DE if you pass in de-DE_phoneb
60 /// bNeutral -- TRUE if it is a neutral locale
62 /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the
63 /// windows locale that's going to provide data for us.
65 private unsafe bool InitCultureData()
67 Debug.Assert(!GlobalizationMode.Invariant);
69 const uint LOCALE_ILANGUAGE = 0x00000001;
70 const uint LOCALE_INEUTRAL = 0x00000071;
71 const uint LOCALE_SNAME = 0x0000005c;
74 string realNameBuffer = _sRealName;
75 char* pBuffer = stackalloc char[LOCALE_NAME_MAX_LENGTH];
77 result = GetLocaleInfoEx(realNameBuffer, LOCALE_SNAME, pBuffer, LOCALE_NAME_MAX_LENGTH);
85 // It worked, note that the name is the locale name, so use that (even for neutrals)
86 // We need to clean up our "real" name, which should look like the windows name right now
87 // so overwrite the input with the cleaned up name
88 _sRealName = new String(pBuffer, 0, result - 1);
89 realNameBuffer = _sRealName;
91 // Check for neutrality, don't expect to fail
92 // (buffer has our name in it, so we don't have to do the gc. stuff)
94 result = GetLocaleInfoEx(realNameBuffer, LOCALE_INEUTRAL | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
100 // Remember our neutrality
101 _bNeutral = *((uint*)pBuffer) != 0;
103 // Note: Parents will be set dynamically
105 // Start by assuming the windows name will be the same as the specific name since windows knows
106 // about specifics on all versions. Only for downlevel Neutral locales does this have to change.
107 _sWindowsName = realNameBuffer;
109 // Neutrals and non-neutrals are slightly different
114 // IETF name looks like neutral name
115 _sName = realNameBuffer;
117 // Specific locale name is whatever ResolveLocaleName (win7+) returns.
118 // (Buffer has our name in it, and we can recycle that because windows resolves it before writing to the buffer)
119 result = Interop.Kernel32.ResolveLocaleName(realNameBuffer, pBuffer, LOCALE_NAME_MAX_LENGTH);
121 // 0 is failure, 1 is invariant (""), which we expect
126 // We found a locale name, so use it.
127 // In vista this should look like a sort name (de-DE_phoneb) or a specific culture (en-US) and be in the "pretty" form
128 _sSpecificCulture = new String(pBuffer, 0, result - 1);
134 // Specific culture's the same as the locale name since we know its not neutral
135 // On mac we'll use this as well, even for neutrals. There's no obvious specific
136 // culture to use and this isn't exposed, but behaviorally this is correct on mac.
137 // Note that specifics include the sort name (de-DE_phoneb)
138 _sSpecificCulture = realNameBuffer;
140 _sName = realNameBuffer;
142 // We need the IETF name (sname)
143 // If we aren't an alt sort locale then this is the same as the windows name.
144 // If we are an alt sort locale then this is the same as the part before the _ in the windows name
145 // This is for like de-DE_phoneb and es-ES_tradnl that hsouldn't have the _ part
147 result = GetLocaleInfoEx(realNameBuffer, LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER, pBuffer, sizeof(int) / sizeof(char));
153 _iLanguage = *((int*)pBuffer);
155 if (!IsCustomCultureId(_iLanguage))
158 int index = realNameBuffer.IndexOf('_');
159 if (index > 0 && index < realNameBuffer.Length)
161 _sName = realNameBuffer.Substring(0, index);
170 // Wrappers around the GetLocaleInfoEx APIs which handle marshalling the returned
171 // data as either and Int or String.
172 internal static unsafe String GetLocaleInfoEx(String localeName, uint field)
174 // REVIEW: Determine the maximum size for the buffer
175 const int BUFFER_SIZE = 530;
177 char* pBuffer = stackalloc char[BUFFER_SIZE];
178 int resultCode = GetLocaleInfoEx(localeName, field, pBuffer, BUFFER_SIZE);
181 return new String(pBuffer);
187 internal static unsafe int GetLocaleInfoExInt(String localeName, uint field)
189 const uint LOCALE_RETURN_NUMBER = 0x20000000;
190 field |= LOCALE_RETURN_NUMBER;
192 GetLocaleInfoEx(localeName, field, (char*) &value, sizeof(int));
196 internal static unsafe int GetLocaleInfoEx(string lpLocaleName, uint lcType, char* lpLCData, int cchData)
198 Debug.Assert(!GlobalizationMode.Invariant);
200 return Interop.Kernel32.GetLocaleInfoEx(lpLocaleName, lcType, lpLCData, cchData);
203 private string GetLocaleInfo(LocaleStringData type)
205 Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected _sWindowsName to be populated by already");
206 return GetLocaleInfo(_sWindowsName, type);
209 // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
210 // "windows" name, which can be specific for downlevel (< windows 7) os's.
211 private string GetLocaleInfo(string localeName, LocaleStringData type)
213 uint lctype = (uint)type;
215 return GetLocaleInfoFromLCType(localeName, lctype, UseUserOverride);
218 private int GetLocaleInfo(LocaleNumberData type)
220 uint lctype = (uint)type;
222 // Fix lctype if we don't want overrides
223 if (!UseUserOverride)
225 lctype |= LOCALE_NOUSEROVERRIDE;
228 // Ask OS for data, note that we presume it returns success, so we have to know that
229 // sWindowsName is valid before calling.
230 Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already");
231 return GetLocaleInfoExInt(_sWindowsName, lctype);
234 private int[] GetLocaleInfo(LocaleGroupingData type)
236 return ConvertWin32GroupString(GetLocaleInfoFromLCType(_sWindowsName, (uint)type, UseUserOverride));
239 private string GetTimeFormatString()
241 const uint LOCALE_STIMEFORMAT = 0x00001003;
243 return ReescapeWin32String(GetLocaleInfoFromLCType(_sWindowsName, LOCALE_STIMEFORMAT, UseUserOverride));
246 private int GetFirstDayOfWeek()
248 Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already");
250 const uint LOCALE_IFIRSTDAYOFWEEK = 0x0000100C;
252 int result = GetLocaleInfoExInt(_sWindowsName, LOCALE_IFIRSTDAYOFWEEK | (!UseUserOverride ? LOCALE_NOUSEROVERRIDE : 0));
254 // Win32 and .NET disagree on the numbering for days of the week, so we have to convert.
255 return ConvertFirstDayOfWeekMonToSun(result);
258 private String[] GetTimeFormats()
260 // Note that this gets overrides for us all the time
261 Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected _sWindowsName to be populated by already");
262 String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, 0, UseUserOverride));
267 private String[] GetShortTimeFormats()
269 // Note that this gets overrides for us all the time
270 Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected _sWindowsName to be populated by already");
271 String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, TIME_NOSECONDS, UseUserOverride));
276 // Enumerate all system cultures and then try to find out which culture has
277 // region name match the requested region name
278 private static CultureData GetCultureDataFromRegionName(String regionName)
280 Debug.Assert(!GlobalizationMode.Invariant);
281 Debug.Assert(regionName != null);
283 const uint LOCALE_SUPPLEMENTAL = 0x00000002;
284 const uint LOCALE_SPECIFICDATA = 0x00000020;
286 EnumLocaleData context = new EnumLocaleData();
287 context.cultureName = null;
288 context.regionName = regionName;
292 Interop.Kernel32.EnumSystemLocalesEx(EnumSystemLocalesProc, LOCALE_SPECIFICDATA | LOCALE_SUPPLEMENTAL, Unsafe.AsPointer(ref context), IntPtr.Zero);
295 if (context.cultureName != null)
297 // we got a matched culture
298 return GetCultureData(context.cultureName, true);
304 private string GetLanguageDisplayName(string cultureName)
307 return WinRTInterop.Callbacks.GetLanguageDisplayName(cultureName);
309 // Usually the UI culture shouldn't be different than what we got from WinRT except
310 // if DefaultThreadCurrentUICulture was set
313 if (CultureInfo.DefaultThreadCurrentUICulture != null &&
314 ((ci = GetUserDefaultCulture()) != null) &&
315 !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
317 return SNATIVEDISPLAYNAME;
321 return GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName);
323 #endif // ENABLE_WINRT
326 private string GetRegionDisplayName(string isoCountryCode)
329 return WinRTInterop.Callbacks.GetRegionDisplayName(isoCountryCode);
331 // If the current UI culture matching the OS UI language, we'll get the display name from the OS.
332 // otherwise, we use the native name as we don't carry resources for the region display names anyway.
333 if (CultureInfo.CurrentUICulture.Name.Equals(CultureInfo.UserDefaultUICulture.Name))
335 return GetLocaleInfo(LocaleStringData.LocalizedCountryName);
338 return SNATIVECOUNTRY;
339 #endif // ENABLE_WINRT
342 private static CultureInfo GetUserDefaultCulture()
345 return (CultureInfo)WinRTInterop.Callbacks.GetUserDefaultCulture();
347 return CultureInfo.GetUserDefaultCulture();
348 #endif // ENABLE_WINRT
351 // PAL methods end here.
353 private static string GetLocaleInfoFromLCType(string localeName, uint lctype, bool useUserOveride)
355 Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoFromLCType] Expected localeName to be not be null");
357 // Fix lctype if we don't want overrides
360 lctype |= LOCALE_NOUSEROVERRIDE;
364 string result = GetLocaleInfoEx(localeName, lctype);
367 // Failed, just use empty string
368 result = String.Empty;
374 ////////////////////////////////////////////////////////////////////////////
376 // Reescape a Win32 style quote string as a NLS+ style quoted string
378 // This is also the escaping style used by custom culture data files
380 // NLS+ uses \ to escape the next character, whether in a quoted string or
381 // not, so we always have to change \ to \\.
383 // NLS+ uses \' to escape a quote inside a quoted string so we have to change
384 // '' to \' (if inside a quoted string)
386 // We don't build the stringbuilder unless we find something to change
387 ////////////////////////////////////////////////////////////////////////////
388 internal static String ReescapeWin32String(String str)
390 // If we don't have data, then don't try anything
394 StringBuilder result = null;
396 bool inQuote = false;
397 for (int i = 0; i < str.Length; i++)
405 // See another single quote. Is this '' of 'fred''s' or '''', or is it an ending quote?
406 if (i + 1 < str.Length && str[i + 1] == '\'')
408 // Found another ', so we have ''. Need to add \' instead.
409 // 1st make sure we have our stringbuilder
411 result = new StringBuilder(str, 0, i, str.Length * 2);
413 // Append a \' and keep going (so we don't turn off quote mode)
414 result.Append("\\'");
419 // Turning off quote mode, fall through to add it
424 // Found beginning quote, fall through to add it
428 // Is there a single \ character?
429 else if (str[i] == '\\')
431 // Found a \, need to change it to \\
432 // 1st make sure we have our stringbuilder
434 result = new StringBuilder(str, 0, i, str.Length * 2);
436 // Append our \\ to the string & continue
437 result.Append("\\\\");
441 // If we have a builder we need to add our character
443 result.Append(str[i]);
446 // Unchanged string? , just return input string
450 // String changed, need to use the builder
451 return result.ToString();
454 internal static String[] ReescapeWin32Strings(String[] array)
458 for (int i = 0; i < array.Length; i++)
460 array[i] = ReescapeWin32String(array[i]);
467 // If we get a group from windows, then its in 3;0 format with the 0 backwards
468 // of how NLS+ uses it (ie: if the string has a 0, then the int[] shouldn't and vice versa)
469 // EXCEPT in the case where the list only contains 0 in which NLS and NLS+ have the same meaning.
470 private static int[] ConvertWin32GroupString(String win32Str)
472 // None of these cases make any sense
473 if (win32Str == null || win32Str.Length == 0)
475 return (new int[] { 3 });
478 if (win32Str[0] == '0')
480 return (new int[] { 0 });
483 // Since its in n;n;n;n;n format, we can always get the length quickly
485 if (win32Str[win32Str.Length - 1] == '0')
487 // Trailing 0 gets dropped. 1;0 -> 1
488 values = new int[(win32Str.Length / 2)];
492 // Need extra space for trailing zero 1 -> 1;0
493 values = new int[(win32Str.Length / 2) + 2];
494 values[values.Length - 1] = 0;
499 for (i = 0, j = 0; i < win32Str.Length && j < values.Length; i += 2, j++)
501 // Note that this # shouldn't ever be zero, 'cause 0 is only at end
502 // But we'll test because its registry that could be anything
503 if (win32Str[i] < '1' || win32Str[i] > '9')
504 return new int[] { 3 };
506 values[j] = (int)(win32Str[i] - '0');
512 private static int ConvertFirstDayOfWeekMonToSun(int iTemp)
514 // Convert Mon-Sun to Sun-Sat format
518 // Wrap Sunday and convert invalid data to Sunday
525 // Context for EnumCalendarInfoExEx callback.
526 private class EnumLocaleData
528 public string regionName;
529 public string cultureName;
532 // EnumSystemLocaleEx callback.
533 // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
534 private static unsafe Interop.BOOL EnumSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle)
536 ref EnumLocaleData context = ref Unsafe.As<byte, EnumLocaleData>(ref *(byte*)contextHandle);
539 string cultureName = new string(lpLocaleString);
540 string regionName = GetLocaleInfoEx(cultureName, LOCALE_SISO3166CTRYNAME);
541 if (regionName != null && regionName.Equals(context.regionName, StringComparison.OrdinalIgnoreCase))
543 context.cultureName = cultureName;
544 return Interop.BOOL.FALSE; // we found a match, then stop the enumeration
547 return Interop.BOOL.TRUE;
551 return Interop.BOOL.FALSE;
555 // EnumSystemLocaleEx callback.
556 // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
557 private static unsafe Interop.BOOL EnumAllSystemLocalesProc(char* lpLocaleString, uint flags, void* contextHandle)
559 ref EnumData context = ref Unsafe.As<byte, EnumData>(ref *(byte*)contextHandle);
562 context.strings.Add(new string(lpLocaleString));
563 return Interop.BOOL.TRUE;
567 return Interop.BOOL.FALSE;
571 // Context for EnumTimeFormatsEx callback.
572 private class EnumData
574 public StringList strings;
577 // EnumTimeFormatsEx callback itself.
578 // [NativeCallable(CallingConvention = CallingConvention.StdCall)]
579 private static unsafe Interop.BOOL EnumTimeCallback(char* lpTimeFormatString, void* lParam)
581 ref EnumData context = ref Unsafe.As<byte, EnumData>(ref *(byte*)lParam);
584 context.strings.Add(new string(lpTimeFormatString));
585 return Interop.BOOL.TRUE;
589 return Interop.BOOL.FALSE;
593 private static unsafe String[] nativeEnumTimeFormats(String localeName, uint dwFlags, bool useUserOverride)
595 const uint LOCALE_SSHORTTIME = 0x00000079;
596 const uint LOCALE_STIMEFORMAT = 0x00001003;
598 EnumData data = new EnumData();
599 data.strings = new StringList();
601 // Now call the enumeration API. Work is done by our callback function
602 Interop.Kernel32.EnumTimeFormatsEx(EnumTimeCallback, localeName, (uint)dwFlags, Unsafe.AsPointer(ref data));
604 if (data.strings.Count > 0)
606 // Now we need to allocate our stringarray and populate it
607 string[] results = data.strings.ToArray();
609 if (!useUserOverride && data.strings.Count > 1)
611 // Since there is no "NoUserOverride" aware EnumTimeFormatsEx, we always get an override
612 // The override is the first entry if it is overriden.
613 // We can check if we have overrides by checking the GetLocaleInfo with no override
614 // If we do have an override, we don't know if it is a user defined override or if the
615 // user has just selected one of the predefined formats so we can't just remove it
616 // but we can move it down.
617 uint lcType = (dwFlags == TIME_NOSECONDS) ? LOCALE_SSHORTTIME : LOCALE_STIMEFORMAT;
618 string timeFormatNoUserOverride = GetLocaleInfoFromLCType(localeName, lcType, useUserOverride);
619 if (timeFormatNoUserOverride != "")
621 string firstTimeFormat = results[0];
622 if (timeFormatNoUserOverride != firstTimeFormat)
624 results[0] = results[1];
625 results[1] = firstTimeFormat;
636 private static int LocaleNameToLCID(string cultureName)
638 Debug.Assert(!GlobalizationMode.Invariant);
640 return Interop.Kernel32.LocaleNameToLCID(cultureName, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES);
643 private static unsafe string LCIDToLocaleName(int culture)
645 Debug.Assert(!GlobalizationMode.Invariant);
647 char *pBuffer = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1]; // +1 for the null termination
648 int length = Interop.Kernel32.LCIDToLocaleName(culture, pBuffer, Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1, Interop.Kernel32.LOCALE_ALLOW_NEUTRAL_NAMES);
652 return new String(pBuffer);
658 private int GetAnsiCodePage(string cultureName)
660 return GetLocaleInfo(LocaleNumberData.AnsiCodePage);
663 private int GetOemCodePage(string cultureName)
665 return GetLocaleInfo(LocaleNumberData.OemCodePage);
668 private int GetMacCodePage(string cultureName)
670 return GetLocaleInfo(LocaleNumberData.MacCodePage);
673 private int GetEbcdicCodePage(string cultureName)
675 return GetLocaleInfo(LocaleNumberData.EbcdicCodePage);
678 private int GetGeoId(string cultureName)
680 return GetLocaleInfo(LocaleNumberData.GeoId);
683 private int GetDigitSubstitution(string cultureName)
685 return GetLocaleInfo(LocaleNumberData.DigitSubstitution);
688 private string GetThreeLetterWindowsLanguageName(string cultureName)
690 return GetLocaleInfo(cultureName, LocaleStringData.AbbreviatedWindowsLanguageName);
693 private static CultureInfo[] EnumCultures(CultureTypes types)
695 Debug.Assert(!GlobalizationMode.Invariant);
699 #pragma warning disable 618
700 if ((types & (CultureTypes.FrameworkCultures | CultureTypes.InstalledWin32Cultures | CultureTypes.ReplacementCultures)) != 0)
702 flags |= Interop.Kernel32.LOCALE_NEUTRALDATA | Interop.Kernel32.LOCALE_SPECIFICDATA;
704 #pragma warning restore 618
706 if ((types & CultureTypes.NeutralCultures) != 0)
708 flags |= Interop.Kernel32.LOCALE_NEUTRALDATA;
711 if ((types & CultureTypes.SpecificCultures) != 0)
713 flags |= Interop.Kernel32.LOCALE_SPECIFICDATA;
716 if ((types & CultureTypes.UserCustomCulture) != 0)
718 flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL;
721 if ((types & CultureTypes.ReplacementCultures) != 0)
723 flags |= Interop.Kernel32.LOCALE_SUPPLEMENTAL;
726 EnumData context = new EnumData();
727 context.strings = new StringList();
731 Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, flags, Unsafe.AsPointer(ref context), IntPtr.Zero);
734 CultureInfo [] cultures = new CultureInfo[context.strings.Count];
735 for (int i = 0; i < cultures.Length; i++)
737 cultures[i] = new CultureInfo(context.strings[i]);
743 private string GetConsoleFallbackName(string cultureName)
745 return GetLocaleInfo(cultureName, LocaleStringData.ConsoleFallbackName);
748 internal bool IsFramework
750 get { return false; }
753 internal bool IsWin32Installed
758 internal bool IsReplacementCulture
762 EnumData context = new EnumData();
763 context.strings = new StringList();
767 Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, Interop.Kernel32.LOCALE_REPLACEMENT, Unsafe.AsPointer(ref context), IntPtr.Zero);
770 for (int i=0; i<context.strings.Count; i++)
772 if (String.Compare(context.strings[i], _sWindowsName, StringComparison.OrdinalIgnoreCase) == 0)