CalendarData.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. using System.Diagnostics;
  5. namespace System.Globalization
  6. {
  7. // List of calendar data
  8. // Note the we cache overrides.
  9. // Note that localized names (resource names) aren't available from here.
  10. //
  11. // NOTE: Calendars depend on the locale name that creates it. Only a few
  12. // properties are available without locales using CalendarData.GetCalendar(CalendarData)
  13. internal partial class CalendarData
  14. {
  15. // Max calendars
  16. internal const int MAX_CALENDARS = 23;
  17. // Identity
  18. internal string sNativeName; // Calendar Name for the locale
  19. // Formats
  20. internal string[] saShortDates; // Short Data format, default first
  21. internal string[] saYearMonths; // Year/Month Data format, default first
  22. internal string[] saLongDates; // Long Data format, default first
  23. internal string sMonthDay; // Month/Day format
  24. // Calendar Parts Names
  25. internal string[] saEraNames; // Names of Eras
  26. internal string[] saAbbrevEraNames; // Abbreviated Era Names
  27. internal string[] saAbbrevEnglishEraNames; // Abbreviated Era Names in English
  28. internal string[] saDayNames; // Day Names, null to use locale data, starts on Sunday
  29. internal string[] saAbbrevDayNames; // Abbrev Day Names, null to use locale data, starts on Sunday
  30. internal string[] saSuperShortDayNames; // Super short Day of week names
  31. internal string[] saMonthNames; // Month Names (13)
  32. internal string[] saAbbrevMonthNames; // Abbrev Month Names (13)
  33. internal string[] saMonthGenitiveNames; // Genitive Month Names (13)
  34. internal string[] saAbbrevMonthGenitiveNames; // Genitive Abbrev Month Names (13)
  35. internal string[] saLeapYearMonthNames; // Multiple strings for the month names in a leap year.
  36. // Integers at end to make marshaller happier
  37. internal int iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
  38. internal int iCurrentEra = 0; // current era # (usually 1)
  39. // Use overrides?
  40. internal bool bUseUserOverrides; // True if we want user overrides.
  41. // Static invariant for the invariant locale
  42. internal static readonly CalendarData Invariant = CreateInvariant();
  43. // Private constructor
  44. private CalendarData() { }
  45. // Invariant factory
  46. private static CalendarData CreateInvariant()
  47. {
  48. // Set our default/gregorian US calendar data
  49. // Calendar IDs are 1-based, arrays are 0 based.
  50. CalendarData invariant = new CalendarData();
  51. // Set default data for calendar
  52. // Note that we don't load resources since this IS NOT supposed to change (by definition)
  53. invariant.sNativeName = "Gregorian Calendar"; // Calendar Name
  54. // Year
  55. invariant.iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
  56. invariant.iCurrentEra = 1; // Current era #
  57. // Formats
  58. invariant.saShortDates = new string[] { "MM/dd/yyyy", "yyyy-MM-dd" }; // short date format
  59. invariant.saLongDates = new string[] { "dddd, dd MMMM yyyy" }; // long date format
  60. invariant.saYearMonths = new string[] { "yyyy MMMM" }; // year month format
  61. invariant.sMonthDay = "MMMM dd"; // Month day pattern
  62. // Calendar Parts Names
  63. invariant.saEraNames = new string[] { "A.D." }; // Era names
  64. invariant.saAbbrevEraNames = new string[] { "AD" }; // Abbreviated Era names
  65. invariant.saAbbrevEnglishEraNames = new string[] { "AD" }; // Abbreviated era names in English
  66. invariant.saDayNames = new string[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };// day names
  67. invariant.saAbbrevDayNames = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // abbreviated day names
  68. invariant.saSuperShortDayNames = new string[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" }; // The super short day names
  69. invariant.saMonthNames = new string[] { "January", "February", "March", "April", "May", "June",
  70. "July", "August", "September", "October", "November", "December", string.Empty}; // month names
  71. invariant.saAbbrevMonthNames = new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  72. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", string.Empty}; // abbreviated month names
  73. invariant.saMonthGenitiveNames = invariant.saMonthNames; // Genitive month names (same as month names for invariant)
  74. invariant.saAbbrevMonthGenitiveNames = invariant.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
  75. invariant.saLeapYearMonthNames = invariant.saMonthNames; // leap year month names are unused in Gregorian English (invariant)
  76. invariant.bUseUserOverrides = false;
  77. return invariant;
  78. }
  79. //
  80. // Get a bunch of data for a calendar
  81. //
  82. internal CalendarData(string localeName, CalendarId calendarId, bool bUseUserOverrides)
  83. {
  84. this.bUseUserOverrides = bUseUserOverrides;
  85. Debug.Assert(!GlobalizationMode.Invariant);
  86. if (!LoadCalendarDataFromSystem(localeName, calendarId))
  87. {
  88. Debug.Fail("[CalendarData] LoadCalendarDataFromSystem call isn't expected to fail for calendar " + calendarId + " locale " + localeName);
  89. // Something failed, try invariant for missing parts
  90. // This is really not good, but we don't want the callers to crash.
  91. if (this.sNativeName == null) this.sNativeName = string.Empty; // Calendar Name for the locale.
  92. // Formats
  93. if (this.saShortDates == null) this.saShortDates = Invariant.saShortDates; // Short Data format, default first
  94. if (this.saYearMonths == null) this.saYearMonths = Invariant.saYearMonths; // Year/Month Data format, default first
  95. if (this.saLongDates == null) this.saLongDates = Invariant.saLongDates; // Long Data format, default first
  96. if (this.sMonthDay == null) this.sMonthDay = Invariant.sMonthDay; // Month/Day format
  97. // Calendar Parts Names
  98. if (this.saEraNames == null) this.saEraNames = Invariant.saEraNames; // Names of Eras
  99. if (this.saAbbrevEraNames == null) this.saAbbrevEraNames = Invariant.saAbbrevEraNames; // Abbreviated Era Names
  100. if (this.saAbbrevEnglishEraNames == null) this.saAbbrevEnglishEraNames = Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
  101. if (this.saDayNames == null) this.saDayNames = Invariant.saDayNames; // Day Names, null to use locale data, starts on Sunday
  102. if (this.saAbbrevDayNames == null) this.saAbbrevDayNames = Invariant.saAbbrevDayNames; // Abbrev Day Names, null to use locale data, starts on Sunday
  103. if (this.saSuperShortDayNames == null) this.saSuperShortDayNames = Invariant.saSuperShortDayNames; // Super short Day of week names
  104. if (this.saMonthNames == null) this.saMonthNames = Invariant.saMonthNames; // Month Names (13)
  105. if (this.saAbbrevMonthNames == null) this.saAbbrevMonthNames = Invariant.saAbbrevMonthNames; // Abbrev Month Names (13)
  106. // Genitive and Leap names can follow the fallback below
  107. }
  108. if (calendarId == CalendarId.TAIWAN)
  109. {
  110. if (SystemSupportsTaiwaneseCalendar())
  111. {
  112. // We got the month/day names from the OS (same as gregorian), but the native name is wrong
  113. this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
  114. }
  115. else
  116. {
  117. this.sNativeName = string.Empty;
  118. }
  119. }
  120. // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
  121. if (this.saMonthGenitiveNames == null || this.saMonthGenitiveNames.Length == 0 || string.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
  122. this.saMonthGenitiveNames = this.saMonthNames; // Genitive month names (same as month names for invariant)
  123. if (this.saAbbrevMonthGenitiveNames == null || this.saAbbrevMonthGenitiveNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
  124. this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
  125. if (this.saLeapYearMonthNames == null || this.saLeapYearMonthNames.Length == 0 || string.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
  126. this.saLeapYearMonthNames = this.saMonthNames;
  127. InitializeEraNames(localeName, calendarId);
  128. InitializeAbbreviatedEraNames(localeName, calendarId);
  129. // Abbreviated English Era Names are only used for the Japanese calendar.
  130. if (calendarId == CalendarId.JAPAN)
  131. {
  132. this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
  133. }
  134. else
  135. {
  136. // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
  137. this.saAbbrevEnglishEraNames = new string[] { "" };
  138. }
  139. // Japanese is the only thing with > 1 era. Its current era # is how many ever
  140. // eras are in the array. (And the others all have 1 string in the array)
  141. this.iCurrentEra = this.saEraNames.Length;
  142. }
  143. private void InitializeEraNames(string localeName, CalendarId calendarId)
  144. {
  145. // Note that the saEraNames only include "A.D." We don't have localized names for other calendars available from windows
  146. switch (calendarId)
  147. {
  148. // For Localized Gregorian we really expect the data from the OS.
  149. case CalendarId.GREGORIAN:
  150. // Fallback for CoreCLR < Win7 or culture.dll missing
  151. if (this.saEraNames == null || this.saEraNames.Length == 0 || string.IsNullOrEmpty(this.saEraNames[0]))
  152. {
  153. this.saEraNames = new string[] { "A.D." };
  154. }
  155. break;
  156. // The rest of the calendars have constant data, so we'll just use that
  157. case CalendarId.GREGORIAN_US:
  158. case CalendarId.JULIAN:
  159. this.saEraNames = new string[] { "A.D." };
  160. break;
  161. case CalendarId.HEBREW:
  162. this.saEraNames = new string[] { "C.E." };
  163. break;
  164. case CalendarId.HIJRI:
  165. case CalendarId.UMALQURA:
  166. if (localeName == "dv-MV")
  167. {
  168. // Special case for Divehi
  169. this.saEraNames = new string[] { "\x0780\x07a8\x0796\x07b0\x0783\x07a9" };
  170. }
  171. else
  172. {
  173. this.saEraNames = new string[] { "\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629" };
  174. }
  175. break;
  176. case CalendarId.GREGORIAN_ARABIC:
  177. case CalendarId.GREGORIAN_XLIT_ENGLISH:
  178. case CalendarId.GREGORIAN_XLIT_FRENCH:
  179. // These are all the same:
  180. this.saEraNames = new string[] { "\x0645" };
  181. break;
  182. case CalendarId.GREGORIAN_ME_FRENCH:
  183. this.saEraNames = new string[] { "ap. J.-C." };
  184. break;
  185. case CalendarId.TAIWAN:
  186. if (SystemSupportsTaiwaneseCalendar())
  187. {
  188. this.saEraNames = new string[] { "\x4e2d\x83ef\x6c11\x570b" };
  189. }
  190. else
  191. {
  192. this.saEraNames = new string[] { string.Empty };
  193. }
  194. break;
  195. case CalendarId.KOREA:
  196. this.saEraNames = new string[] { "\xb2e8\xae30" };
  197. break;
  198. case CalendarId.THAI:
  199. this.saEraNames = new string[] { "\x0e1e\x002e\x0e28\x002e" };
  200. break;
  201. case CalendarId.JAPAN:
  202. case CalendarId.JAPANESELUNISOLAR:
  203. this.saEraNames = JapaneseCalendar.EraNames();
  204. break;
  205. case CalendarId.PERSIAN:
  206. if (this.saEraNames == null || this.saEraNames.Length == 0 || string.IsNullOrEmpty(this.saEraNames[0]))
  207. {
  208. this.saEraNames = new string[] { "\x0647\x002e\x0634" };
  209. }
  210. break;
  211. default:
  212. // Most calendars are just "A.D."
  213. this.saEraNames = Invariant.saEraNames;
  214. break;
  215. }
  216. }
  217. private void InitializeAbbreviatedEraNames(string localeName, CalendarId calendarId)
  218. {
  219. // Note that the saAbbrevEraNames only include "AD" We don't have localized names for other calendars available from windows
  220. switch (calendarId)
  221. {
  222. // For Localized Gregorian we really expect the data from the OS.
  223. case CalendarId.GREGORIAN:
  224. // Fallback for CoreCLR < Win7 or culture.dll missing
  225. if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevEraNames[0]))
  226. {
  227. this.saAbbrevEraNames = new string[] { "AD" };
  228. }
  229. break;
  230. // The rest of the calendars have constant data, so we'll just use that
  231. case CalendarId.GREGORIAN_US:
  232. case CalendarId.JULIAN:
  233. this.saAbbrevEraNames = new string[] { "AD" };
  234. break;
  235. case CalendarId.JAPAN:
  236. case CalendarId.JAPANESELUNISOLAR:
  237. this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
  238. break;
  239. case CalendarId.HIJRI:
  240. case CalendarId.UMALQURA:
  241. if (localeName == "dv-MV")
  242. {
  243. // Special case for Divehi
  244. this.saAbbrevEraNames = new string[] { "\x0780\x002e" };
  245. }
  246. else
  247. {
  248. this.saAbbrevEraNames = new string[] { "\x0647\x0640" };
  249. }
  250. break;
  251. case CalendarId.TAIWAN:
  252. // Get era name and abbreviate it
  253. this.saAbbrevEraNames = new string[1];
  254. if (this.saEraNames[0].Length == 4)
  255. {
  256. this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2, 2);
  257. }
  258. else
  259. {
  260. this.saAbbrevEraNames[0] = this.saEraNames[0];
  261. }
  262. break;
  263. case CalendarId.PERSIAN:
  264. if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || string.IsNullOrEmpty(this.saAbbrevEraNames[0]))
  265. {
  266. this.saAbbrevEraNames = this.saEraNames;
  267. }
  268. break;
  269. default:
  270. // Most calendars just use the full name
  271. this.saAbbrevEraNames = this.saEraNames;
  272. break;
  273. }
  274. }
  275. internal static CalendarData GetCalendarData(CalendarId calendarId)
  276. {
  277. //
  278. // Get a calendar.
  279. // Unfortunately we depend on the locale in the OS, so we need a locale
  280. // no matter what. So just get the appropriate calendar from the
  281. // appropriate locale here
  282. //
  283. // Get a culture name
  284. // TODO: Note that this doesn't handle the new calendars (lunisolar, etc)
  285. string culture = CalendarIdToCultureName(calendarId);
  286. // Return our calendar
  287. return CultureInfo.GetCultureInfo(culture)._cultureData.GetCalendar(calendarId);
  288. }
  289. private static string CalendarIdToCultureName(CalendarId calendarId)
  290. {
  291. switch (calendarId)
  292. {
  293. case CalendarId.GREGORIAN_US:
  294. return "fa-IR"; // "fa-IR" Iran
  295. case CalendarId.JAPAN:
  296. return "ja-JP"; // "ja-JP" Japan
  297. case CalendarId.TAIWAN:
  298. return "zh-TW"; // zh-TW Taiwan
  299. case CalendarId.KOREA:
  300. return "ko-KR"; // "ko-KR" Korea
  301. case CalendarId.HIJRI:
  302. case CalendarId.GREGORIAN_ARABIC:
  303. case CalendarId.UMALQURA:
  304. return "ar-SA"; // "ar-SA" Saudi Arabia
  305. case CalendarId.THAI:
  306. return "th-TH"; // "th-TH" Thailand
  307. case CalendarId.HEBREW:
  308. return "he-IL"; // "he-IL" Israel
  309. case CalendarId.GREGORIAN_ME_FRENCH:
  310. return "ar-DZ"; // "ar-DZ" Algeria
  311. case CalendarId.GREGORIAN_XLIT_ENGLISH:
  312. case CalendarId.GREGORIAN_XLIT_FRENCH:
  313. return "ar-IQ"; // "ar-IQ"; Iraq
  314. default:
  315. // Default to gregorian en-US
  316. break;
  317. }
  318. return "en-US";
  319. }
  320. }
  321. }