3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 namespace System.Globalization
10 using System.Runtime.InteropServices;
11 using System.Runtime.CompilerServices;
12 using System.Runtime.Versioning;
13 using System.Diagnostics.Contracts;
19 // List of calendar data
20 // Note the we cache overrides.
21 // Note that localized names (resource names) aren't available from here.
23 // NOTE: Calendars depend on the locale name that creates it. Only a few
24 // properties are available without locales using CalendarData.GetCalendar(int)
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
29 // WARNING WARNING WARNING
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
35 internal partial class CalendarData
38 internal const int MAX_CALENDARS = 23;
41 internal String sNativeName ; // Calendar Name for the locale
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
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.
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)
67 internal bool bUseUserOverrides ; // True if we want user overrides.
69 // Static invariant for the invariant locale
70 internal static CalendarData Invariant;
72 // Private constructor
73 private CalendarData() {}
75 // Invariant constructor
79 // Set our default/gregorian US calendar data
80 // Calendar IDs are 1-based, arrays are 0 based.
81 CalendarData invariant = new CalendarData();
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
88 invariant.iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
89 invariant.iCurrentEra = 1; // Current era #
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
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)
112 invariant.bUseUserOverrides = false;
114 // Calendar was built, go ahead and assign it...
115 Invariant = invariant;
121 // Get a bunch of data for a calendar
123 internal CalendarData(String localeName, int calendarId, bool bUseUserOverrides)
125 // Call nativeGetCalendarData to populate the data
126 this.bUseUserOverrides = bUseUserOverrides;
127 if (!nativeGetCalendarData(this, localeName, calendarId))
129 Contract.Assert(false, "[CalendarData] nativeGetCalendarData call isn't expected to fail for calendar " + calendarId + " locale " +localeName);
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.
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
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
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);
159 if ((CalendarId)calendarId == CalendarId.TAIWAN)
161 // for Geo----al reasons, the ----ese native name should only be returned when
163 if (CultureInfo.IsTaiwanSku)
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";
170 this.sNativeName = String.Empty;
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;
182 InitializeEraNames(localeName, calendarId);
184 InitializeAbbreviatedEraNames(localeName, calendarId);
186 // Abbreviated English Era Names are only used for the Japanese calendar.
187 if (calendarId == (int)CalendarId.JAPAN)
189 this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
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[] { "" };
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;
202 private void InitializeEraNames(string localeName, int calendarId)
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)
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]))
212 this.saEraNames = new String[] { "A.D." };
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." };
221 case CalendarId.HEBREW:
222 this.saEraNames = new String[] { "C.E." };
224 case CalendarId.HIJRI:
225 case CalendarId.UMALQURA:
226 if (localeName == "dv-MV")
228 // Special case for Divehi
229 this.saEraNames = new String[] { "\x0780\x07a8\x0796\x07b0\x0783\x07a9" };
233 this.saEraNames = new String[] { "\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629" };
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" };
243 case CalendarId.GREGORIAN_ME_FRENCH:
244 this.saEraNames = new String[] { "ap. J.-C." };
247 case CalendarId.TAIWAN:
248 // for Geo----al reasons, the ----ese native name should only be returned when
250 if (CultureInfo.IsTaiwanSku)
253 this.saEraNames = new String[] { "\x4e2d\x83ef\x6c11\x570b" };
257 this.saEraNames = new String[] { String.Empty };
261 case CalendarId.KOREA:
262 this.saEraNames = new String[] { "\xb2e8\xae30" };
265 case CalendarId.THAI:
266 this.saEraNames = new String[] { "\x0e1e\x002e\x0e28\x002e" };
269 case CalendarId.JAPAN:
270 case CalendarId.JAPANESELUNISOLAR:
271 this.saEraNames = JapaneseCalendar.EraNames();
274 case CalendarId.PERSIAN:
275 if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
277 this.saEraNames = new String[] { "\x0647\x002e\x0634" };
282 // Most calendars are just "A.D."
283 this.saEraNames = Invariant.saEraNames;
288 private void InitializeAbbreviatedEraNames(string localeName, int calendarId)
290 // Note that the saAbbrevEraNames only include "AD" We don't have localized names for other calendars available from windows
291 switch ((CalendarId)calendarId)
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]))
298 this.saAbbrevEraNames = new String[] { "AD" };
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" };
307 case CalendarId.JAPAN:
308 case CalendarId.JAPANESELUNISOLAR:
309 this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
311 case CalendarId.HIJRI:
312 case CalendarId.UMALQURA:
313 if (localeName == "dv-MV")
315 // Special case for Divehi
316 this.saAbbrevEraNames = new String[] { "\x0780\x002e" };
320 this.saAbbrevEraNames = new String[] { "\x0647\x0640" };
323 case CalendarId.TAIWAN:
324 // Get era name and abbreviate it
325 this.saAbbrevEraNames = new String[1];
326 if (this.saEraNames[0].Length == 4)
328 this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2,2);
332 this.saAbbrevEraNames[0] = this.saEraNames[0];
336 case CalendarId.PERSIAN:
337 if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
339 this.saAbbrevEraNames = this.saEraNames;
344 // Most calendars just use the full name
345 this.saAbbrevEraNames = this.saEraNames;
350 internal static CalendarData GetCalendarData(int calendarId)
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
359 // Get a culture name
361 String culture = CalendarIdToCultureName(calendarId);
363 // Return our calendar
364 return CultureInfo.GetCultureInfo(culture).m_cultureData.GetCalendar(calendarId);
370 private static String CalendarIdToCultureName(int calendarId)
374 case Calendar.CAL_GREGORIAN_US:
375 return "fa-IR"; // "fa-IR" Iran
377 case Calendar.CAL_JAPAN:
378 return "ja-JP"; // "ja-JP" Japan
380 case Calendar.CAL_TAIWAN:
381 return "zh-TW"; // zh-TW Taiwan
383 case Calendar.CAL_KOREA:
384 return "ko-KR"; // "ko-KR" Korea
386 case Calendar.CAL_HIJRI:
387 case Calendar.CAL_GREGORIAN_ARABIC:
388 case Calendar.CAL_UMALQURA:
389 return "ar-SA"; // "ar-SA" Saudi Arabia
391 case Calendar.CAL_THAI:
392 return "th-TH"; // "th-TH" Thailand
394 case Calendar.CAL_HEBREW:
395 return "he-IL"; // "he-IL" Israel
397 case Calendar.CAL_GREGORIAN_ME_FRENCH:
398 return "ar-DZ"; // "ar-DZ" Algeria
400 case Calendar.CAL_GREGORIAN_XLIT_ENGLISH:
401 case Calendar.CAL_GREGORIAN_XLIT_FRENCH:
402 return "ar-IQ"; // "ar-IQ"; Iraq
405 // Default to gregorian en-US
411 #if !MONO_CULTURE_DATA
412 internal void FixupWin7MonthDaySemicolonBug()
414 int unescapedCharacterIndex = FindUnescapedCharacter(sMonthDay, ';');
415 if (unescapedCharacterIndex > 0)
417 sMonthDay = sMonthDay.Substring(0, unescapedCharacterIndex);
420 private static int FindUnescapedCharacter(string s, char charToFind)
422 bool inComment = false;
423 int length = s.Length;
424 for (int i = 0; i < length; i++)
431 inComment = !inComment;
434 i++; // escape sequence -- skip next character
437 if (!inComment && charToFind == c)
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);
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);
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);