CalendarData.cs 19 KB

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