Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / globalization / calendardata.cs
1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 //
5 // ==--==
6 namespace System.Globalization
7 {
8
9     using System;
10     using System.Runtime.InteropServices;    
11     using System.Runtime.CompilerServices;
12     using System.Runtime.Versioning;
13     using System.Diagnostics.Contracts;
14
15 // 
16
17
18     //
19     // List of calendar data
20     // Note the we cache overrides.
21     // Note that localized names (resource names) aren't available from here.
22     //
23     //  NOTE: Calendars depend on the locale name that creates it.  Only a few
24     //            properties are available without locales using CalendarData.GetCalendar(int)
25
26     // StructLayout is needed here otherwise compiler can re-arrange the fields.
27     // We have to keep this in-sync with the definition in calendardata.h
28     //
29     // WARNING WARNING WARNING
30     //
31     // WARNING: Anything changed here also needs to be updated on the native side (object.h see type CalendarDataBaseObject)
32     // WARNING: The type loader will rearrange class member offsets so the mscorwks!CalendarDataBaseObject
33     // WARNING: must be manually structured to match the true loaded class layout
34     //
35     internal partial class CalendarData
36     {
37         // Max calendars
38         internal const int MAX_CALENDARS = 23;
39
40         // Identity
41         internal String     sNativeName              ; // Calendar Name for the locale
42
43         // Formats
44         internal String[]   saShortDates             ; // Short Data format, default first
45         internal String[]   saYearMonths             ; // Year/Month Data format, default first
46         internal String[]   saLongDates              ; // Long Data format, default first
47         internal String     sMonthDay                ; // Month/Day format
48
49         // Calendar Parts Names
50         internal String[]   saEraNames               ; // Names of Eras
51         internal String[]   saAbbrevEraNames         ; // Abbreviated Era Names
52         internal String[]   saAbbrevEnglishEraNames  ; // Abbreviated Era Names in English
53         internal String[]   saDayNames               ; // Day Names, null to use locale data, starts on Sunday
54         internal String[]   saAbbrevDayNames         ; // Abbrev Day Names, null to use locale data, starts on Sunday
55         internal String[]   saSuperShortDayNames     ; // Super short Day of week names
56         internal String[]   saMonthNames             ; // Month Names (13)
57         internal String[]   saAbbrevMonthNames       ; // Abbrev Month Names (13)
58         internal String[]   saMonthGenitiveNames     ; // Genitive Month Names (13)
59         internal String[]   saAbbrevMonthGenitiveNames; // Genitive Abbrev Month Names (13)
60         internal String[]   saLeapYearMonthNames     ; // Multiple strings for the month names in a leap year.
61
62         // Integers at end to make marshaller happier
63         internal int        iTwoDigitYearMax=2029    ; // Max 2 digit year (for Y2K bug data entry)
64         internal int        iCurrentEra=0            ;  // current era # (usually 1)
65
66         // Use overrides?
67         internal bool       bUseUserOverrides        ; // True if we want user overrides.
68
69         // Static invariant for the invariant locale
70         internal static CalendarData Invariant;
71
72         // Private constructor
73         private CalendarData() {}
74
75         // Invariant constructor
76         static CalendarData()
77         {
78
79             // Set our default/gregorian US calendar data
80             // Calendar IDs are 1-based, arrays are 0 based.
81             CalendarData invariant = new CalendarData();
82
83             // Set default data for calendar
84             // Note that we don't load resources since this IS NOT supposed to change (by definition)
85             invariant.sNativeName           = "Gregorian Calendar";  // Calendar Name
86
87             // Year
88             invariant.iTwoDigitYearMax      = 2029; // Max 2 digit year (for Y2K bug data entry)
89             invariant.iCurrentEra           = 1; // Current era #
90
91             // Formats
92             invariant.saShortDates          = new String[] { "MM/dd/yyyy", "yyyy-MM-dd" };          // short date format
93             invariant.saLongDates           = new String[] { "dddd, dd MMMM yyyy"};                 // long date format
94             invariant.saYearMonths          = new String[] { "yyyy MMMM" };                         // year month format
95             invariant.sMonthDay             = "MMMM dd";                                            // Month day pattern
96
97             // Calendar Parts Names
98             invariant.saEraNames            = new String[] { "A.D." };     // Era names
99             invariant.saAbbrevEraNames      = new String[] { "AD" };      // Abbreviated Era names
100             invariant.saAbbrevEnglishEraNames=new String[] { "AD" };     // Abbreviated era names in English
101             invariant.saDayNames            = new String[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };// day names
102             invariant.saAbbrevDayNames      = new String[] { "Sun",    "Mon",    "Tue",     "Wed",       "Thu",      "Fri",    "Sat" };     // abbreviated day names
103             invariant.saSuperShortDayNames  = new String[] { "Su",     "Mo",     "Tu",      "We",        "Th",       "Fr",     "Sa" };      // The super short day names
104             invariant.saMonthNames          = new String[] { "January", "February", "March", "April", "May", "June", 
105                                                             "July", "August", "September", "October", "November", "December", String.Empty}; // month names
106             invariant.saAbbrevMonthNames    = new String[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
107                                                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", String.Empty}; // abbreviated month names
108             invariant.saMonthGenitiveNames  = invariant.saMonthNames;              // Genitive month names (same as month names for invariant)
109             invariant.saAbbrevMonthGenitiveNames=invariant.saAbbrevMonthNames;    // Abbreviated genitive month names (same as abbrev month names for invariant)
110             invariant.saLeapYearMonthNames  = invariant.saMonthNames;              // leap year month names are unused in Gregorian English (invariant)
111
112             invariant.bUseUserOverrides     = false;
113
114             // Calendar was built, go ahead and assign it...            
115             Invariant = invariant;
116         }
117
118
119
120         //
121         // Get a bunch of data for a calendar
122         //
123         internal CalendarData(String localeName, int calendarId, bool bUseUserOverrides)
124         {
125             // Call nativeGetCalendarData to populate the data
126             this.bUseUserOverrides = bUseUserOverrides;
127             if (!nativeGetCalendarData(this, localeName, calendarId))
128             {
129                 Contract.Assert(false, "[CalendarData] nativeGetCalendarData call isn't expected to fail for calendar " + calendarId + " locale " +localeName);
130                 
131                 // Something failed, try invariant for missing parts
132                 // This is really not good, but we don't want the callers to crash.
133                 if (this.sNativeName == null)   this.sNativeName  = String.Empty;           // Calendar Name for the locale.
134                 
135                 // Formats
136                 if (this.saShortDates == null)  this.saShortDates = Invariant.saShortDates; // Short Data format, default first
137                 if (this.saYearMonths == null)  this.saYearMonths = Invariant.saYearMonths; // Year/Month Data format, default first
138                 if (this.saLongDates == null)   this.saLongDates  = Invariant.saLongDates;  // Long Data format, default first
139                 if (this.sMonthDay == null)     this.sMonthDay    = Invariant.sMonthDay;    // Month/Day format
140                 
141                 // Calendar Parts Names
142                 if (this.saEraNames == null)              this.saEraNames              = Invariant.saEraNames;              // Names of Eras
143                 if (this.saAbbrevEraNames == null)        this.saAbbrevEraNames        = Invariant.saAbbrevEraNames;        // Abbreviated Era Names
144                 if (this.saAbbrevEnglishEraNames == null) this.saAbbrevEnglishEraNames = Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
145                 if (this.saDayNames == null)              this.saDayNames              = Invariant.saDayNames;              // Day Names, null to use locale data, starts on Sunday
146                 if (this.saAbbrevDayNames == null)        this.saAbbrevDayNames        = Invariant.saAbbrevDayNames;        // Abbrev Day Names, null to use locale data, starts on Sunday
147                 if (this.saSuperShortDayNames == null)    this.saSuperShortDayNames    = Invariant.saSuperShortDayNames;    // Super short Day of week names
148                 if (this.saMonthNames == null)            this.saMonthNames            = Invariant.saMonthNames;            // Month Names (13)
149                 if (this.saAbbrevMonthNames == null)      this.saAbbrevMonthNames      = Invariant.saAbbrevMonthNames;      // Abbrev Month Names (13)
150                 // Genitive and Leap names can follow the fallback below
151             }
152
153             // Clean up the escaping of the formats
154             this.saShortDates = CultureData.ReescapeWin32Strings(this.saShortDates);
155             this.saLongDates = CultureData.ReescapeWin32Strings(this.saLongDates);
156             this.saYearMonths = CultureData.ReescapeWin32Strings(this.saYearMonths);
157             this.sMonthDay = CultureData.ReescapeWin32String(this.sMonthDay);
158
159             if ((CalendarId)calendarId == CalendarId.TAIWAN)
160             {
161                 // for Geo----al reasons, the ----ese native name should only be returned when 
162                 // for ----ese SKU
163                 if (CultureInfo.IsTaiwanSku)
164                 {
165                     // We got the month/day names from the OS (same as gregorian), but the native name is wrong
166                     this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
167                 }
168                 else
169                 {
170                     this.sNativeName = String.Empty;
171                 }
172             }
173
174             // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
175             if (this.saMonthGenitiveNames == null || String.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
176                 this.saMonthGenitiveNames = this.saMonthNames;              // Genitive month names (same as month names for invariant)
177             if (this.saAbbrevMonthGenitiveNames == null || String.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
178                 this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames;    // Abbreviated genitive month names (same as abbrev month names for invariant)
179             if (this.saLeapYearMonthNames == null || String.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
180                 this.saLeapYearMonthNames = this.saMonthNames;
181
182             InitializeEraNames(localeName, calendarId);
183
184             InitializeAbbreviatedEraNames(localeName, calendarId);
185
186             // Abbreviated English Era Names are only used for the Japanese calendar.
187             if (calendarId == (int)CalendarId.JAPAN)
188             {
189                 this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
190             }
191             else
192             {
193                 // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
194                 this.saAbbrevEnglishEraNames = new String[] { "" };
195             }
196
197             // Japanese is the only thing with > 1 era.  Its current era # is how many ever 
198             // eras are in the array.  (And the others all have 1 string in the array)
199             this.iCurrentEra = this.saEraNames.Length;
200         }
201
202         private void InitializeEraNames(string localeName, int calendarId)
203         {
204             // Note that the saEraNames only include "A.D."  We don't have localized names for other calendars available from windows
205             switch ((CalendarId)calendarId)
206             {
207                     // For Localized Gregorian we really expect the data from the OS.
208                 case CalendarId.GREGORIAN:
209                     // Fallback for CoreCLR < Win7 or culture.dll missing            
210                     if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
211                     {
212                         this.saEraNames = new String[] { "A.D." };
213                     }
214                     break;
215
216                     // The rest of the calendars have constant data, so we'll just use that
217                 case CalendarId.GREGORIAN_US:
218                 case CalendarId.JULIAN:
219                     this.saEraNames = new String[] { "A.D." };
220                     break;
221                 case CalendarId.HEBREW:
222                     this.saEraNames = new String[] { "C.E." };
223                     break;
224                 case CalendarId.HIJRI:
225                 case CalendarId.UMALQURA:
226                     if (localeName == "dv-MV")
227                     {
228                         // Special case for Divehi
229                         this.saEraNames = new String[] { "\x0780\x07a8\x0796\x07b0\x0783\x07a9" };
230                     }
231                     else
232                     {
233                         this.saEraNames = new String[] { "\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629" };
234                     }
235                     break;
236                 case CalendarId.GREGORIAN_ARABIC:
237                 case CalendarId.GREGORIAN_XLIT_ENGLISH:
238                 case CalendarId.GREGORIAN_XLIT_FRENCH:
239                     // These are all the same:
240                     this.saEraNames = new String[] { "\x0645" };
241                     break;
242
243                 case CalendarId.GREGORIAN_ME_FRENCH:
244                     this.saEraNames = new String[] { "ap. J.-C." };
245                     break;
246                     
247                 case CalendarId.TAIWAN:
248                     // for Geo----al reasons, the ----ese native name should only be returned when 
249                     // for ----ese SKU
250                     if (CultureInfo.IsTaiwanSku)
251                     {
252                         // 
253                         this.saEraNames = new String[] { "\x4e2d\x83ef\x6c11\x570b" };
254                     }
255                     else
256                     {
257                         this.saEraNames = new String[] { String.Empty };
258                     }
259                     break;
260
261                 case CalendarId.KOREA:
262                     this.saEraNames = new String[] { "\xb2e8\xae30" };
263                     break;
264                     
265                 case CalendarId.THAI:
266                     this.saEraNames = new String[] { "\x0e1e\x002e\x0e28\x002e" };
267                     break;
268                     
269                 case CalendarId.JAPAN:
270                 case CalendarId.JAPANESELUNISOLAR:
271                     this.saEraNames = JapaneseCalendar.EraNames();
272                     break;
273
274                 case CalendarId.PERSIAN:
275                     if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
276                     {
277                         this.saEraNames = new String[] { "\x0647\x002e\x0634" };
278                     }
279                     break;
280
281                 default:
282                     // Most calendars are just "A.D."
283                     this.saEraNames = Invariant.saEraNames;
284                     break;
285             }
286         }
287
288         private void InitializeAbbreviatedEraNames(string localeName, int calendarId)
289         {
290             // Note that the saAbbrevEraNames only include "AD"  We don't have localized names for other calendars available from windows
291             switch ((CalendarId)calendarId)
292             {
293                     // For Localized Gregorian we really expect the data from the OS.
294                 case CalendarId.GREGORIAN:
295                     // Fallback for CoreCLR < Win7 or culture.dll missing            
296                     if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
297                     {
298                         this.saAbbrevEraNames = new String[] { "AD" };
299                     }
300                     break;
301
302                     // The rest of the calendars have constant data, so we'll just use that
303                 case CalendarId.GREGORIAN_US:
304                 case CalendarId.JULIAN:                        
305                     this.saAbbrevEraNames = new String[] { "AD" };
306                     break;                    
307                 case CalendarId.JAPAN:
308                 case CalendarId.JAPANESELUNISOLAR:
309                     this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
310                     break;
311                 case CalendarId.HIJRI:
312                 case CalendarId.UMALQURA:
313                     if (localeName == "dv-MV")
314                     {
315                         // Special case for Divehi
316                         this.saAbbrevEraNames = new String[] { "\x0780\x002e" };
317                     }
318                     else
319                     {
320                         this.saAbbrevEraNames = new String[] { "\x0647\x0640" };
321                     }
322                     break;
323                 case CalendarId.TAIWAN:
324                     // Get era name and abbreviate it
325                     this.saAbbrevEraNames = new String[1];
326                     if (this.saEraNames[0].Length == 4)
327                     {
328                         this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2,2);
329                     }
330                     else
331                     {
332                         this.saAbbrevEraNames[0] = this.saEraNames[0];
333                     }                        
334                     break;
335
336                 case CalendarId.PERSIAN:
337                     if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
338                     {
339                         this.saAbbrevEraNames = this.saEraNames;
340                     }
341                     break;
342
343                 default:
344                     // Most calendars just use the full name
345                     this.saAbbrevEraNames = this.saEraNames;
346                     break;
347             }
348         }
349
350         internal static CalendarData GetCalendarData(int calendarId)
351         {
352             //
353             // Get a calendar.
354             // Unfortunately we depend on the locale in the OS, so we need a locale
355             // no matter what.  So just get the appropriate calendar from the 
356             // appropriate locale here
357             //
358
359             // Get a culture name
360             // 
361             String culture = CalendarIdToCultureName(calendarId);
362            
363             // Return our calendar
364             return CultureInfo.GetCultureInfo(culture).m_cultureData.GetCalendar(calendarId);
365         }
366
367         //
368         // Helper methods
369         //
370         private static String CalendarIdToCultureName(int calendarId)
371         {
372             switch (calendarId)
373             {
374                 case Calendar.CAL_GREGORIAN_US: 
375                     return "fa-IR";             // "fa-IR" Iran
376                     
377                 case Calendar.CAL_JAPAN:
378                     return "ja-JP";             // "ja-JP" Japan
379                 
380                 case Calendar.CAL_TAIWAN:
381                     return "zh-TW";             // zh-TW Taiwan
382                 
383                 case Calendar.CAL_KOREA:            
384                     return "ko-KR";             // "ko-KR" Korea
385                     
386                 case Calendar.CAL_HIJRI:
387                 case Calendar.CAL_GREGORIAN_ARABIC:
388                 case Calendar.CAL_UMALQURA:
389                     return "ar-SA";             // "ar-SA" Saudi Arabia
390
391                 case Calendar.CAL_THAI:
392                     return "th-TH";             // "th-TH" Thailand
393                     
394                 case Calendar.CAL_HEBREW:
395                     return "he-IL";             // "he-IL" Israel
396                     
397                 case Calendar.CAL_GREGORIAN_ME_FRENCH:
398                     return "ar-DZ";             // "ar-DZ" Algeria
399                 
400                 case Calendar.CAL_GREGORIAN_XLIT_ENGLISH:
401                 case Calendar.CAL_GREGORIAN_XLIT_FRENCH:
402                     return "ar-IQ";             // "ar-IQ"; Iraq
403                 
404                 default:
405                     // Default to gregorian en-US
406                     break;
407             }
408
409             return "en-US";
410         }
411 #if !MONO_CULTURE_DATA
412         internal void FixupWin7MonthDaySemicolonBug()
413         {
414             int unescapedCharacterIndex = FindUnescapedCharacter(sMonthDay, ';');
415             if (unescapedCharacterIndex > 0)
416             {
417                 sMonthDay = sMonthDay.Substring(0, unescapedCharacterIndex);
418             }
419         }
420         private static int FindUnescapedCharacter(string s, char charToFind)
421         {
422             bool inComment = false;
423             int length = s.Length;
424             for (int i = 0; i < length; i++)
425             {
426                 char c = s[i];
427
428                 switch (c)
429                 {
430                     case '\'':
431                         inComment = !inComment;
432                         break;
433                     case '\\':
434                         i++; // escape sequence -- skip next character
435                         break;
436                     default:
437                         if (!inComment && charToFind == c)
438                         {
439                             return i;
440                         }
441                         break;
442                 }
443             }
444             return -1;
445         }
446
447         
448         // Get native two digit year max
449         [System.Security.SecurityCritical]  // auto-generated
450         [ResourceExposure(ResourceScope.None)]
451         [MethodImplAttribute(MethodImplOptions.InternalCall)]
452         internal static extern int nativeGetTwoDigitYearMax(int calID);
453
454         // Call native side to load our calendar data
455         [System.Security.SecuritySafeCritical]  // auto-generated
456         [ResourceExposure(ResourceScope.None)]
457         [MethodImplAttribute(MethodImplOptions.InternalCall)]
458         private static extern bool nativeGetCalendarData(CalendarData data, String localeName, int calendar);
459
460         // Call native side to figure out which calendars are allowed
461         [System.Security.SecuritySafeCritical]  // auto-generated
462         [ResourceExposure(ResourceScope.None)]
463         [MethodImplAttribute(MethodImplOptions.InternalCall)]
464         internal static extern int nativeGetCalendars(String localeName, bool useUserOverride, [In, Out] int[] calendars);
465 #endif
466     }
467  }
468