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