d913a8929f40e58b4beb4542302c4c65dd4d665d
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Globalization / CultureData.cs
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.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Text;
8 using System.Threading;
9
10 namespace System.Globalization
11 {
12 #if CORECLR
13     using StringStringDictionary = Dictionary<string, string>;
14     using StringCultureDataDictionary = Dictionary<string, CultureData>;
15     using LcidToCultureNameDictionary = Dictionary<int, string>;
16     using Lock = Object;
17 #else
18     using StringStringDictionary = LowLevelDictionary<string, string>;
19     using StringCultureDataDictionary = LowLevelDictionary<string, CultureData>;
20     using LcidToCultureNameDictionary = LowLevelDictionary<int, string>;
21 #endif
22
23     //
24     // List of culture data
25     // Note the we cache overrides.
26     // Note that localized names (resource names) aren't available from here.
27     //
28
29     //
30     // Our names are a tad confusing.
31     //
32     // sWindowsName -- The name that windows thinks this culture is, ie:
33     //                            en-US if you pass in en-US
34     //                            de-DE_phoneb if you pass in de-DE_phoneb
35     //                            fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
36     //                            fj if you pass in fj (neutral, post-Windows 7 machine)
37     //
38     // sRealName -- The name you used to construct the culture, in pretty form
39     //                       en-US if you pass in EN-us
40     //                       en if you pass in en
41     //                       de-DE_phoneb if you pass in de-DE_phoneb
42     //
43     // sSpecificCulture -- The specific culture for this culture
44     //                             en-US for en-US
45     //                             en-US for en
46     //                             de-DE_phoneb for alt sort
47     //                             fj-FJ for fj (neutral)
48     //
49     // sName -- The IETF name of this culture (ie: no sort info, could be neutral)
50     //                en-US if you pass in en-US
51     //                en if you pass in en
52     //                de-DE if you pass in de-DE_phoneb
53     //
54     internal partial class CultureData
55     {
56         private const int LOCALE_NAME_MAX_LENGTH = 85;
57         private const int undef = -1;
58
59         // Override flag
60         private String _sRealName; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
61         private String _sWindowsName; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
62
63         // Identity
64         private String _sName; // locale name (ie: en-us, NO sort info, but could be neutral)
65         private String _sParent; // Parent name (which may be a custom locale/culture)
66         private String _sLocalizedDisplayName; // Localized pretty name for this locale
67         private String _sEnglishDisplayName; // English pretty name for this locale
68         private String _sNativeDisplayName; // Native pretty name for this locale
69         private String _sSpecificCulture; // The culture name to be used in CultureInfo.CreateSpecificCulture(), en-US form if neutral, sort name if sort
70
71         // Language
72         private String _sISO639Language; // ISO 639 Language Name
73         private String _sISO639Language2; // ISO 639 Language Name
74         private String _sLocalizedLanguage; // Localized name for this language
75         private String _sEnglishLanguage; // English name for this language
76         private String _sNativeLanguage; // Native name of this language
77         private String _sAbbrevLang; // abbreviated language name (Windows Language Name) ex: ENU
78         private string _sConsoleFallbackName; // The culture name for the console fallback UI culture
79         private int    _iInputLanguageHandle=undef;// input language handle
80
81         // Region
82         private String _sRegionName; // (RegionInfo)
83         private String _sLocalizedCountry; // localized country name
84         private String _sEnglishCountry; // english country name (RegionInfo)
85         private String _sNativeCountry; // native country name
86         private String _sISO3166CountryName; // ISO 3166 (RegionInfo), ie: US
87         private String _sISO3166CountryName2; // 3 char ISO 3166 country name 2 2(RegionInfo) ex: USA (ISO)
88         private int    _iGeoId = undef; // GeoId
89
90         // Numbers
91         private String _sPositiveSign; // (user can override) positive sign
92         private String _sNegativeSign; // (user can override) negative sign
93         // (nfi populates these 5, don't have to be = undef)
94         private int _iDigits; // (user can override) number of fractional digits
95         private int _iNegativeNumber; // (user can override) negative number format
96         private int[] _waGrouping; // (user can override) grouping of digits
97         private String _sDecimalSeparator; // (user can override) decimal separator
98         private String _sThousandSeparator; // (user can override) thousands separator
99         private String _sNaN; // Not a Number
100         private String _sPositiveInfinity; // + Infinity
101         private String _sNegativeInfinity; // - Infinity
102
103         // Percent
104         private int _iNegativePercent = undef; // Negative Percent (0-3)
105         private int _iPositivePercent = undef; // Positive Percent (0-11)
106         private String _sPercent; // Percent (%) symbol
107         private String _sPerMille; // PerMille symbol
108
109         // Currency
110         private String _sCurrency; // (user can override) local monetary symbol
111         private String _sIntlMonetarySymbol; // international monetary symbol (RegionInfo)
112         private String _sEnglishCurrency; // English name for this currency
113         private String _sNativeCurrency; // Native name for this currency
114         // (nfi populates these 4, don't have to be = undef)
115         private int _iCurrencyDigits; // (user can override) # local monetary fractional digits
116         private int _iCurrency; // (user can override) positive currency format
117         private int _iNegativeCurrency; // (user can override) negative currency format
118         private int[] _waMonetaryGrouping; // (user can override) monetary grouping of digits
119         private String _sMonetaryDecimal; // (user can override) monetary decimal separator
120         private String _sMonetaryThousand; // (user can override) monetary thousands separator
121
122         // Misc
123         private int _iMeasure = undef; // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
124         private String _sListSeparator; // (user can override) list separator
125
126         // Time
127         private String _sAM1159; // (user can override) AM designator
128         private String _sPM2359; // (user can override) PM designator
129         private String _sTimeSeparator;
130         private volatile String[] _saLongTimes; // (user can override) time format
131         private volatile String[] _saShortTimes; // short time format
132         private volatile String[] _saDurationFormats; // time duration format
133
134         // Calendar specific data
135         private int _iFirstDayOfWeek = undef; // (user can override) first day of week (gregorian really)
136         private int _iFirstWeekOfYear = undef; // (user can override) first week of year (gregorian really)
137         private volatile CalendarId[] _waCalendars; // all available calendar type(s).  The first one is the default calendar
138
139         // Store for specific data about each calendar
140         private CalendarData[] _calendars; // Store for specific calendar data
141
142         // Text information
143         private int _iReadingLayout = undef; // Reading layout data
144         // 0 - Left to right (eg en-US)
145         // 1 - Right to left (eg arabic locales)
146         // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
147         // 3 - Vertical top to bottom with columns proceeding to the right
148
149         // CoreCLR depends on this even though its not exposed publicly.
150
151         private int _iDefaultAnsiCodePage = undef; // default ansi code page ID (ACP)
152         private int _iDefaultOemCodePage = undef; // default oem code page ID (OCP or OEM)
153         private int _iDefaultMacCodePage = undef; // default macintosh code page
154         private int _iDefaultEbcdicCodePage = undef; // default EBCDIC code page
155
156         private int _iLanguage; // locale ID (0409) - NO sort information
157         private bool _bUseOverrides; // use user overrides?
158         private bool _bNeutral; // Flags for the culture (ie: neutral or not right now)
159
160         // Region Name to Culture Name mapping table
161         // (In future would be nice to be in registry or something)
162
163         //Using a property so we avoid creating the dictionary until we need it
164         private static StringStringDictionary RegionNames
165         {
166             get
167             {
168                 if (s_RegionNames == null)
169                 {
170                     StringStringDictionary regionNames = new StringStringDictionary(211 /* prime */);
171
172                     regionNames.Add("029", "en-029");
173                     regionNames.Add("AE", "ar-AE");
174                     regionNames.Add("AF", "prs-AF");
175                     regionNames.Add("AL", "sq-AL");
176                     regionNames.Add("AM", "hy-AM");
177                     regionNames.Add("AR", "es-AR");
178                     regionNames.Add("AT", "de-AT");
179                     regionNames.Add("AU", "en-AU");
180                     regionNames.Add("AZ", "az-Cyrl-AZ");
181                     regionNames.Add("BA", "bs-Latn-BA");
182                     regionNames.Add("BD", "bn-BD");
183                     regionNames.Add("BE", "nl-BE");
184                     regionNames.Add("BG", "bg-BG");
185                     regionNames.Add("BH", "ar-BH");
186                     regionNames.Add("BN", "ms-BN");
187                     regionNames.Add("BO", "es-BO");
188                     regionNames.Add("BR", "pt-BR");
189                     regionNames.Add("BY", "be-BY");
190                     regionNames.Add("BZ", "en-BZ");
191                     regionNames.Add("CA", "en-CA");
192                     regionNames.Add("CH", "it-CH");
193                     regionNames.Add("CL", "es-CL");
194                     regionNames.Add("CN", "zh-CN");
195                     regionNames.Add("CO", "es-CO");
196                     regionNames.Add("CR", "es-CR");
197                     regionNames.Add("CS", "sr-Cyrl-CS");
198                     regionNames.Add("CZ", "cs-CZ");
199                     regionNames.Add("DE", "de-DE");
200                     regionNames.Add("DK", "da-DK");
201                     regionNames.Add("DO", "es-DO");
202                     regionNames.Add("DZ", "ar-DZ");
203                     regionNames.Add("EC", "es-EC");
204                     regionNames.Add("EE", "et-EE");
205                     regionNames.Add("EG", "ar-EG");
206                     regionNames.Add("ES", "es-ES");
207                     regionNames.Add("ET", "am-ET");
208                     regionNames.Add("FI", "fi-FI");
209                     regionNames.Add("FO", "fo-FO");
210                     regionNames.Add("FR", "fr-FR");
211                     regionNames.Add("GB", "en-GB");
212                     regionNames.Add("GE", "ka-GE");
213                     regionNames.Add("GL", "kl-GL");
214                     regionNames.Add("GR", "el-GR");
215                     regionNames.Add("GT", "es-GT");
216                     regionNames.Add("HK", "zh-HK");
217                     regionNames.Add("HN", "es-HN");
218                     regionNames.Add("HR", "hr-HR");
219                     regionNames.Add("HU", "hu-HU");
220                     regionNames.Add("ID", "id-ID");
221                     regionNames.Add("IE", "en-IE");
222                     regionNames.Add("IL", "he-IL");
223                     regionNames.Add("IN", "hi-IN");
224                     regionNames.Add("IQ", "ar-IQ");
225                     regionNames.Add("IR", "fa-IR");
226                     regionNames.Add("IS", "is-IS");
227                     regionNames.Add("IT", "it-IT");
228                     regionNames.Add("IV", "");
229                     regionNames.Add("JM", "en-JM");
230                     regionNames.Add("JO", "ar-JO");
231                     regionNames.Add("JP", "ja-JP");
232                     regionNames.Add("KE", "sw-KE");
233                     regionNames.Add("KG", "ky-KG");
234                     regionNames.Add("KH", "km-KH");
235                     regionNames.Add("KR", "ko-KR");
236                     regionNames.Add("KW", "ar-KW");
237                     regionNames.Add("KZ", "kk-KZ");
238                     regionNames.Add("LA", "lo-LA");
239                     regionNames.Add("LB", "ar-LB");
240                     regionNames.Add("LI", "de-LI");
241                     regionNames.Add("LK", "si-LK");
242                     regionNames.Add("LT", "lt-LT");
243                     regionNames.Add("LU", "lb-LU");
244                     regionNames.Add("LV", "lv-LV");
245                     regionNames.Add("LY", "ar-LY");
246                     regionNames.Add("MA", "ar-MA");
247                     regionNames.Add("MC", "fr-MC");
248                     regionNames.Add("ME", "sr-Latn-ME");
249                     regionNames.Add("MK", "mk-MK");
250                     regionNames.Add("MN", "mn-MN");
251                     regionNames.Add("MO", "zh-MO");
252                     regionNames.Add("MT", "mt-MT");
253                     regionNames.Add("MV", "dv-MV");
254                     regionNames.Add("MX", "es-MX");
255                     regionNames.Add("MY", "ms-MY");
256                     regionNames.Add("NG", "ig-NG");
257                     regionNames.Add("NI", "es-NI");
258                     regionNames.Add("NL", "nl-NL");
259                     regionNames.Add("NO", "nn-NO");
260                     regionNames.Add("NP", "ne-NP");
261                     regionNames.Add("NZ", "en-NZ");
262                     regionNames.Add("OM", "ar-OM");
263                     regionNames.Add("PA", "es-PA");
264                     regionNames.Add("PE", "es-PE");
265                     regionNames.Add("PH", "en-PH");
266                     regionNames.Add("PK", "ur-PK");
267                     regionNames.Add("PL", "pl-PL");
268                     regionNames.Add("PR", "es-PR");
269                     regionNames.Add("PT", "pt-PT");
270                     regionNames.Add("PY", "es-PY");
271                     regionNames.Add("QA", "ar-QA");
272                     regionNames.Add("RO", "ro-RO");
273                     regionNames.Add("RS", "sr-Latn-RS");
274                     regionNames.Add("RU", "ru-RU");
275                     regionNames.Add("RW", "rw-RW");
276                     regionNames.Add("SA", "ar-SA");
277                     regionNames.Add("SE", "sv-SE");
278                     regionNames.Add("SG", "zh-SG");
279                     regionNames.Add("SI", "sl-SI");
280                     regionNames.Add("SK", "sk-SK");
281                     regionNames.Add("SN", "wo-SN");
282                     regionNames.Add("SV", "es-SV");
283                     regionNames.Add("SY", "ar-SY");
284                     regionNames.Add("TH", "th-TH");
285                     regionNames.Add("TJ", "tg-Cyrl-TJ");
286                     regionNames.Add("TM", "tk-TM");
287                     regionNames.Add("TN", "ar-TN");
288                     regionNames.Add("TR", "tr-TR");
289                     regionNames.Add("TT", "en-TT");
290                     regionNames.Add("TW", "zh-TW");
291                     regionNames.Add("UA", "uk-UA");
292                     regionNames.Add("US", "en-US");
293                     regionNames.Add("UY", "es-UY");
294                     regionNames.Add("UZ", "uz-Cyrl-UZ");
295                     regionNames.Add("VE", "es-VE");
296                     regionNames.Add("VN", "vi-VN");
297                     regionNames.Add("YE", "ar-YE");
298                     regionNames.Add("ZA", "af-ZA");
299                     regionNames.Add("ZW", "en-ZW");
300
301                     s_RegionNames = regionNames;
302                 }
303
304                 return s_RegionNames;
305             }
306         }
307
308         // Cache of regions we've already looked up
309         private static volatile StringCultureDataDictionary s_cachedRegions;
310         private static volatile StringStringDictionary s_RegionNames;
311
312         internal static CultureData GetCultureDataForRegion(String cultureName, bool useUserOverride)
313         {
314             // First do a shortcut for Invariant
315             if (String.IsNullOrEmpty(cultureName))
316             {
317                 return CultureData.Invariant;
318             }
319
320             //
321             // First check if GetCultureData() can find it (ie: its a real culture)
322             //
323             CultureData retVal = GetCultureData(cultureName, useUserOverride);
324             if (retVal != null && (retVal.IsNeutralCulture == false)) return retVal;
325
326             //
327             // Not a specific culture, perhaps it's region-only name
328             // (Remember this isn't a core clr path where that's not supported)
329             //
330
331             // If it was neutral remember that so that RegionInfo() can throw the right exception
332             CultureData neutral = retVal;
333
334             // Try the hash table next
335             String hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
336             StringCultureDataDictionary tempHashTable = s_cachedRegions;
337             if (tempHashTable == null)
338             {
339                 // No table yet, make a new one
340                 tempHashTable = new StringCultureDataDictionary();
341             }
342             else
343             {
344                 // Check the hash table
345                 lock (s_lock)
346                 {
347                     tempHashTable.TryGetValue(hashName, out retVal);
348                 }
349                 if (retVal != null)
350                 {
351                     return retVal;
352                 }
353             }
354
355             //
356             // Not found in the hash table, look it up the hard way
357             //
358
359             // If not a valid mapping from the registry we'll have to try the hard coded table
360             if (retVal == null || (retVal.IsNeutralCulture == true))
361             {
362                 // Not a valid mapping, try the hard coded table
363                 string name;
364                 if (RegionNames.TryGetValue(cultureName, out name))
365                 {
366                     // Make sure we can get culture data for it
367                     retVal = GetCultureData(name, useUserOverride);
368                 }
369             }
370
371             // If not found in the hard coded table we'll have to find a culture that works for us
372             if (retVal == null || (retVal.IsNeutralCulture == true))
373             {
374                 retVal = GetCultureDataFromRegionName(cultureName);
375             }
376
377             // If we found one we can use, then cache it for next time
378             if (retVal != null && (retVal.IsNeutralCulture == false))
379             {
380                 // first add it to the cache
381                 lock (s_lock)
382                 {
383                     tempHashTable[hashName] = retVal;
384                 }
385
386                 // Copy the hashtable to the corresponding member variables.  This will potentially overwrite
387                 // new tables simultaneously created by a new thread, but maximizes thread safety.
388                 s_cachedRegions = tempHashTable;
389             }
390             else
391             {
392                 // Unable to find a matching culture/region, return null or neutral
393                 // (regionInfo throws a more specific exception on neutrals)
394                 retVal = neutral;
395             }
396
397             // Return the found culture to use, null, or the neutral culture.
398             return retVal;
399         }
400
401         // Clear our internal caches
402         internal static void ClearCachedData()
403         {
404             s_cachedCultures = null;
405             s_cachedRegions = null;
406         }
407
408         internal static CultureInfo[] GetCultures(CultureTypes types)
409         {
410             // Disable  warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
411 #pragma warning disable 618
412             // Validate flags
413             if ((int)types <= 0 || ((int)types & (int)~(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures |
414                                                         CultureTypes.InstalledWin32Cultures | CultureTypes.UserCustomCulture |
415                                                         CultureTypes.ReplacementCultures | CultureTypes.WindowsOnlyCultures |
416                                                         CultureTypes.FrameworkCultures)) != 0)
417             {
418                 throw new ArgumentOutOfRangeException(nameof(types), 
419                               SR.Format(SR.ArgumentOutOfRange_Range, CultureTypes.NeutralCultures, CultureTypes.FrameworkCultures));
420             }
421
422             // We have deprecated CultureTypes.FrameworkCultures.
423             // When this enum is used, we will enumerate Whidbey framework cultures (for compatibility).
424
425             // We have deprecated CultureTypes.WindowsOnlyCultures.
426             // When this enum is used, we will return an empty array for this enum.
427             if ((types & CultureTypes.WindowsOnlyCultures) != 0)
428             {
429                 // Remove the enum as it is an no-op.
430                 types &= (~CultureTypes.WindowsOnlyCultures);
431             }
432             
433             if (GlobalizationMode.Invariant)
434             {
435                 // in invariant mode we always return invariant culture only from the enumeration
436                 return new CultureInfo[1] { new CultureInfo("") };
437             }
438
439 #pragma warning restore 618
440             return EnumCultures(types);
441         }
442
443         private static CultureData CreateCultureWithInvariantData()
444         {
445             // Make a new culturedata
446             CultureData invariant = new CultureData();
447
448             // Basics
449             // Note that we override the resources since this IS NOT supposed to change (by definition)
450             invariant._bUseOverrides = false;
451             invariant._sRealName = "";                     // Name you passed in (ie: en-US, en, or de-DE_phoneb)
452             invariant._sWindowsName = "";                     // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
453
454             // Identity
455             invariant._sName = "";                     // locale name (ie: en-us)
456             invariant._sParent = "";                     // Parent name (which may be a custom locale/culture)
457             invariant._bNeutral = false;                   // Flags for the culture (ie: neutral or not right now)
458             invariant._sEnglishDisplayName = "Invariant Language (Invariant Country)"; // English pretty name for this locale
459             invariant._sNativeDisplayName = "Invariant Language (Invariant Country)";  // Native pretty name for this locale
460             invariant._sSpecificCulture = "";                     // The culture name to be used in CultureInfo.CreateSpecificCulture()
461
462             // Language
463             invariant._sISO639Language = "iv";                   // ISO 639 Language Name
464             invariant._sISO639Language2 = "ivl";                  // 3 char ISO 639 lang name 2
465             invariant._sLocalizedLanguage = "Invariant Language";   // Display name for this Language
466             invariant._sEnglishLanguage = "Invariant Language";   // English name for this language
467             invariant._sNativeLanguage = "Invariant Language";   // Native name of this language
468             invariant._sAbbrevLang = "IVL";                  // abbreviated language name (Windows Language Name)
469             invariant._sConsoleFallbackName = "";            // The culture name for the console fallback UI culture
470             invariant._iInputLanguageHandle = 0x07F;         // input language handle
471
472             // Region
473             invariant._sRegionName = "IV";                    // (RegionInfo)
474             invariant._sEnglishCountry = "Invariant Country"; // english country name (RegionInfo)
475             invariant._sNativeCountry = "Invariant Country";  // native country name (Windows Only)
476             invariant._sISO3166CountryName = "IV";            // (RegionInfo), ie: US
477             invariant._sISO3166CountryName2 = "ivc";          // 3 char ISO 3166 country name 2 2(RegionInfo)
478             invariant._iGeoId = 244;                          // GeoId (Windows Only)
479
480             // Numbers
481             invariant._sPositiveSign = "+";                    // positive sign
482             invariant._sNegativeSign = "-";                    // negative sign
483             invariant._iDigits = 2;                      // number of fractional digits
484             invariant._iNegativeNumber = 1;                      // negative number format
485             invariant._waGrouping = new int[] { 3 };          // grouping of digits
486             invariant._sDecimalSeparator = ".";                    // decimal separator
487             invariant._sThousandSeparator = ",";                    // thousands separator
488             invariant._sNaN = "NaN";                  // Not a Number
489             invariant._sPositiveInfinity = "Infinity";             // + Infinity
490             invariant._sNegativeInfinity = "-Infinity";            // - Infinity
491
492             // Percent
493             invariant._iNegativePercent = 0;                      // Negative Percent (0-3)
494             invariant._iPositivePercent = 0;                      // Positive Percent (0-11)
495             invariant._sPercent = "%";                    // Percent (%) symbol
496             invariant._sPerMille = "\x2030";               // PerMille symbol
497
498             // Currency
499             invariant._sCurrency = "\x00a4";                // local monetary symbol: for international monetary symbol
500             invariant._sIntlMonetarySymbol = "XDR";                  // international monetary symbol (RegionInfo)
501             invariant._sEnglishCurrency = "International Monetary Fund"; // English name for this currency (Windows Only)
502             invariant._sNativeCurrency = "International Monetary Fund"; // Native name for this currency (Windows Only)
503             invariant._iCurrencyDigits = 2;                      // # local monetary fractional digits
504             invariant._iCurrency = 0;                      // positive currency format
505             invariant._iNegativeCurrency = 0;                      // negative currency format
506             invariant._waMonetaryGrouping = new int[] { 3 };          // monetary grouping of digits
507             invariant._sMonetaryDecimal = ".";                    // monetary decimal separator
508             invariant._sMonetaryThousand = ",";                    // monetary thousands separator
509
510             // Misc
511             invariant._iMeasure = 0;                      // system of measurement 0=metric, 1=US (RegionInfo)
512             invariant._sListSeparator = ",";                    // list separator
513
514             // Time
515             invariant._sTimeSeparator = ":";
516             invariant._sAM1159 = "AM";                   // AM designator
517             invariant._sPM2359 = "PM";                   // PM designator
518             invariant._saLongTimes = new String[] { "HH:mm:ss" };                             // time format
519             invariant._saShortTimes = new String[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
520             invariant._saDurationFormats = new String[] { "HH:mm:ss" };                             // time duration format
521
522
523             // Calendar specific data
524             invariant._iFirstDayOfWeek = 0;                      // first day of week
525             invariant._iFirstWeekOfYear = 0;                      // first week of year
526             invariant._waCalendars = new CalendarId[] { CalendarId.GREGORIAN };       // all available calendar type(s).  The first one is the default calendar
527
528             // Store for specific data about each calendar
529             invariant._calendars = new CalendarData[CalendarData.MAX_CALENDARS];
530             invariant._calendars[0] = CalendarData.Invariant;
531
532             // Text information
533             invariant._iReadingLayout = 0;
534
535             // These are desktop only, not coreclr
536
537             invariant._iLanguage = CultureInfo.LOCALE_INVARIANT;   // locale ID (0409) - NO sort information
538             invariant._iDefaultAnsiCodePage = 1252;         // default ansi code page ID (ACP)
539             invariant._iDefaultOemCodePage = 437;           // default oem code page ID (OCP or OEM)
540             invariant._iDefaultMacCodePage = 10000;         // default macintosh code page
541             invariant._iDefaultEbcdicCodePage = 037;        // default EBCDIC code page
542             
543             if (GlobalizationMode.Invariant)
544             {
545                 invariant._sLocalizedDisplayName = invariant._sNativeDisplayName;
546                 invariant._sLocalizedCountry = invariant._sNativeCountry;
547             }
548
549             return invariant;
550         }
551
552         /////////////////////////////////////////////////////////////////////////
553         // Build our invariant information
554         //
555         // We need an invariant instance, which we build hard-coded
556         /////////////////////////////////////////////////////////////////////////
557         internal static CultureData Invariant
558         {
559             get
560             {
561                 if (s_Invariant == null)
562                 {
563                     // Remember it
564                     s_Invariant = CreateCultureWithInvariantData();
565                 }
566                 return s_Invariant;
567             }
568         }
569         private volatile static CultureData s_Invariant;
570
571         ///////////////
572         // Constructors //
573         ///////////////
574         // Cache of cultures we've already looked up
575         private static volatile StringCultureDataDictionary s_cachedCultures;
576         private static readonly Lock s_lock = new Lock();
577
578         internal static CultureData GetCultureData(String cultureName, bool useUserOverride)
579         {
580             // First do a shortcut for Invariant
581             if (String.IsNullOrEmpty(cultureName))
582             {
583                 return CultureData.Invariant;
584             }
585
586             // Try the hash table first
587             String hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
588             StringCultureDataDictionary tempHashTable = s_cachedCultures;
589             if (tempHashTable == null)
590             {
591                 // No table yet, make a new one
592                 tempHashTable = new StringCultureDataDictionary();
593             }
594             else
595             {
596                 // Check the hash table
597                 bool ret;
598                 CultureData retVal;
599                 lock (s_lock)
600                 {
601                     ret = tempHashTable.TryGetValue(hashName, out retVal);
602                 }
603                 if (ret && retVal != null)
604                 {
605                     return retVal;
606                 }
607             }
608
609             // Not found in the hash table, need to see if we can build one that works for us
610             CultureData culture = CreateCultureData(cultureName, useUserOverride);
611             if (culture == null)
612             {
613                 return null;
614             }
615
616             // Found one, add it to the cache
617             lock (s_lock)
618             {
619                 tempHashTable[hashName] = culture;
620             }
621
622             // Copy the hashtable to the corresponding member variables.  This will potentially overwrite
623             // new tables simultaneously created by a new thread, but maximizes thread safety.
624             s_cachedCultures = tempHashTable;
625
626             return culture;
627         }
628
629         private static unsafe string NormalizeCultureName(string name, out bool isNeutralName)
630         {
631             isNeutralName = true;
632             int i = 0;
633
634             Debug.Assert(name.Length <= LOCALE_NAME_MAX_LENGTH);
635
636             char *pName = stackalloc char[LOCALE_NAME_MAX_LENGTH];
637             bool changed = false;
638
639             while (i < name.Length && name[i] != '-' && name[i] != '_')
640             {
641                 if (name[i] >= 'A' && name[i] <= 'Z')
642                 {
643                     // lowercase characters before '-'
644                     pName[i] = (char) (((int)name[i]) + 0x20);
645                     changed = true;
646                 }
647                 else
648                 {
649                     pName[i] = name[i];
650                 }
651                 i++;
652             }
653
654             if (i < name.Length)
655             {
656                 // this is not perfect to detect the non neutral cultures but it is good enough when we are running in invariant mode
657                 isNeutralName = false;
658             }
659
660             while (i < name.Length)
661             {
662                 if (name[i] >= 'a' && name[i] <= 'z')
663                 {
664                     pName[i] = (char) (((int)name[i]) - 0x20);
665                     changed = true;
666                 }
667                 else
668                 {
669                     pName[i] = name[i];
670                 }
671                 i++;
672             }
673
674             if (changed)
675                 return new string(pName, 0, name.Length);
676             
677             return name;
678         }
679
680         private static CultureData CreateCultureData(string cultureName, bool useUserOverride)
681         {
682             if (GlobalizationMode.Invariant)
683             {
684                 CultureInfo.VerifyCultureName(cultureName, true);
685                 CultureData cd = CreateCultureWithInvariantData();
686                 cd._bUseOverrides = useUserOverride;
687                 cd._sName = NormalizeCultureName(cultureName, out cd._bNeutral);
688                 cd._sRealName = cd._sName;
689                 cd._sWindowsName = cd._sName;
690                 cd._iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED;
691
692                 return cd;
693             }
694
695             CultureData culture = new CultureData();
696             culture._bUseOverrides = useUserOverride;
697             culture._sRealName = cultureName;
698
699             // Ask native code if that one's real
700             if (culture.InitCultureData() == false)
701             {
702                 if (culture.InitCompatibilityCultureData() == false)
703                 {
704                     return null;
705                 }
706             }
707
708             return culture;
709         }
710
711         private bool InitCompatibilityCultureData()
712         {
713             // for compatibility handle the deprecated ids: zh-chs, zh-cht
714             string cultureName = _sRealName;
715
716             string fallbackCultureName;
717             string realCultureName;
718             switch (AnsiToLower(cultureName))
719             {
720                 case "zh-chs":
721                     fallbackCultureName = "zh-Hans";
722                     realCultureName = "zh-CHS";
723                     break;
724                 case "zh-cht":
725                     fallbackCultureName = "zh-Hant";
726                     realCultureName = "zh-CHT";
727                     break;
728                 default:
729                     return false;
730             }
731
732             _sRealName = fallbackCultureName;
733             if (InitCultureData() == false)
734             {
735                 return false;
736             }
737             // fixup our data
738             _sName = realCultureName; // the name that goes back to the user
739             _sParent = fallbackCultureName;
740
741             return true;
742         }
743
744         // We'd rather people use the named version since this doesn't allow custom locales
745         internal static CultureData GetCultureData(int culture, bool bUseUserOverride)
746         {
747             string localeName = null;
748             CultureData retVal = null;
749
750             if (culture == CultureInfo.LOCALE_INVARIANT)
751                 return Invariant;
752             
753             if (GlobalizationMode.Invariant)
754             {
755                 // LCID is not supported in the InvariantMode
756                 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
757             }
758
759             // Convert the lcid to a name, then use that
760             // Note that this will return neutral names (unlike Vista native API)
761             localeName = LCIDToLocaleName(culture);
762
763             if (!String.IsNullOrEmpty(localeName))
764             {
765                 // Valid name, use it
766                 retVal = GetCultureData(localeName, bUseUserOverride);
767             }
768
769             // If not successful, throw
770             if (retVal == null)
771                 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
772
773             // Return the one we found
774             return retVal;
775         }
776
777         ////////////////////////////////////////////////////////////////////////
778         //
779         //  All the accessors
780         //
781         //  Accessors for our data object items
782         //
783         ////////////////////////////////////////////////////////////////////////
784
785         ///////////
786         // Identity //
787         ///////////
788
789         // The real name used to construct the locale (ie: de-DE_phoneb)
790         internal String CultureName
791         {
792             get
793             {
794                 Debug.Assert(_sRealName != null, "[CultureData.CultureName] Expected _sRealName to be populated by already");
795                 // since windows doesn't know about zh-CHS and zh-CHT,
796                 // we leave sRealName == zh-Hanx but we still need to
797                 // pretend that it was zh-CHX.
798                 switch (_sName)
799                 {
800                     case "zh-CHS":
801                     case "zh-CHT":
802                         return _sName;
803                 }
804                 return _sRealName;
805             }
806         }
807
808         // Are overrides enabled?
809         internal bool UseUserOverride
810         {
811             get
812             {
813                 return _bUseOverrides;
814             }
815         }
816
817         // locale name (ie: de-DE, NO sort information)
818         internal String SNAME
819         {
820             get
821             {
822                 if (_sName == null)
823                 {
824                     _sName = String.Empty;
825                 }
826                 return _sName;
827             }
828         }
829
830         // Parent name (which may be a custom locale/culture)
831         internal String SPARENT
832         {
833             get
834             {
835                 if (_sParent == null)
836                 {
837                     // Ask using the real name, so that we get parents of neutrals
838                     _sParent = GetLocaleInfo(_sRealName, LocaleStringData.ParentName);
839                 }
840                 return _sParent;
841             }
842         }
843
844         // Localized pretty name for this locale (ie: Inglis (estados Unitos))
845         internal String SLOCALIZEDDISPLAYNAME
846         {
847             get
848             {
849                 if (_sLocalizedDisplayName == null)
850                 {
851                     if (this.IsSupplementalCustomCulture)
852                     {
853                         if (this.IsNeutralCulture)
854                         {
855                             _sLocalizedDisplayName = this.SNATIVELANGUAGE;
856                         }
857                         else
858                         {
859                             _sLocalizedDisplayName = this.SNATIVEDISPLAYNAME;
860                         }
861                     }
862                     else
863                     {
864                         try
865                         {
866                             const string ZH_CHT = "zh-CHT";
867                             const string ZH_CHS = "zh-CHS";
868
869                             if (SNAME.Equals(ZH_CHT, StringComparison.OrdinalIgnoreCase))
870                             {
871                                 _sLocalizedDisplayName = GetLanguageDisplayName("zh-Hant");
872                             }
873                             else if (SNAME.Equals(ZH_CHS, StringComparison.OrdinalIgnoreCase))
874                             {
875                                 _sLocalizedDisplayName = GetLanguageDisplayName("zh-Hans");
876                             }
877                             else
878                             {
879                                 _sLocalizedDisplayName = GetLanguageDisplayName(SNAME);
880                             }
881                         }
882                         catch (Exception)
883                         {
884                             // do nothing
885                         }
886                     }
887                     // If it hasn't been found (Windows 8 and up), fallback to the system
888                     if (String.IsNullOrEmpty(_sLocalizedDisplayName))
889                     {
890                         // If its neutral use the language name
891                         if (this.IsNeutralCulture)
892                         {
893                             _sLocalizedDisplayName = this.SLOCALIZEDLANGUAGE;
894                         }
895                         else
896                         {
897                             // Usually the UI culture shouldn't be different than what we got from WinRT except 
898                             // if DefaultThreadCurrentUICulture was set
899                             CultureInfo ci;
900
901                             if (CultureInfo.DefaultThreadCurrentUICulture != null &&
902                                 ((ci = GetUserDefaultCulture()) != null) &&
903                                 !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
904                             {
905                                 _sLocalizedDisplayName = this.SNATIVEDISPLAYNAME;
906                             }
907                             else
908                             {
909                                 _sLocalizedDisplayName = GetLocaleInfo(LocaleStringData.LocalizedDisplayName);
910                             }
911                         }
912                     }
913                 }
914
915                 return _sLocalizedDisplayName;
916             }
917         }
918
919         // English pretty name for this locale (ie: English (United States))
920         internal String SENGDISPLAYNAME
921         {
922             get
923             {
924                 if (_sEnglishDisplayName == null)
925                 {
926                     // If its neutral use the language name
927                     if (this.IsNeutralCulture)
928                     {
929                         _sEnglishDisplayName = this.SENGLISHLANGUAGE;
930                         // differentiate the legacy display names
931                         switch (_sName)
932                         {
933                             case "zh-CHS":
934                             case "zh-CHT":
935                                 _sEnglishDisplayName += " Legacy";
936                                 break;
937                         }
938                     }
939                     else
940                     {
941                         _sEnglishDisplayName = GetLocaleInfo(LocaleStringData.EnglishDisplayName);
942
943                         // if it isn't found build one:
944                         if (String.IsNullOrEmpty(_sEnglishDisplayName))
945                         {
946                             // Our existing names mostly look like:
947                             // "English" + "United States" -> "English (United States)"
948                             // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
949                             if (this.SENGLISHLANGUAGE[this.SENGLISHLANGUAGE.Length - 1] == ')')
950                             {
951                                 // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
952                                 _sEnglishDisplayName =
953                                     this.SENGLISHLANGUAGE.Substring(0, _sEnglishLanguage.Length - 1) +
954                                     ", " + this.SENGCOUNTRY + ")";
955                             }
956                             else
957                             {
958                                 // "English" + "United States" -> "English (United States)"
959                                 _sEnglishDisplayName = this.SENGLISHLANGUAGE + " (" + this.SENGCOUNTRY + ")";
960                             }
961                         }
962                     }
963                 }
964                 return _sEnglishDisplayName;
965             }
966         }
967
968         // Native pretty name for this locale (ie: Deutsch (Deutschland))
969         internal String SNATIVEDISPLAYNAME
970         {
971             get
972             {
973                 if (_sNativeDisplayName == null)
974                 {
975                     // If its neutral use the language name
976                     if (this.IsNeutralCulture)
977                     {
978                         _sNativeDisplayName = this.SNATIVELANGUAGE;
979                         // differentiate the legacy display names
980                         switch (_sName)
981                         {
982                             case "zh-CHS":
983                                 _sNativeDisplayName += " \u65E7\u7248";
984                                 break;
985                             case "zh-CHT":
986                                 _sNativeDisplayName += " \u820A\u7248";
987                                 break;
988                         }
989                     }
990                     else
991                     {
992                         _sNativeDisplayName = GetLocaleInfo(LocaleStringData.NativeDisplayName);
993
994                         // if it isn't found build one:
995                         if (String.IsNullOrEmpty(_sNativeDisplayName))
996                         {
997                             // These should primarily be "Deutsch (Deutschland)" type names
998                             _sNativeDisplayName = this.SNATIVELANGUAGE + " (" + this.SNATIVECOUNTRY + ")";
999                         }
1000                     }
1001                 }
1002                 return _sNativeDisplayName;
1003             }
1004         }
1005
1006         // The culture name to be used in CultureInfo.CreateSpecificCulture()
1007         internal string SSPECIFICCULTURE
1008         {
1009             get
1010             {
1011                 // This got populated during the culture initialization
1012                 Debug.Assert(_sSpecificCulture != null, "[CultureData.SSPECIFICCULTURE] Expected this.sSpecificCulture to be populated by culture data initialization already");
1013                 return _sSpecificCulture;
1014             }
1015         }
1016
1017         /////////////
1018         // Language //
1019         /////////////
1020
1021         // iso 639 language name, ie: en
1022         internal String SISO639LANGNAME
1023         {
1024             get
1025             {
1026                 if (_sISO639Language == null)
1027                 {
1028                     _sISO639Language = GetLocaleInfo(LocaleStringData.Iso639LanguageTwoLetterName);
1029                 }
1030                 return _sISO639Language;
1031             }
1032         }
1033
1034         // iso 639 language name, ie: eng
1035         internal string SISO639LANGNAME2
1036         {
1037             get
1038             {
1039                 if (_sISO639Language2 == null)
1040                 {
1041                     _sISO639Language2 = GetLocaleInfo(LocaleStringData.Iso639LanguageThreeLetterName);
1042                 }
1043                 return _sISO639Language2;
1044             }
1045         }
1046
1047         // abbreviated windows language name (ie: enu) (non-standard, avoid this)
1048         internal string SABBREVLANGNAME
1049         {
1050             get
1051             {
1052                 if (_sAbbrevLang == null)
1053                 {
1054                     _sAbbrevLang = GetThreeLetterWindowsLanguageName(_sRealName);
1055                 }
1056                 return _sAbbrevLang;
1057             }
1058         }
1059
1060         // Localized name for this language (Windows Only) ie: Inglis
1061         // This is only valid for Windows 8 and higher neutrals:
1062         internal String SLOCALIZEDLANGUAGE
1063         {
1064             get
1065             {
1066                 if (_sLocalizedLanguage == null)
1067                 {
1068                     // Usually the UI culture shouldn't be different than what we got from WinRT except 
1069                     // if DefaultThreadCurrentUICulture was set
1070                     CultureInfo ci;
1071
1072                     if (CultureInfo.DefaultThreadCurrentUICulture != null &&
1073                         ((ci = GetUserDefaultCulture()) != null) &&
1074                         !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name))
1075                     {
1076                         _sLocalizedLanguage = SNATIVELANGUAGE;
1077                     }
1078                     else
1079                     {
1080                         _sLocalizedLanguage = GetLocaleInfo(LocaleStringData.LocalizedLanguageName);
1081                     }
1082                 }
1083
1084                 return _sLocalizedLanguage;
1085             }
1086         }
1087
1088         // English name for this language (Windows Only) ie: German
1089         internal String SENGLISHLANGUAGE
1090         {
1091             get
1092             {
1093                 if (_sEnglishLanguage == null)
1094                 {
1095                     _sEnglishLanguage = GetLocaleInfo(LocaleStringData.EnglishLanguageName);
1096                 }
1097                 return _sEnglishLanguage;
1098             }
1099         }
1100
1101         // Native name of this language (Windows Only) ie: Deutsch
1102         internal String SNATIVELANGUAGE
1103         {
1104             get
1105             {
1106                 if (_sNativeLanguage == null)
1107                 {
1108                     _sNativeLanguage = GetLocaleInfo(LocaleStringData.NativeLanguageName);
1109                 }
1110                 return _sNativeLanguage;
1111             }
1112         }
1113
1114         ///////////
1115         // Region //
1116         ///////////
1117
1118         // region name (eg US)
1119         internal String SREGIONNAME
1120         {
1121             get
1122             {
1123                 if (_sRegionName == null)
1124                 {
1125                     _sRegionName = GetLocaleInfo(LocaleStringData.Iso3166CountryName);
1126                 }
1127                 return _sRegionName;
1128             }
1129         }
1130
1131         internal int IGEOID
1132         {
1133             get
1134             {
1135                 if (_iGeoId == undef)
1136                 {
1137                     _iGeoId = GetGeoId(_sRealName);
1138                 }
1139                 return _iGeoId;
1140             }
1141         }
1142
1143         // localized name for the country
1144         internal string SLOCALIZEDCOUNTRY
1145         {
1146             get
1147             {
1148                 if (_sLocalizedCountry == null)
1149                 {
1150                     try
1151                     {
1152                         _sLocalizedCountry = GetRegionDisplayName(SISO3166CTRYNAME);
1153                     }
1154                     catch (Exception)
1155                     {
1156                         // do nothing. we'll fallback 
1157                     }
1158
1159                     if (_sLocalizedCountry == null)
1160                     {
1161                         _sLocalizedCountry = SNATIVECOUNTRY;
1162                     }
1163                 }
1164                 return _sLocalizedCountry;
1165             }
1166         }
1167
1168         // english country name (RegionInfo) ie: Germany
1169         internal String SENGCOUNTRY
1170         {
1171             get
1172             {
1173                 if (_sEnglishCountry == null)
1174                 {
1175                     _sEnglishCountry = GetLocaleInfo(LocaleStringData.EnglishCountryName);
1176                 }
1177                 return _sEnglishCountry;
1178             }
1179         }
1180
1181         // native country name (RegionInfo) ie: Deutschland
1182         internal String SNATIVECOUNTRY
1183         {
1184             get
1185             {
1186                 if (_sNativeCountry == null)
1187                 {
1188                     _sNativeCountry = GetLocaleInfo(LocaleStringData.NativeCountryName);
1189                 }
1190                 return _sNativeCountry;
1191             }
1192         }
1193
1194         // ISO 3166 Country Name
1195         internal String SISO3166CTRYNAME
1196         {
1197             get
1198             {
1199                 if (_sISO3166CountryName == null)
1200                 {
1201                     _sISO3166CountryName = GetLocaleInfo(LocaleStringData.Iso3166CountryName);
1202                 }
1203                 return _sISO3166CountryName;
1204             }
1205         }
1206
1207         // 3 letter ISO 3166 country code
1208         internal String SISO3166CTRYNAME2
1209         {
1210             get
1211             {
1212                 if (_sISO3166CountryName2 == null)
1213                 {
1214                     _sISO3166CountryName2 = GetLocaleInfo(LocaleStringData.Iso3166CountryName2);
1215                 }
1216                 return _sISO3166CountryName2;
1217             }
1218         }
1219
1220         internal int IINPUTLANGUAGEHANDLE
1221         {
1222             get
1223             {
1224                 if (_iInputLanguageHandle == undef)
1225                 {
1226                     if (IsSupplementalCustomCulture)
1227                     {
1228                         _iInputLanguageHandle = 0x0409;
1229                     }
1230                     else
1231                     {
1232                         // Input Language is same as LCID for built-in cultures
1233                         _iInputLanguageHandle = this.ILANGUAGE;
1234                     }
1235                 }
1236                 return _iInputLanguageHandle;
1237             }
1238         }
1239
1240         // Console fallback name (ie: locale to use for console apps for unicode-only locales)
1241         internal string SCONSOLEFALLBACKNAME
1242         {
1243             get
1244             {
1245                 if (_sConsoleFallbackName == null)
1246                 {
1247                     _sConsoleFallbackName = GetConsoleFallbackName(_sRealName);
1248                 }
1249                 return _sConsoleFallbackName;
1250             }
1251         }
1252
1253         // (user can override) grouping of digits
1254         internal int[] WAGROUPING
1255         {
1256             get
1257             {
1258                 if (_waGrouping == null)
1259                 {
1260                     _waGrouping = GetLocaleInfo(LocaleGroupingData.Digit);
1261                 }
1262                 return _waGrouping;
1263             }
1264         }
1265
1266
1267         //                internal String sDecimalSeparator        ; // (user can override) decimal separator
1268         //                internal String sThousandSeparator       ; // (user can override) thousands separator
1269
1270         // Not a Number
1271         internal String SNAN
1272         {
1273             get
1274             {
1275                 if (_sNaN == null)
1276                 {
1277                     _sNaN = GetLocaleInfo(LocaleStringData.NaNSymbol);
1278                 }
1279                 return _sNaN;
1280             }
1281         }
1282
1283         // + Infinity
1284         internal String SPOSINFINITY
1285         {
1286             get
1287             {
1288                 if (_sPositiveInfinity == null)
1289                 {
1290                     _sPositiveInfinity = GetLocaleInfo(LocaleStringData.PositiveInfinitySymbol);
1291                 }
1292                 return _sPositiveInfinity;
1293             }
1294         }
1295
1296         // - Infinity
1297         internal String SNEGINFINITY
1298         {
1299             get
1300             {
1301                 if (_sNegativeInfinity == null)
1302                 {
1303                     _sNegativeInfinity = GetLocaleInfo(LocaleStringData.NegativeInfinitySymbol);
1304                 }
1305                 return _sNegativeInfinity;
1306             }
1307         }
1308
1309
1310         ////////////
1311         // Percent //
1312         ///////////
1313
1314         // Negative Percent (0-3)
1315         internal int INEGATIVEPERCENT
1316         {
1317             get
1318             {
1319                 if (_iNegativePercent == undef)
1320                 {
1321                     // Note that <= Windows Vista this is synthesized by native code
1322                     _iNegativePercent = GetLocaleInfo(LocaleNumberData.NegativePercentFormat);
1323                 }
1324                 return _iNegativePercent;
1325             }
1326         }
1327
1328         // Positive Percent (0-11)
1329         internal int IPOSITIVEPERCENT
1330         {
1331             get
1332             {
1333                 if (_iPositivePercent == undef)
1334                 {
1335                     // Note that <= Windows Vista this is synthesized by native code
1336                     _iPositivePercent = GetLocaleInfo(LocaleNumberData.PositivePercentFormat);
1337                 }
1338                 return _iPositivePercent;
1339             }
1340         }
1341
1342         // Percent (%) symbol
1343         internal String SPERCENT
1344         {
1345             get
1346             {
1347                 if (_sPercent == null)
1348                 {
1349                     _sPercent = GetLocaleInfo(LocaleStringData.PercentSymbol);
1350                 }
1351                 return _sPercent;
1352             }
1353         }
1354
1355         // PerMille symbol
1356         internal String SPERMILLE
1357         {
1358             get
1359             {
1360                 if (_sPerMille == null)
1361                 {
1362                     _sPerMille = GetLocaleInfo(LocaleStringData.PerMilleSymbol);
1363                 }
1364                 return _sPerMille;
1365             }
1366         }
1367
1368         /////////////
1369         // Currency //
1370         /////////////
1371
1372         // (user can override) local monetary symbol, eg: $
1373         internal String SCURRENCY
1374         {
1375             get
1376             {
1377                 if (_sCurrency == null)
1378                 {
1379                     _sCurrency = GetLocaleInfo(LocaleStringData.MonetarySymbol);
1380                 }
1381                 return _sCurrency;
1382             }
1383         }
1384
1385         // international monetary symbol (RegionInfo), eg: USD
1386         internal String SINTLSYMBOL
1387         {
1388             get
1389             {
1390                 if (_sIntlMonetarySymbol == null)
1391                 {
1392                     _sIntlMonetarySymbol = GetLocaleInfo(LocaleStringData.Iso4217MonetarySymbol);
1393                 }
1394                 return _sIntlMonetarySymbol;
1395             }
1396         }
1397
1398         // English name for this currency (RegionInfo), eg: US Dollar
1399         internal String SENGLISHCURRENCY
1400         {
1401             get
1402             {
1403                 if (_sEnglishCurrency == null)
1404                 {
1405                     _sEnglishCurrency = GetLocaleInfo(LocaleStringData.CurrencyEnglishName);
1406                 }
1407                 return _sEnglishCurrency;
1408             }
1409         }
1410
1411         // Native name for this currency (RegionInfo), eg: Schweiz Frank
1412         internal String SNATIVECURRENCY
1413         {
1414             get
1415             {
1416                 if (_sNativeCurrency == null)
1417                 {
1418                     _sNativeCurrency = GetLocaleInfo(LocaleStringData.CurrencyNativeName);
1419                 }
1420                 return _sNativeCurrency;
1421             }
1422         }
1423
1424         //                internal int iCurrencyDigits          ; // (user can override) # local monetary fractional digits
1425         //                internal int iCurrency                ; // (user can override) positive currency format
1426         //                internal int iNegativeCurrency        ; // (user can override) negative currency format
1427
1428         // (user can override) monetary grouping of digits
1429         internal int[] WAMONGROUPING
1430         {
1431             get
1432             {
1433                 if (_waMonetaryGrouping == null)
1434                 {
1435                     _waMonetaryGrouping = GetLocaleInfo(LocaleGroupingData.Monetary);
1436                 }
1437                 return _waMonetaryGrouping;
1438             }
1439         }
1440
1441         // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
1442         internal int IMEASURE
1443         {
1444             get
1445             {
1446                 if (_iMeasure == undef)
1447                 {
1448                     _iMeasure = GetLocaleInfo(LocaleNumberData.MeasurementSystem);
1449                 }
1450                 return _iMeasure;
1451             }
1452         }
1453
1454         // (user can override) list Separator
1455         internal String SLIST
1456         {
1457             get
1458             {
1459                 if (_sListSeparator == null)
1460                 {
1461                     _sListSeparator = GetLocaleInfo(LocaleStringData.ListSeparator);
1462                 }
1463                 return _sListSeparator;
1464             }
1465         }
1466
1467
1468         ////////////////////////////
1469         // Calendar/Time (Gregorian) //
1470         ////////////////////////////
1471
1472         // (user can override) AM designator
1473         internal String SAM1159
1474         {
1475             get
1476             {
1477                 if (_sAM1159 == null)
1478                 {
1479                     _sAM1159 = GetLocaleInfo(LocaleStringData.AMDesignator);
1480                 }
1481                 return _sAM1159;
1482             }
1483         }
1484
1485         // (user can override) PM designator
1486         internal String SPM2359
1487         {
1488             get
1489             {
1490                 if (_sPM2359 == null)
1491                 {
1492                     _sPM2359 = GetLocaleInfo(LocaleStringData.PMDesignator);
1493                 }
1494                 return _sPM2359;
1495             }
1496         }
1497
1498         // (user can override) time format
1499         internal String[] LongTimes
1500         {
1501             get
1502             {
1503                 if (_saLongTimes == null)
1504                 {
1505                     Debug.Assert(!GlobalizationMode.Invariant);
1506
1507                     String[] longTimes = GetTimeFormats();
1508                     if (longTimes == null || longTimes.Length == 0)
1509                     {
1510                         _saLongTimes = Invariant._saLongTimes;
1511                     }
1512                     else
1513                     {
1514                         _saLongTimes = longTimes;
1515                     }
1516                 }
1517                 return _saLongTimes;
1518             }
1519         }
1520
1521         // short time format
1522         // Short times (derived from long times format)
1523         // TODO: NLS Arrowhead -  On Windows 7 we should have short times so this isn't necessary
1524         internal String[] ShortTimes
1525         {
1526             get
1527             {
1528                 if (_saShortTimes == null)
1529                 {
1530                     Debug.Assert(!GlobalizationMode.Invariant);
1531
1532                     // Try to get the short times from the OS/culture.dll
1533                     String[] shortTimes = null;
1534                     shortTimes = GetShortTimeFormats();
1535
1536                     if (shortTimes == null || shortTimes.Length == 0)
1537                     {
1538                         //
1539                         // If we couldn't find short times, then compute them from long times
1540                         // (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
1541                         //
1542                         shortTimes = DeriveShortTimesFromLong();
1543                     }
1544
1545                     /* The above logic doesn't make sense on Mac, since the OS can provide us a "short time pattern".
1546                      * currently this is the 4th element in the array returned by LongTimes.  We'll add this to our array
1547                      * if it doesn't exist.
1548                      */
1549                     shortTimes = AdjustShortTimesForMac(shortTimes);
1550
1551                     // Found short times, use them
1552                     _saShortTimes = shortTimes;
1553                 }
1554                 return _saShortTimes;
1555             }
1556         }
1557
1558         private string[] AdjustShortTimesForMac(string[] shortTimes)
1559         {
1560             return shortTimes;
1561         }
1562
1563         private string[] DeriveShortTimesFromLong()
1564         {
1565             // Our logic is to look for h,H,m,s,t.  If we find an s, then we check the string
1566             // between it and the previous marker, if any.  If its a short, unescaped separator,
1567             // then we don't retain that part.
1568             // We then check after the ss and remove anything before the next h,H,m,t...
1569             string[] shortTimes = new string[LongTimes.Length];
1570
1571             for (int i = 0; i < LongTimes.Length; i++)
1572             {
1573                 shortTimes[i] = StripSecondsFromPattern(LongTimes[i]);
1574             }
1575             return shortTimes;
1576         }
1577
1578         private static string StripSecondsFromPattern(string time)
1579         {
1580             bool bEscape = false;
1581             int iLastToken = -1;
1582
1583             // Find the seconds
1584             for (int j = 0; j < time.Length; j++)
1585             {
1586                 // Change escape mode?
1587                 if (time[j] == '\'')
1588                 {
1589                     // Continue
1590                     bEscape = !bEscape;
1591                     continue;
1592                 }
1593
1594                 // See if there was a single \
1595                 if (time[j] == '\\')
1596                 {
1597                     // Skip next char
1598                     j++;
1599                     continue;
1600                 }
1601
1602                 if (bEscape)
1603                 {
1604                     continue;
1605                 }
1606
1607                 switch (time[j])
1608                 {
1609                     // Check for seconds
1610                     case 's':
1611                         // Found seconds, see if there was something unescaped and short between
1612                         // the last marker and the seconds.  Windows says separator can be a
1613                         // maximum of three characters (without null)
1614                         // If 1st or last characters were ', then ignore it
1615                         if ((j - iLastToken) <= 4 && (j - iLastToken) > 1 &&
1616                             (time[iLastToken + 1] != '\'') &&
1617                             (time[j - 1] != '\''))
1618                         {
1619                             // There was something there we want to remember
1620                             if (iLastToken >= 0)
1621                             {
1622                                 j = iLastToken + 1;
1623                             }
1624                         }
1625
1626                         bool containsSpace;
1627                         int endIndex = GetIndexOfNextTokenAfterSeconds(time, j, out containsSpace);
1628
1629                         string sep;
1630
1631                         if (containsSpace)
1632                         {
1633                             sep = " ";
1634                         }
1635                         else
1636                         {
1637                             sep = "";
1638                         }
1639
1640                         time = time.Substring(0, j) + sep + time.Substring(endIndex);
1641                         break;
1642                     case 'm':
1643                     case 'H':
1644                     case 'h':
1645                         iLastToken = j;
1646                         break;
1647                 }
1648             }
1649             return time;
1650         }
1651
1652         private static int GetIndexOfNextTokenAfterSeconds(string time, int index, out bool containsSpace)
1653         {
1654             bool bEscape = false;
1655             containsSpace = false;
1656             for (; index < time.Length; index++)
1657             {
1658                 switch (time[index])
1659                 {
1660                     case '\'':
1661                         bEscape = !bEscape;
1662                         continue;
1663                     case '\\':
1664                         index++;
1665                         if (time[index] == ' ')
1666                         {
1667                             containsSpace = true;
1668                         }
1669                         continue;
1670                     case ' ':
1671                         containsSpace = true;
1672                         break;
1673                     case 't':
1674                     case 'm':
1675                     case 'H':
1676                     case 'h':
1677                         if (bEscape)
1678                         {
1679                             continue;
1680                         }
1681                         return index;
1682                 }
1683             }
1684             containsSpace = false;
1685             return index;
1686         }
1687
1688         // (user can override) first day of week
1689         internal int IFIRSTDAYOFWEEK
1690         {
1691             get
1692             {
1693                 if (_iFirstDayOfWeek == undef)
1694                 {
1695                     _iFirstDayOfWeek = GetFirstDayOfWeek();
1696                 }
1697                 return _iFirstDayOfWeek;
1698             }
1699         }
1700
1701         // (user can override) first week of year
1702         internal int IFIRSTWEEKOFYEAR
1703         {
1704             get
1705             {
1706                 if (_iFirstWeekOfYear == undef)
1707                 {
1708                     _iFirstWeekOfYear = GetLocaleInfo(LocaleNumberData.FirstWeekOfYear);
1709                 }
1710                 return _iFirstWeekOfYear;
1711             }
1712         }
1713
1714         // (user can override default only) short date format
1715         internal String[] ShortDates(CalendarId calendarId)
1716         {
1717             return GetCalendar(calendarId).saShortDates;
1718         }
1719
1720         // (user can override default only) long date format
1721         internal String[] LongDates(CalendarId calendarId)
1722         {
1723             return GetCalendar(calendarId).saLongDates;
1724         }
1725
1726         // (user can override) date year/month format.
1727         internal String[] YearMonths(CalendarId calendarId)
1728         {
1729             return GetCalendar(calendarId).saYearMonths;
1730         }
1731
1732         // day names
1733         internal string[] DayNames(CalendarId calendarId)
1734         {
1735             return GetCalendar(calendarId).saDayNames;
1736         }
1737
1738         // abbreviated day names
1739         internal string[] AbbreviatedDayNames(CalendarId calendarId)
1740         {
1741             // Get abbreviated day names for this calendar from the OS if necessary
1742             return GetCalendar(calendarId).saAbbrevDayNames;
1743         }
1744
1745         // The super short day names
1746         internal string[] SuperShortDayNames(CalendarId calendarId)
1747         {
1748             return GetCalendar(calendarId).saSuperShortDayNames;
1749         }
1750
1751         // month names
1752         internal string[] MonthNames(CalendarId calendarId)
1753         {
1754             return GetCalendar(calendarId).saMonthNames;
1755         }
1756
1757         // Genitive month names
1758         internal string[] GenitiveMonthNames(CalendarId calendarId)
1759         {
1760             return GetCalendar(calendarId).saMonthGenitiveNames;
1761         }
1762
1763         // month names
1764         internal string[] AbbreviatedMonthNames(CalendarId calendarId)
1765         {
1766             return GetCalendar(calendarId).saAbbrevMonthNames;
1767         }
1768
1769         // Genitive month names
1770         internal string[] AbbreviatedGenitiveMonthNames(CalendarId calendarId)
1771         {
1772             return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
1773         }
1774
1775         // Leap year month names
1776         // Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
1777         // the non-leap names skip the 7th name in the normal month name array
1778         internal string[] LeapYearMonthNames(CalendarId calendarId)
1779         {
1780             return GetCalendar(calendarId).saLeapYearMonthNames;
1781         }
1782
1783         // month/day format (single string, no override)
1784         internal String MonthDay(CalendarId calendarId)
1785         {
1786             return GetCalendar(calendarId).sMonthDay;
1787         }
1788
1789
1790
1791         /////////////
1792         // Calendars //
1793         /////////////
1794
1795         // all available calendar type(s), The first one is the default calendar.
1796         internal CalendarId[] CalendarIds
1797         {
1798             get
1799             {
1800                 if (_waCalendars == null)
1801                 {
1802                     // We pass in an array of ints, and native side fills it up with count calendars.
1803                     // We then have to copy that list to a new array of the right size.
1804                     // Default calendar should be first
1805                     CalendarId[] calendars = new CalendarId[23];
1806                     Debug.Assert(_sWindowsName != null, "[CultureData.CalendarIds] Expected _sWindowsName to be populated by already");
1807                     int count = CalendarData.GetCalendars(_sWindowsName, _bUseOverrides, calendars);
1808
1809                     // See if we had a calendar to add.
1810                     if (count == 0)
1811                     {
1812                         // Failed for some reason, just grab Gregorian from Invariant
1813                         _waCalendars = Invariant._waCalendars;
1814                     }
1815                     else
1816                     {
1817                         // The OS may not return calendar 4 for zh-TW, but we've always allowed it.
1818                         // TODO: Is this hack necessary long-term?
1819                         if (_sWindowsName == "zh-TW")
1820                         {
1821                             bool found = false;
1822
1823                             // Do we need to insert calendar 4?
1824                             for (int i = 0; i < count; i++)
1825                             {
1826                                 // Stop if we found calendar four
1827                                 if (calendars[i] == CalendarId.TAIWAN)
1828                                 {
1829                                     found = true;
1830                                     break;
1831                                 }
1832                             }
1833
1834                             // If not found then insert it
1835                             if (!found)
1836                             {
1837                                 // Insert it as the 2nd calendar
1838                                 count++;
1839                                 // Copy them from the 2nd position to the end, -1 for skipping 1st, -1 for one being added.
1840                                 Array.Copy(calendars, 1, calendars, 2, 23 - 1 - 1);
1841                                 calendars[1] = CalendarId.TAIWAN;
1842                             }
1843                         }
1844
1845                         // It worked, remember the list
1846                         CalendarId[] temp = new CalendarId[count];
1847                         Array.Copy(calendars, temp, count);
1848
1849                         // Want 1st calendar to be default
1850                         // Prior to Vista the enumeration didn't have default calendar first
1851                         if (temp.Length > 1)
1852                         {
1853                             CalendarId i = (CalendarId)GetLocaleInfo(LocaleNumberData.CalendarType);
1854                             if (temp[1] == i)
1855                             {
1856                                 temp[1] = temp[0];
1857                                 temp[0] = i;
1858                             }
1859                         }
1860
1861                         _waCalendars = temp;
1862                     }
1863                 }
1864
1865                 return _waCalendars;
1866             }
1867         }
1868
1869         // Native calendar names.  index of optional calendar - 1, empty if no optional calendar at that number
1870         internal string CalendarName(CalendarId calendarId)
1871         {
1872             // Get the calendar
1873             return GetCalendar(calendarId).sNativeName;
1874         }
1875
1876         internal CalendarData GetCalendar(CalendarId calendarId)
1877         {
1878             Debug.Assert(calendarId > 0 && calendarId <= CalendarId.LAST_CALENDAR,
1879                 "[CultureData.GetCalendar] Expect calendarId to be in a valid range");
1880
1881             // arrays are 0 based, calendarIds are 1 based
1882             int calendarIndex = (int)calendarId - 1;
1883
1884             // Have to have calendars
1885             if (_calendars == null)
1886             {
1887                 _calendars = new CalendarData[CalendarData.MAX_CALENDARS];
1888             }
1889
1890             // we need the following local variable to avoid returning null
1891             // when another thread creates a new array of CalendarData (above)
1892             // right after we insert the newly created CalendarData (below)
1893             CalendarData calendarData = _calendars[calendarIndex];
1894             // Make sure that calendar has data
1895             if (calendarData == null)
1896             {
1897                 Debug.Assert(_sWindowsName != null, "[CultureData.GetCalendar] Expected _sWindowsName to be populated by already");
1898                 calendarData = new CalendarData(_sWindowsName, calendarId, this.UseUserOverride);
1899                 _calendars[calendarIndex] = calendarData;
1900             }
1901
1902             return calendarData;
1903         }
1904
1905         ///////////////////
1906         // Text Information //
1907         ///////////////////
1908
1909         // IsRightToLeft
1910         internal bool IsRightToLeft
1911         {
1912             get
1913             {
1914                 // Returns one of the following 4 reading layout values:
1915                 // 0 - Left to right (eg en-US)
1916                 // 1 - Right to left (eg arabic locales)
1917                 // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
1918                 // 3 - Vertical top to bottom with columns proceeding to the right
1919                 return (this.IREADINGLAYOUT == 1);
1920             }
1921         }
1922
1923         // IREADINGLAYOUT
1924         // Returns one of the following 4 reading layout values:
1925         // 0 - Left to right (eg en-US)
1926         // 1 - Right to left (eg arabic locales)
1927         // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
1928         // 3 - Vertical top to bottom with columns proceeding to the right
1929         //
1930         // If exposed as a public API, we'd have an enum with those 4 values
1931         private int IREADINGLAYOUT
1932         {
1933             get
1934             {
1935                 if (_iReadingLayout == undef)
1936                 {
1937                     Debug.Assert(_sRealName != null, "[CultureData.IsRightToLeft] Expected _sRealName to be populated by already");
1938                     _iReadingLayout = GetLocaleInfo(LocaleNumberData.ReadingLayout);
1939                 }
1940
1941                 return (_iReadingLayout);
1942             }
1943         }
1944
1945         // The TextInfo name never includes that alternate sort and is always specific
1946         // For customs, it uses the SortLocale (since the textinfo is not exposed in Win7)
1947         // en -> en-US
1948         // en-US -> en-US
1949         // fj (custom neutral) -> en-US (assuming that en-US is the sort locale for fj)
1950         // fj_FJ (custom specific) -> en-US (assuming that en-US is the sort locale for fj-FJ)
1951         // es-ES_tradnl -> es-ES
1952         internal String STEXTINFO               // Text info name to use for text information
1953         {
1954             get
1955             {
1956                 // Note: Custom cultures might point at another culture's textinfo, however windows knows how
1957                 // to redirect it to the desired textinfo culture, so this is OK.
1958                 Debug.Assert(_sRealName != null, "[CultureData.STEXTINFO] Expected _sRealName to be populated by already");
1959                 return (_sRealName);
1960             }
1961         }
1962
1963         // Compare info name (including sorting key) to use if custom
1964         internal String SCOMPAREINFO
1965         {
1966             get
1967             {
1968                 Debug.Assert(_sRealName != null, "[CultureData.SCOMPAREINFO] Expected _sRealName to be populated by already");
1969                 return (_sRealName);
1970             }
1971         }
1972
1973         internal bool IsSupplementalCustomCulture
1974         {
1975             get
1976             {
1977                 return IsCustomCultureId(this.ILANGUAGE);
1978             }
1979         }
1980
1981         internal int IDEFAULTANSICODEPAGE   // default ansi code page ID (ACP)
1982         {
1983             get
1984             {
1985                 if (_iDefaultAnsiCodePage == undef)
1986                 {
1987                     _iDefaultAnsiCodePage = GetAnsiCodePage(_sRealName);
1988                 }
1989                 return _iDefaultAnsiCodePage;
1990             }
1991         }
1992
1993         internal int IDEFAULTOEMCODEPAGE   // default oem code page ID (OCP or OEM)
1994         {
1995             get
1996             {
1997                 if (_iDefaultOemCodePage == undef)
1998                 {
1999                     _iDefaultOemCodePage = GetOemCodePage(_sRealName);
2000                 }
2001                 return _iDefaultOemCodePage;
2002             }
2003         }
2004
2005         internal int IDEFAULTMACCODEPAGE   // default macintosh code page
2006         {
2007             get
2008             {
2009                 if (_iDefaultMacCodePage == undef)
2010                 {
2011                     _iDefaultMacCodePage = GetMacCodePage(_sRealName);
2012                 }
2013                 return _iDefaultMacCodePage;
2014             }
2015         }
2016
2017         internal int IDEFAULTEBCDICCODEPAGE   // default EBCDIC code page
2018         {
2019             get
2020             {
2021                 if (_iDefaultEbcdicCodePage == undef)
2022                 {
2023                     _iDefaultEbcdicCodePage = GetEbcdicCodePage(_sRealName);
2024                 }
2025                 return _iDefaultEbcdicCodePage;
2026             }
2027         }
2028
2029         internal int ILANGUAGE
2030         {
2031             get
2032             {
2033                 if (_iLanguage == 0)
2034                 {
2035                     Debug.Assert(_sRealName != null, "[CultureData.ILANGUAGE] Expected this.sRealName to be populated already");
2036                     _iLanguage = LocaleNameToLCID(_sRealName);
2037                 }
2038                 return _iLanguage;
2039             }
2040         }
2041
2042         internal bool IsNeutralCulture
2043         {
2044             get
2045             {
2046                 // InitCultureData told us if we're neutral or not
2047                 return _bNeutral;
2048             }
2049         }
2050
2051         internal bool IsInvariantCulture
2052         {
2053             get
2054             {
2055                 return String.IsNullOrEmpty(this.SNAME);
2056             }
2057         }
2058
2059         // Get an instance of our default calendar
2060         internal Calendar DefaultCalendar
2061         {
2062             get
2063             {
2064                 if (GlobalizationMode.Invariant)
2065                 {
2066                     return CultureInfo.GetCalendarInstance(CalendarIds[0]);
2067                 }
2068
2069                 CalendarId defaultCalId = (CalendarId)GetLocaleInfo(LocaleNumberData.CalendarType);
2070
2071                 if (defaultCalId == 0)
2072                 {
2073                     defaultCalId = this.CalendarIds[0];
2074                 }
2075
2076                 return CultureInfo.GetCalendarInstance(defaultCalId);
2077             }
2078         }
2079
2080         // All of our era names
2081         internal String[] EraNames(CalendarId calendarId)
2082         {
2083             Debug.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
2084
2085             return this.GetCalendar(calendarId).saEraNames;
2086         }
2087
2088         internal String[] AbbrevEraNames(CalendarId calendarId)
2089         {
2090             Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
2091
2092             return this.GetCalendar(calendarId).saAbbrevEraNames;
2093         }
2094
2095         internal String[] AbbreviatedEnglishEraNames(CalendarId calendarId)
2096         {
2097             Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
2098
2099             return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
2100         }
2101
2102         //// String array DEFAULTS
2103         //// Note: GetDTFIOverrideValues does the user overrides for these, so we don't have to.
2104
2105
2106         // Time separator (derived from time format)
2107         internal String TimeSeparator
2108         {
2109             get
2110             {
2111                 if (_sTimeSeparator == null)
2112                 {
2113                     string longTimeFormat = GetTimeFormatString();
2114                     if (String.IsNullOrEmpty(longTimeFormat))
2115                     {
2116                         longTimeFormat = LongTimes[0];
2117                     }
2118
2119                     // Compute STIME from time format
2120                     _sTimeSeparator = GetTimeSeparator(longTimeFormat);
2121                 }
2122                 return _sTimeSeparator;
2123             }
2124         }
2125
2126         // Date separator (derived from short date format)
2127         internal String DateSeparator(CalendarId calendarId)
2128         {
2129             return GetDateSeparator(ShortDates(calendarId)[0]);
2130         }
2131
2132         //////////////////////////////////////
2133         // Helper Functions to get derived properties //
2134         //////////////////////////////////////
2135
2136         ////////////////////////////////////////////////////////////////////////////
2137         //
2138         // Unescape a NLS style quote string
2139         //
2140         // This removes single quotes:
2141         //      'fred' -> fred
2142         //      'fred -> fred
2143         //      fred' -> fred
2144         //      fred's -> freds
2145         //
2146         // This removes the first \ of escaped characters:
2147         //      fred\'s -> fred's
2148         //      a\\b -> a\b
2149         //      a\b -> ab
2150         //
2151         // We don't build the stringbuilder unless we find a ' or a \.  If we find a ' or a \, we
2152         // always build a stringbuilder because we need to remove the ' or \.
2153         //
2154         ////////////////////////////////////////////////////////////////////////////
2155         private static String UnescapeNlsString(String str, int start, int end)
2156         {
2157             Debug.Assert(str != null);
2158             Debug.Assert(start >= 0);
2159             Debug.Assert(end >= 0);
2160             StringBuilder result = null;
2161
2162             for (int i = start; i < str.Length && i <= end; i++)
2163             {
2164                 switch (str[i])
2165                 {
2166                     case '\'':
2167                         if (result == null)
2168                         {
2169                             result = new StringBuilder(str, start, i - start, str.Length);
2170                         }
2171                         break;
2172                     case '\\':
2173                         if (result == null)
2174                         {
2175                             result = new StringBuilder(str, start, i - start, str.Length);
2176                         }
2177                         ++i;
2178                         if (i < str.Length)
2179                         {
2180                             result.Append(str[i]);
2181                         }
2182                         break;
2183                     default:
2184                         if (result != null)
2185                         {
2186                             result.Append(str[i]);
2187                         }
2188                         break;
2189                 }
2190             }
2191
2192             if (result == null)
2193                 return (str.Substring(start, end - start + 1));
2194
2195             return (result.ToString());
2196         }
2197
2198         private static String GetTimeSeparator(String format)
2199         {
2200             // Time format separator (ie: : in 12:39:00)
2201             //
2202             // We calculate this from the provided time format
2203             //
2204
2205             //
2206             //  Find the time separator so that we can pretend we know STIME.
2207             //
2208             return GetSeparator(format, "Hhms");
2209         }
2210
2211         private static String GetDateSeparator(String format)
2212         {
2213             // Date format separator (ie: / in 9/1/03)
2214             //
2215             // We calculate this from the provided short date
2216             //
2217
2218             //
2219             //  Find the date separator so that we can pretend we know SDATE.
2220             //
2221             return GetSeparator(format, "dyM");
2222         }
2223
2224         private static string GetSeparator(string format, string timeParts)
2225         {
2226             int index = IndexOfTimePart(format, 0, timeParts);
2227
2228             if (index != -1)
2229             {
2230                 // Found a time part, find out when it changes
2231                 char cTimePart = format[index];
2232
2233                 do
2234                 {
2235                     index++;
2236                 } while (index < format.Length && format[index] == cTimePart);
2237
2238                 int separatorStart = index;
2239
2240                 // Now we need to find the end of the separator
2241                 if (separatorStart < format.Length)
2242                 {
2243                     int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
2244                     if (separatorEnd != -1)
2245                     {
2246                         // From [separatorStart, count) is our string, except we need to unescape
2247                         return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
2248                     }
2249                 }
2250             }
2251
2252             return String.Empty;
2253         }
2254
2255         private static int IndexOfTimePart(string format, int startIndex, string timeParts)
2256         {
2257             Debug.Assert(startIndex >= 0, "startIndex cannot be negative");
2258             Debug.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
2259             bool inQuote = false;
2260             for (int i = startIndex; i < format.Length; ++i)
2261             {
2262                 // See if we have a time Part
2263                 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
2264                 {
2265                     return i;
2266                 }
2267                 switch (format[i])
2268                 {
2269                     case '\\':
2270                         if (i + 1 < format.Length)
2271                         {
2272                             ++i;
2273                             switch (format[i])
2274                             {
2275                                 case '\'':
2276                                 case '\\':
2277                                     break;
2278                                 default:
2279                                     --i; //backup since we will move over this next
2280                                     break;
2281                             }
2282                         }
2283                         break;
2284                     case '\'':
2285                         inQuote = !inQuote;
2286                         break;
2287                 }
2288             }
2289
2290             return -1;
2291         }
2292
2293         internal static bool IsCustomCultureId(int cultureId)
2294         {
2295             return (cultureId == CultureInfo.LOCALE_CUSTOM_DEFAULT || cultureId == CultureInfo.LOCALE_CUSTOM_UNSPECIFIED);
2296         }
2297
2298         internal void GetNFIValues(NumberFormatInfo nfi)
2299         {
2300             if (GlobalizationMode.Invariant || this.IsInvariantCulture)
2301             {
2302                 // FUTURE: NumberFormatInfo already has default values for many of these fields.  Can we not do this?
2303                 nfi.positiveSign = _sPositiveSign;
2304                 nfi.negativeSign = _sNegativeSign;
2305
2306                 nfi.numberGroupSeparator = _sThousandSeparator;
2307                 nfi.numberDecimalSeparator = _sDecimalSeparator;
2308                 nfi.numberDecimalDigits = _iDigits;
2309                 nfi.numberNegativePattern = _iNegativeNumber;
2310
2311                 nfi.currencySymbol = _sCurrency;
2312                 nfi.currencyGroupSeparator = _sMonetaryThousand;
2313                 nfi.currencyDecimalSeparator = _sMonetaryDecimal;
2314                 nfi.currencyDecimalDigits = _iCurrencyDigits;
2315                 nfi.currencyNegativePattern = _iNegativeCurrency;
2316                 nfi.currencyPositivePattern = _iCurrency;
2317             }
2318             else
2319             {
2320                 Debug.Assert(_sWindowsName != null, "[CultureData.GetNFIValues] Expected _sWindowsName to be populated by already");
2321                 // String values
2322                 nfi.positiveSign = GetLocaleInfo(LocaleStringData.PositiveSign);
2323                 nfi.negativeSign = GetLocaleInfo(LocaleStringData.NegativeSign);
2324
2325                 nfi.numberDecimalSeparator = GetLocaleInfo(LocaleStringData.DecimalSeparator);
2326                 nfi.numberGroupSeparator = GetLocaleInfo(LocaleStringData.ThousandSeparator);
2327                 nfi.currencyGroupSeparator = GetLocaleInfo(LocaleStringData.MonetaryThousandSeparator);
2328                 nfi.currencyDecimalSeparator = GetLocaleInfo(LocaleStringData.MonetaryDecimalSeparator);
2329                 nfi.currencySymbol = GetLocaleInfo(LocaleStringData.MonetarySymbol);
2330
2331                 // Numeric values
2332                 nfi.numberDecimalDigits = GetLocaleInfo(LocaleNumberData.FractionalDigitsCount);
2333                 nfi.currencyDecimalDigits = GetLocaleInfo(LocaleNumberData.MonetaryFractionalDigitsCount);
2334                 nfi.currencyPositivePattern = GetLocaleInfo(LocaleNumberData.PositiveMonetaryNumberFormat);
2335                 nfi.currencyNegativePattern = GetLocaleInfo(LocaleNumberData.NegativeMonetaryNumberFormat);
2336                 nfi.numberNegativePattern = GetLocaleInfo(LocaleNumberData.NegativeNumberFormat);
2337
2338                 // LOCALE_SNATIVEDIGITS (array of 10 single character strings).
2339                 string digits = GetLocaleInfo(LocaleStringData.Digits);
2340                 nfi.nativeDigits = new string[10];
2341                 for (int i = 0; i < nfi.nativeDigits.Length; i++)
2342                 {
2343                     nfi.nativeDigits[i] = new string(digits[i], 1);
2344                 }
2345
2346                 nfi.digitSubstitution = GetDigitSubstitution(_sRealName);
2347             }
2348
2349             //
2350             // Gather additional data
2351             //
2352             nfi.numberGroupSizes = this.WAGROUPING;
2353             nfi.currencyGroupSizes = this.WAMONGROUPING;
2354
2355             // prefer the cached value since these do not have user overrides
2356             nfi.percentNegativePattern = this.INEGATIVEPERCENT;
2357             nfi.percentPositivePattern = this.IPOSITIVEPERCENT;
2358             nfi.percentSymbol = this.SPERCENT;
2359             nfi.perMilleSymbol = this.SPERMILLE;
2360
2361             nfi.negativeInfinitySymbol = this.SNEGINFINITY;
2362             nfi.positiveInfinitySymbol = this.SPOSINFINITY;
2363             nfi.nanSymbol = this.SNAN;
2364
2365             //
2366             // We don't have percent values, so use the number values
2367             //
2368             nfi.percentDecimalDigits = nfi.numberDecimalDigits;
2369             nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
2370             nfi.percentGroupSizes = nfi.numberGroupSizes;
2371             nfi.percentGroupSeparator = nfi.numberGroupSeparator;
2372
2373             //
2374             // Clean up a few odd values
2375             //
2376
2377             // Windows usually returns an empty positive sign, but we like it to be "+"
2378             if (nfi.positiveSign == null || nfi.positiveSign.Length == 0) nfi.positiveSign = "+";
2379
2380             //Special case for Italian.  The currency decimal separator in the control panel is the empty string. When the user
2381             //specifies C4 as the currency format, this results in the number apparently getting multiplied by 10000 because the
2382             //decimal point doesn't show up.  We'll just hack this here because our default currency format will never use nfi.
2383             if (nfi.currencyDecimalSeparator == null || nfi.currencyDecimalSeparator.Length == 0)
2384             {
2385                 nfi.currencyDecimalSeparator = nfi.numberDecimalSeparator;
2386             }
2387         }
2388
2389         // Helper
2390         // This is ONLY used for caching names and shouldn't be used for anything else
2391         internal static string AnsiToLower(string testString)
2392         {
2393             int index = 0; 
2394             
2395             while (index<testString.Length && (testString[index]<'A' || testString[index]>'Z' ))
2396             {
2397                 index++;
2398             }
2399             if (index >= testString.Length)
2400             {
2401                 return testString; // we didn't really change the string
2402             }
2403             
2404             StringBuilder sb = new StringBuilder(testString.Length);
2405             for (int i=0; i<index; i++)
2406             {
2407                 sb.Append(testString[i]);
2408             }
2409
2410             sb.Append((char) (testString[index] -'A' + 'a'));
2411
2412             for (int ich = index+1; ich < testString.Length; ich++)
2413             {
2414                 char ch = testString[ich];
2415                 sb.Append(ch <= 'Z' && ch >= 'A' ? (char)(ch - 'A' + 'a') : ch);
2416             }
2417
2418             return (sb.ToString());
2419         }
2420
2421         /// <remarks>
2422         /// The numeric values of the enum members match their Win32 counterparts.  The CultureData Win32 PAL implementation
2423         /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
2424         /// </remarks>
2425         private enum LocaleStringData : uint
2426         {
2427             /// <summary>localized name of locale, eg "German (Germany)" in UI language (corresponds to LOCALE_SLOCALIZEDDISPLAYNAME)</summary>
2428             LocalizedDisplayName = 0x00000002,
2429             /// <summary>Display name (language + country usually) in English, eg "German (Germany)" (corresponds to LOCALE_SENGLISHDISPLAYNAME)</summary>
2430             EnglishDisplayName = 0x00000072,
2431             /// <summary>Display name in native locale language, eg "Deutsch (Deutschland) (corresponds to LOCALE_SNATIVEDISPLAYNAME)</summary>
2432             NativeDisplayName = 0x00000073,
2433             /// <summary>Language Display Name for a language, eg "German" in UI language (corresponds to LOCALE_SLOCALIZEDLANGUAGENAME)</summary>
2434             LocalizedLanguageName = 0x0000006f,
2435             /// <summary>English name of language, eg "German" (corresponds to LOCALE_SENGLISHLANGUAGENAME)</summary>
2436             EnglishLanguageName = 0x00001001,
2437             /// <summary>native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME)</summary>
2438             NativeLanguageName = 0x00000004,
2439             /// <summary>localized name of country, eg "Germany" in UI language (corresponds to LOCALE_SLOCALIZEDCOUNTRYNAME)</summary>
2440             LocalizedCountryName = 0x00000006,
2441             /// <summary>English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME)</summary>
2442             EnglishCountryName = 0x00001002,
2443             /// <summary>native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME)</summary>
2444             NativeCountryName = 0x00000008,
2445             /// <summary>abbreviated language name (corresponds to LOCALE_SABBREVLANGNAME)</summary>
2446             AbbreviatedWindowsLanguageName = 0x00000003,
2447             /// <summary>list item separator (corresponds to LOCALE_SLIST)</summary>
2448             ListSeparator = 0x0000000C,
2449             /// <summary>decimal separator (corresponds to LOCALE_SDECIMAL)</summary>
2450             DecimalSeparator = 0x0000000E,
2451             /// <summary>thousand separator (corresponds to LOCALE_STHOUSAND)</summary>
2452             ThousandSeparator = 0x0000000F,
2453             /// <summary>digit grouping (corresponds to LOCALE_SGROUPING)</summary>
2454             Digits = 0x00000013,
2455             /// <summary>local monetary symbol (corresponds to LOCALE_SCURRENCY)</summary>
2456             MonetarySymbol = 0x00000014,
2457             /// <summary>English currency name (corresponds to LOCALE_SENGCURRNAME)</summary>
2458             CurrencyEnglishName = 0x00001007,
2459             /// <summary>Native currency name (corresponds to LOCALE_SNATIVECURRNAME)</summary>
2460             CurrencyNativeName = 0x00001008,
2461             /// <summary>uintl monetary symbol (corresponds to LOCALE_SINTLSYMBOL)</summary>
2462             Iso4217MonetarySymbol = 0x00000015,
2463             /// <summary>monetary decimal separator (corresponds to LOCALE_SMONDECIMALSEP)</summary>
2464             MonetaryDecimalSeparator = 0x00000016,
2465             /// <summary>monetary thousand separator (corresponds to LOCALE_SMONTHOUSANDSEP)</summary>
2466             MonetaryThousandSeparator = 0x00000017,
2467             /// <summary>AM designator (corresponds to LOCALE_S1159)</summary>
2468             AMDesignator = 0x00000028,
2469             /// <summary>PM designator (corresponds to LOCALE_S2359)</summary>
2470             PMDesignator = 0x00000029,
2471             /// <summary>positive sign (corresponds to LOCALE_SPOSITIVESIGN)</summary>
2472             PositiveSign = 0x00000050,
2473             /// <summary>negative sign (corresponds to LOCALE_SNEGATIVESIGN)</summary>
2474             NegativeSign = 0x00000051,
2475             /// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
2476             Iso639LanguageTwoLetterName = 0x00000059,
2477             /// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO639LANGNAME2)</summary>
2478             Iso639LanguageThreeLetterName = 0x00000067,
2479             /// <summary>ISO abbreviated language name (corresponds to LOCALE_SISO639LANGNAME)</summary>
2480             Iso639LanguageName = 0x00000059,
2481             /// <summary>ISO abbreviated country name (corresponds to LOCALE_SISO3166CTRYNAME)</summary>
2482             Iso3166CountryName = 0x0000005A,
2483             /// <summary>3 letter ISO country code (corresponds to LOCALE_SISO3166CTRYNAME2)</summary>
2484             Iso3166CountryName2 = 0x00000068,   // 3 character ISO country name
2485             /// <summary>Not a Number (corresponds to LOCALE_SNAN)</summary>
2486             NaNSymbol = 0x00000069,
2487             /// <summary>+ Infinity (corresponds to LOCALE_SPOSINFINITY)</summary>
2488             PositiveInfinitySymbol = 0x0000006a,
2489             /// <summary>- Infinity (corresponds to LOCALE_SNEGINFINITY)</summary>
2490             NegativeInfinitySymbol = 0x0000006b,
2491             /// <summary>Fallback name for resources (corresponds to LOCALE_SPARENT)</summary>
2492             ParentName = 0x0000006d,
2493             /// <summary>Fallback name for within the console (corresponds to LOCALE_SCONSOLEFALLBACKNAME)</summary>
2494             ConsoleFallbackName = 0x0000006e,
2495             /// <summary>Returns the percent symbol (corresponds to LOCALE_SPERCENT)</summary>
2496             PercentSymbol = 0x00000076,
2497             /// <summary>Returns the permille (U+2030) symbol (corresponds to LOCALE_SPERMILLE)</summary>
2498             PerMilleSymbol = 0x00000077
2499         }
2500
2501         /// <remarks>
2502         /// The numeric values of the enum members match their Win32 counterparts.  The CultureData Win32 PAL implementation
2503         /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
2504         /// </remarks>
2505         private enum LocaleGroupingData : uint
2506         {
2507             /// <summary>digit grouping (corresponds to LOCALE_SGROUPING)</summary>
2508             Digit = 0x00000010,
2509             /// <summary>monetary grouping (corresponds to LOCALE_SMONGROUPING)</summary>
2510             Monetary = 0x00000018,
2511         }
2512
2513         /// <remarks>
2514         /// The numeric values of the enum members match their Win32 counterparts.  The CultureData Win32 PAL implementation
2515         /// takes a dependency on this fact, in order to prevent having to construct a mapping from internal values to LCTypes.
2516         /// </remarks>
2517         private enum LocaleNumberData : uint
2518         {
2519             /// <summary>language id (corresponds to LOCALE_ILANGUAGE)</summary>
2520             LanguageId = 0x00000001,
2521             /// <summary>geographical location id, (corresponds to LOCALE_IGEOID)</summary>
2522             GeoId = 0x0000005B,
2523             /// <summary>0 = context, 1 = none, 2 = national (corresponds to LOCALE_IDIGITSUBSTITUTION)</summary>
2524             DigitSubstitution = 0x00001014,
2525             /// <summary>0 = metric, 1 = US (corresponds to LOCALE_IMEASURE)</summary>
2526             MeasurementSystem = 0x0000000D,
2527             /// <summary>number of fractional digits (corresponds to LOCALE_IDIGITS)</summary>
2528             FractionalDigitsCount = 0x00000011,
2529             /// <summary>negative number mode (corresponds to LOCALE_INEGNUMBER)</summary>
2530             NegativeNumberFormat = 0x00001010,
2531             /// <summary># local monetary digits (corresponds to LOCALE_ICURRDIGITS)</summary>
2532             MonetaryFractionalDigitsCount = 0x00000019,
2533             /// <summary>positive currency mode (corresponds to LOCALE_ICURRENCY)</summary>
2534             PositiveMonetaryNumberFormat = 0x0000001B,
2535             /// <summary>negative currency mode (corresponds to LOCALE_INEGCURR)</summary>
2536             NegativeMonetaryNumberFormat = 0x0000001C,
2537             /// <summary>type of calendar specifier (corresponds to LOCALE_ICALENDARTYPE)</summary>
2538             CalendarType = 0x00001009,
2539             /// <summary>first day of week specifier (corresponds to LOCALE_IFIRSTDAYOFWEEK)</summary>
2540             FirstDayOfWeek = 0x0000100C,
2541             /// <summary>first week of year specifier (corresponds to LOCALE_IFIRSTWEEKOFYEAR)</summary>
2542             FirstWeekOfYear = 0x0000100D,
2543             /// <summary>
2544             /// Returns one of the following 4 reading layout values:
2545             ///  0 - Left to right (eg en-US)
2546             ///  1 - Right to left (eg arabic locales)
2547             ///  2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
2548             ///  3 - Vertical top to bottom with columns proceeding to the right
2549             /// (corresponds to LOCALE_IREADINGLAYOUT)
2550             /// </summary>
2551             ReadingLayout = 0x00000070,
2552             /// <summary>Returns 0-11 for the negative percent format (corresponds to LOCALE_INEGATIVEPERCENT)</summary>
2553             NegativePercentFormat = 0x00000074,
2554             /// <summary>Returns 0-3 for the positive percent format (corresponds to LOCALE_IPOSITIVEPERCENT)</summary>
2555             PositivePercentFormat = 0x00000075,
2556             /// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTCODEPAGE)</summary>
2557             OemCodePage = 0x0000000B,
2558             /// <summary>default ansi code page (corresponds to LOCALE_IDEFAULTANSICODEPAGE)</summary>
2559             AnsiCodePage = 0x00001004,
2560             /// <summary>default mac code page (corresponds to LOCALE_IDEFAULTMACCODEPAGE)</summary>
2561             MacCodePage = 0x00001011,
2562             /// <summary>default ebcdic code page (corresponds to LOCALE_IDEFAULTEBCDICCODEPAGE)</summary>
2563             EbcdicCodePage = 0x00001012,
2564         }
2565     }
2566 }