HebrewCalendar.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  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. /// <remarks>
  8. /// Rules for the Hebrew calendar:
  9. /// - The Hebrew calendar is both a Lunar (months) and Solar (years)
  10. /// calendar, but allows for a week of seven days.
  11. /// - Days begin at sunset.
  12. /// - Leap Years occur in the 3, 6, 8, 11, 14, 17, &amp; 19th years of a
  13. /// 19-year cycle. Year = leap iff ((7y+1) mod 19 &lt; 7).
  14. /// - There are 12 months in a common year and 13 months in a leap year.
  15. /// - In a common year, the 6th month, Adar, has 29 days. In a leap
  16. /// year, the 6th month, Adar I, has 30 days and the leap month,
  17. /// Adar II, has 29 days.
  18. /// - Common years have 353-355 days. Leap years have 383-385 days.
  19. /// - The Hebrew new year (Rosh HaShanah) begins on the 1st of Tishri,
  20. /// the 7th month in the list below.
  21. /// - The new year may not begin on Sunday, Wednesday, or Friday.
  22. /// - If the new year would fall on a Tuesday and the conjunction of
  23. /// the following year were at midday or later, the new year is
  24. /// delayed until Thursday.
  25. /// - If the new year would fall on a Monday after a leap year, the
  26. /// new year is delayed until Tuesday.
  27. /// - The length of the 8th and 9th months vary from year to year,
  28. /// depending on the overall length of the year.
  29. /// - The length of a year is determined by the dates of the new
  30. /// years (Tishri 1) preceding and following the year in question.
  31. /// - The 2th month is long (30 days) if the year has 355 or 385 days.
  32. /// - The 3th month is short (29 days) if the year has 353 or 383 days.
  33. /// - The Hebrew months are:
  34. /// 1. Tishri (30 days)
  35. /// 2. Heshvan (29 or 30 days)
  36. /// 3. Kislev (29 or 30 days)
  37. /// 4. Teveth (29 days)
  38. /// 5. Shevat (30 days)
  39. /// 6. Adar I (30 days)
  40. /// 7. Adar {II} (29 days, this only exists if that year is a leap year)
  41. /// 8. Nisan (30 days)
  42. /// 9. Iyyar (29 days)
  43. /// 10. Sivan (30 days)
  44. /// 11. Tammuz (29 days)
  45. /// 12. Av (30 days)
  46. /// 13. Elul (29 days)
  47. /// Calendar support range:
  48. /// Calendar Minimum Maximum
  49. /// ========== ========== ==========
  50. /// Gregorian 1583/01/01 2239/09/29
  51. /// Hebrew 5343/04/07 5999/13/29
  52. ///
  53. /// Includes CHebrew implemetation;i.e All the code necessary for converting
  54. /// Gregorian to Hebrew Lunar from 1583 to 2239.
  55. /// </remarks>
  56. public class HebrewCalendar : Calendar
  57. {
  58. public static readonly int HebrewEra = 1;
  59. private const int DatePartYear = 0;
  60. private const int DatePartMonth = 2;
  61. private const int DatePartDay = 3;
  62. // Hebrew Translation Table.
  63. //
  64. // This table is used to get the following Hebrew calendar information for a
  65. // given Gregorian year:
  66. // 1. The day of the Hebrew month corresponding to Gregorian January 1st
  67. // for a given Gregorian year.
  68. // 2. The month of the Hebrew month corresponding to Gregorian January 1st
  69. // for a given Gregorian year.
  70. // The information is not directly in the table. Instead, the info is decoded
  71. // by special values (numbers above 29 and below 1).
  72. // 3. The type of the Hebrew year for a given Gregorian year.
  73. //
  74. // More notes:
  75. // This table includes 2 numbers for each year.
  76. // The offset into the table determines the year. (offset 0 is Gregorian year 1500)
  77. // 1st number determines the day of the Hebrew month coresponeds to January 1st.
  78. // 2nd number determines the type of the Hebrew year. (the type determines how
  79. // many days are there in the year.)
  80. //
  81. // normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
  82. // Leap years : 4 = 383 5 384 6 = 385 days.
  83. //
  84. // A 99 means the year is not supported for translation.
  85. // for convenience the table was defined for 750 year,
  86. // but only 640 years are supported. (from 1583 to 2239)
  87. // the years before 1582 (starting of Georgian calendar)
  88. // and after 2239, are filled with 99.
  89. //
  90. // Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
  91. // That's why, there no nead to specify the lunar month in the table.
  92. // There are exceptions, these are coded by giving numbers above 29 and below 1.
  93. // Actual decoding is takenig place whenever fetching information from the table.
  94. // The function for decoding is in GetLunarMonthDay().
  95. //
  96. // Example:
  97. // The data for 2000 - 2005 A.D. is:
  98. // 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
  99. //
  100. // For year 2000, we know it has a Hebrew year type 6, which means it has 385 days.
  101. // And 1/1/2000 A.D. is Hebrew year 5760, 23rd day of 4th month.
  102. //
  103. // Jewish Era in use today is dated from the supposed year of the
  104. // Creation with its beginning in 3761 B.C.
  105. //
  106. // The Hebrew year of Gregorian 1st year AD.
  107. // 0001/01/01 AD is Hebrew 3760/01/01
  108. private const int HebrewYearOf1AD = 3760;
  109. private const int FirstGregorianTableYear = 1583; // == Hebrew Year 5343
  110. private const int LastGregorianTableYear = 2239; // == Hebrew Year 5999
  111. private const int TableSize = (LastGregorianTableYear - FirstGregorianTableYear);
  112. private const int MinHebrewYear = HebrewYearOf1AD + FirstGregorianTableYear; // == 5343
  113. private const int MaxHebrewYear = HebrewYearOf1AD + LastGregorianTableYear; // == 5999
  114. private static readonly byte[] s_hebrewTable =
  115. {
  116. 7,3,17,3, // 1583-1584 (Hebrew year: 5343 - 5344)
  117. 0,4,11,2,21,6,1,3,13,2, // 1585-1589
  118. 25,4,5,3,16,2,27,6,9,1, // 1590-1594
  119. 20,2,0,6,11,3,23,4,4,2, // 1595-1599
  120. 14,3,27,4,8,2,18,3,28,6, // 1600
  121. 11,1,22,5,2,3,12,3,25,4, // 1605
  122. 6,2,16,3,26,6,8,2,20,1, // 1610
  123. 0,6,11,2,24,4,4,3,15,2, // 1615
  124. 25,6,8,1,19,2,29,6,9,3, // 1620
  125. 22,4,3,2,13,3,25,4,6,3, // 1625
  126. 17,2,27,6,7,3,19,2,31,4, // 1630
  127. 11,3,23,4,5,2,15,3,25,6, // 1635
  128. 6,2,19,1,29,6,10,2,22,4, // 1640
  129. 3,3,14,2,24,6,6,1,17,3, // 1645
  130. 28,5,8,3,20,1,32,5,12,3, // 1650
  131. 22,6,4,1,16,2,26,6,6,3, // 1655
  132. 17,2,0,4,10,3,22,4,3,2, // 1660
  133. 14,3,24,6,5,2,17,1,28,6, // 1665
  134. 9,2,19,3,31,4,13,2,23,6, // 1670
  135. 3,3,15,1,27,5,7,3,17,3, // 1675
  136. 29,4,11,2,21,6,3,1,14,2, // 1680
  137. 25,6,5,3,16,2,28,4,9,3, // 1685
  138. 20,2,0,6,12,1,23,6,4,2, // 1690
  139. 14,3,26,4,8,2,18,3,0,4, // 1695
  140. 10,3,21,5,1,3,13,1,24,5, // 1700
  141. 5,3,15,3,27,4,8,2,19,3, // 1705
  142. 29,6,10,2,22,4,3,3,14,2, // 1710
  143. 26,4,6,3,18,2,28,6,10,1, // 1715
  144. 20,6,2,2,12,3,24,4,5,2, // 1720
  145. 16,3,28,4,8,3,19,2,0,6, // 1725
  146. 12,1,23,5,3,3,14,3,26,4, // 1730
  147. 7,2,17,3,28,6,9,2,21,4, // 1735
  148. 1,3,13,2,25,4,5,3,16,2, // 1740
  149. 27,6,9,1,19,3,0,5,11,3, // 1745
  150. 23,4,4,2,14,3,25,6,7,1, // 1750
  151. 18,2,28,6,9,3,21,4,2,2, // 1755
  152. 12,3,25,4,6,2,16,3,26,6, // 1760
  153. 8,2,20,1,0,6,11,2,22,6, // 1765
  154. 4,1,15,2,25,6,6,3,18,1, // 1770
  155. 29,5,9,3,22,4,2,3,13,2, // 1775
  156. 23,6,4,3,15,2,27,4,7,3, // 1780
  157. 19,2,31,4,11,3,21,6,3,2, // 1785
  158. 15,1,25,6,6,2,17,3,29,4, // 1790
  159. 10,2,20,6,3,1,13,3,24,5, // 1795
  160. 4,3,16,1,27,5,7,3,17,3, // 1800
  161. 0,4,11,2,21,6,1,3,13,2, // 1805
  162. 25,4,5,3,16,2,29,4,9,3, // 1810
  163. 19,6,30,2,13,1,23,6,4,2, // 1815
  164. 14,3,27,4,8,2,18,3,0,4, // 1820
  165. 11,3,22,5,2,3,14,1,26,5, // 1825
  166. 6,3,16,3,28,4,10,2,20,6, // 1830
  167. 30,3,11,2,24,4,4,3,15,2, // 1835
  168. 25,6,8,1,19,2,29,6,9,3, // 1840
  169. 22,4,3,2,13,3,25,4,7,2, // 1845
  170. 17,3,27,6,9,1,21,5,1,3, // 1850
  171. 11,3,23,4,5,2,15,3,25,6, // 1855
  172. 6,2,19,1,29,6,10,2,22,4, // 1860
  173. 3,3,14,2,24,6,6,1,18,2, // 1865
  174. 28,6,8,3,20,4,2,2,12,3, // 1870
  175. 24,4,4,3,16,2,26,6,6,3, // 1875
  176. 17,2,0,4,10,3,22,4,3,2, // 1880
  177. 14,3,24,6,5,2,17,1,28,6, // 1885
  178. 9,2,21,4,1,3,13,2,23,6, // 1890
  179. 5,1,15,3,27,5,7,3,19,1, // 1895
  180. 0,5,10,3,22,4,2,3,13,2, // 1900
  181. 24,6,4,3,15,2,27,4,8,3, // 1905
  182. 20,4,1,2,11,3,22,6,3,2, // 1910
  183. 15,1,25,6,7,2,17,3,29,4, // 1915
  184. 10,2,21,6,1,3,13,1,24,5, // 1920
  185. 5,3,15,3,27,4,8,2,19,6, // 1925
  186. 1,1,12,2,22,6,3,3,14,2, // 1930
  187. 26,4,6,3,18,2,28,6,10,1, // 1935
  188. 20,6,2,2,12,3,24,4,5,2, // 1940
  189. 16,3,28,4,9,2,19,6,30,3, // 1945
  190. 12,1,23,5,3,3,14,3,26,4, // 1950
  191. 7,2,17,3,28,6,9,2,21,4, // 1955
  192. 1,3,13,2,25,4,5,3,16,2, // 1960
  193. 27,6,9,1,19,6,30,2,11,3, // 1965
  194. 23,4,4,2,14,3,27,4,7,3, // 1970
  195. 18,2,28,6,11,1,22,5,2,3, // 1975
  196. 12,3,25,4,6,2,16,3,26,6, // 1980
  197. 8,2,20,4,30,3,11,2,24,4, // 1985
  198. 4,3,15,2,25,6,8,1,18,3, // 1990
  199. 29,5,9,3,22,4,3,2,13,3, // 1995
  200. 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
  201. 20,4,1,2,11,3,23,4,5,2, // 2005 - 2009
  202. 15,3,25,6,6,2,19,1,29,6, // 2010
  203. 10,2,20,6,3,1,14,2,24,6, // 2015
  204. 4,3,17,1,28,5,8,3,20,4, // 2020
  205. 1,3,12,2,22,6,2,3,14,2, // 2025
  206. 26,4,6,3,17,2,0,4,10,3, // 2030
  207. 20,6,1,2,14,1,24,6,5,2, // 2035
  208. 15,3,28,4,9,2,19,6,1,1, // 2040
  209. 12,3,23,5,3,3,15,1,27,5, // 2045
  210. 7,3,17,3,29,4,11,2,21,6, // 2050
  211. 1,3,12,2,25,4,5,3,16,2, // 2055
  212. 28,4,9,3,19,6,30,2,12,1, // 2060
  213. 23,6,4,2,14,3,26,4,8,2, // 2065
  214. 18,3,0,4,10,3,22,5,2,3, // 2070
  215. 14,1,25,5,6,3,16,3,28,4, // 2075
  216. 9,2,20,6,30,3,11,2,23,4, // 2080
  217. 4,3,15,2,27,4,7,3,19,2, // 2085
  218. 29,6,11,1,21,6,3,2,13,3, // 2090
  219. 25,4,6,2,17,3,27,6,9,1, // 2095
  220. 20,5,30,3,10,3,22,4,3,2, // 2100
  221. 14,3,24,6,5,2,17,1,28,6, // 2105
  222. 9,2,21,4,1,3,13,2,23,6, // 2110
  223. 5,1,16,2,27,6,7,3,19,4, // 2115
  224. 30,2,11,3,23,4,3,3,14,2, // 2120
  225. 25,6,5,3,16,2,28,4,9,3, // 2125
  226. 21,4,2,2,12,3,23,6,4,2, // 2130
  227. 16,1,26,6,8,2,20,4,30,3, // 2135
  228. 11,2,22,6,4,1,14,3,25,5, // 2140
  229. 6,3,18,1,29,5,9,3,22,4, // 2145
  230. 2,3,13,2,23,6,4,3,15,2, // 2150
  231. 27,4,7,3,20,4,1,2,11,3, // 2155
  232. 21,6,3,2,15,1,25,6,6,2, // 2160
  233. 17,3,29,4,10,2,20,6,3,1, // 2165
  234. 13,3,24,5,4,3,17,1,28,5, // 2170
  235. 8,3,18,6,1,1,12,2,22,6, // 2175
  236. 2,3,14,2,26,4,6,3,17,2, // 2180
  237. 28,6,10,1,20,6,1,2,12,3, // 2185
  238. 24,4,5,2,15,3,28,4,9,2, // 2190
  239. 19,6,33,3,12,1,23,5,3,3, // 2195
  240. 13,3,25,4,6,2,16,3,26,6, // 2200
  241. 8,2,20,4,30,3,11,2,24,4, // 2205
  242. 4,3,15,2,25,6,8,1,18,6, // 2210
  243. 33,2,9,3,22,4,3,2,13,3, // 2215
  244. 25,4,6,3,17,2,27,6,9,1, // 2220
  245. 21,5,1,3,11,3,23,4,5,2, // 2225
  246. 15,3,25,6,6,2,19,4,33,3, // 2230
  247. 10,2,22,4,3,3,14,2,24,6, // 2235
  248. 6,1 // 2240 (Hebrew year: 6000)
  249. };
  250. private const int MaxMonthPlusOne = 14;
  251. // The lunar calendar has 6 different variations of month lengths
  252. // within a year.
  253. private static readonly byte[] s_lunarMonthLen =
  254. {
  255. 0,00,00,00,00,00,00,00,00,00,00,00,00,0,
  256. 0,30,29,29,29,30,29,30,29,30,29,30,29,0, // 3 common year variations
  257. 0,30,29,30,29,30,29,30,29,30,29,30,29,0,
  258. 0,30,30,30,29,30,29,30,29,30,29,30,29,0,
  259. 0,30,29,29,29,30,30,29,30,29,30,29,30,29, // 3 leap year variations
  260. 0,30,29,30,29,30,30,29,30,29,30,29,30,29,
  261. 0,30,30,30,29,30,30,29,30,29,30,29,30,29
  262. };
  263. private static readonly DateTime s_calendarMinValue = new DateTime(1583, 1, 1);
  264. // Gregorian 2239/9/29 = Hebrew 5999/13/29 (last day in Hebrew year 5999).
  265. // We can only format/parse Hebrew numbers up to 999, so we limit the max range to Hebrew year 5999.
  266. private static readonly DateTime s_calendarMaxValue = new DateTime((new DateTime(2239, 9, 29, 23, 59, 59, 999)).Ticks + 9999);
  267. public override DateTime MinSupportedDateTime => s_calendarMinValue;
  268. public override DateTime MaxSupportedDateTime => s_calendarMaxValue;
  269. public override CalendarAlgorithmType AlgorithmType => CalendarAlgorithmType.LunisolarCalendar;
  270. public HebrewCalendar()
  271. {
  272. }
  273. internal override CalendarId ID => CalendarId.HEBREW;
  274. private static void CheckHebrewYearValue(int y, int era, string varName)
  275. {
  276. CheckEraRange(era);
  277. if (y > MaxHebrewYear || y < MinHebrewYear)
  278. {
  279. throw new ArgumentOutOfRangeException(
  280. varName,
  281. y,
  282. SR.Format(SR.ArgumentOutOfRange_Range, MinHebrewYear, MaxHebrewYear));
  283. }
  284. }
  285. /// <summary>
  286. /// Check if the Hebrew month value is valid.
  287. /// </summary>
  288. /// <remarks>
  289. /// Call CheckHebrewYearValue() before calling this to verify the year value is supported.
  290. /// </remarks>
  291. private void CheckHebrewMonthValue(int year, int month, int era)
  292. {
  293. int monthsInYear = GetMonthsInYear(year, era);
  294. if (month < 1 || month > monthsInYear)
  295. {
  296. throw new ArgumentOutOfRangeException(
  297. nameof(month),
  298. month,
  299. SR.Format(SR.ArgumentOutOfRange_Range, 1, monthsInYear));
  300. }
  301. }
  302. /// <summary>
  303. /// Check if the Hebrew day value is valid.
  304. /// </summary>
  305. /// <remarks>
  306. /// Call CheckHebrewYearValue()/CheckHebrewMonthValue() before calling this to verify the year/month values are valid.
  307. /// </remarks>
  308. private void CheckHebrewDayValue(int year, int month, int day, int era)
  309. {
  310. int daysInMonth = GetDaysInMonth(year, month, era);
  311. if (day < 1 || day > daysInMonth)
  312. {
  313. throw new ArgumentOutOfRangeException(
  314. nameof(day),
  315. day,
  316. SR.Format(SR.ArgumentOutOfRange_Range, 1, daysInMonth));
  317. }
  318. }
  319. private static void CheckEraRange(int era)
  320. {
  321. if (era != CurrentEra && era != HebrewEra)
  322. {
  323. throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue);
  324. }
  325. }
  326. private static void CheckTicksRange(long ticks)
  327. {
  328. if (ticks < s_calendarMinValue.Ticks || ticks > s_calendarMaxValue.Ticks)
  329. {
  330. throw new ArgumentOutOfRangeException(
  331. "time",
  332. ticks,
  333. // Print out the date in Gregorian using InvariantCulture since the DateTime is based on GreograinCalendar.
  334. SR.Format(
  335. CultureInfo.InvariantCulture,
  336. SR.ArgumentOutOfRange_CalendarRange,
  337. s_calendarMinValue,
  338. s_calendarMaxValue));
  339. }
  340. }
  341. private static int GetResult(DateBuffer result, int part)
  342. {
  343. switch (part)
  344. {
  345. case DatePartYear:
  346. return result.year;
  347. case DatePartMonth:
  348. return result.month;
  349. case DatePartDay:
  350. return result.day;
  351. }
  352. throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
  353. }
  354. /// <summary>
  355. /// Using the Hebrew table (HebrewTable) to get the Hebrew month/day value for Gregorian January 1st
  356. /// in a given Gregorian year.
  357. /// Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
  358. /// That's why, there no nead to specify the lunar month in the table. There are exceptions, and these
  359. /// are coded by giving numbers above 29 and below 1.
  360. /// Actual decoding is takenig place in the switch statement below.
  361. /// </summary>
  362. /// <returns>
  363. /// The Hebrew year type. The value is from 1 to 6.
  364. /// normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
  365. /// Leap years : 4 = 383 5 384 6 = 385 days.
  366. /// </returns>
  367. internal static int GetLunarMonthDay(int gregorianYear, DateBuffer lunarDate)
  368. {
  369. // Get the offset into the LunarMonthLen array and the lunar day
  370. // for January 1st.
  371. int index = gregorianYear - FirstGregorianTableYear;
  372. if (index < 0 || index > TableSize)
  373. {
  374. throw new ArgumentOutOfRangeException(nameof(gregorianYear));
  375. }
  376. index *= 2;
  377. lunarDate.day = s_hebrewTable[index];
  378. // Get the type of the year. The value is from 1 to 6
  379. int lunarYearType = s_hebrewTable[index + 1];
  380. // Get the Lunar Month.
  381. switch (lunarDate.day)
  382. {
  383. case 0: // 1/1 is on Shvat 1
  384. lunarDate.month = 5;
  385. lunarDate.day = 1;
  386. break;
  387. case 30: // 1/1 is on Kislev 30
  388. lunarDate.month = 3;
  389. break;
  390. case 31: // 1/1 is on Shvat 2
  391. lunarDate.month = 5;
  392. lunarDate.day = 2;
  393. break;
  394. case 32: // 1/1 is on Shvat 3
  395. lunarDate.month = 5;
  396. lunarDate.day = 3;
  397. break;
  398. case 33: // 1/1 is on Kislev 29
  399. lunarDate.month = 3;
  400. lunarDate.day = 29;
  401. break;
  402. default: // 1/1 is on Tevet (This is the general case)
  403. lunarDate.month = 4;
  404. break;
  405. }
  406. return lunarYearType;
  407. }
  408. /// <summary>
  409. /// Returns a given date part of this DateTime. This method is used
  410. /// to compute the year, day-of-year, month, or day part.
  411. /// </summary>
  412. internal virtual int GetDatePart(long ticks, int part)
  413. {
  414. // The Gregorian year, month, day value for ticks.
  415. int gregorianYear, gregorianMonth, gregorianDay;
  416. int hebrewYearType; // lunar year type
  417. long AbsoluteDate; // absolute date - absolute date 1/1/1600
  418. // Make sure we have a valid Gregorian date that will fit into our
  419. // Hebrew conversion limits.
  420. CheckTicksRange(ticks);
  421. DateTime time = new DateTime(ticks);
  422. // Save the Gregorian date values.
  423. time.GetDatePart(out gregorianYear, out gregorianMonth, out gregorianDay);
  424. DateBuffer lunarDate = new DateBuffer(); // lunar month and day for Jan 1
  425. // From the table looking-up value of HebrewTable[index] (stored in lunarDate.day), we get the
  426. // lunar month and lunar day where the Gregorian date 1/1 falls.
  427. lunarDate.year = gregorianYear + HebrewYearOf1AD;
  428. hebrewYearType = GetLunarMonthDay(gregorianYear, lunarDate);
  429. // This is the buffer used to store the result Hebrew date.
  430. DateBuffer result = new DateBuffer();
  431. // Store the values for the start of the new year - 1/1.
  432. result.year = lunarDate.year;
  433. result.month = lunarDate.month;
  434. result.day = lunarDate.day;
  435. // Get the absolute date from 1/1/1600.
  436. AbsoluteDate = GregorianCalendar.GetAbsoluteDate(gregorianYear, gregorianMonth, gregorianDay);
  437. // If the requested date was 1/1, then we're done.
  438. if ((gregorianMonth == 1) && (gregorianDay == 1))
  439. {
  440. return GetResult(result, part);
  441. }
  442. // Calculate the number of days between 1/1 and the requested date.
  443. long numDays = AbsoluteDate - GregorianCalendar.GetAbsoluteDate(gregorianYear, 1, 1);
  444. // If the requested date is within the current lunar month, then
  445. // we're done.
  446. if ((numDays + (long)lunarDate.day) <= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month]))
  447. {
  448. result.day += (int)numDays;
  449. return GetResult(result, part);
  450. }
  451. // Adjust for the current partial month.
  452. result.month++;
  453. result.day = 1;
  454. // Adjust the Lunar Month and Year (if necessary) based on the number
  455. // of days between 1/1 and the requested date.
  456. // Assumes Jan 1 can never translate to the last Lunar month, which
  457. // is true.
  458. numDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month] - lunarDate.day);
  459. Debug.Assert(numDays >= 1, "NumDays >= 1");
  460. // If NumDays is 1, then we are done. Otherwise, find the correct Hebrew month
  461. // and day.
  462. if (numDays > 1)
  463. {
  464. // See if we're on the correct Lunar month.
  465. while (numDays > (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month]))
  466. {
  467. // Adjust the number of days and move to the next month.
  468. numDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month++]);
  469. // See if we need to adjust the Year.
  470. // Must handle both 12 and 13 month years.
  471. if ((result.month > 13) || (s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month] == 0))
  472. {
  473. // Adjust the Year.
  474. result.year++;
  475. hebrewYearType = s_hebrewTable[(gregorianYear + 1 - FirstGregorianTableYear) * 2 + 1];
  476. // Adjust the Month.
  477. result.month = 1;
  478. }
  479. }
  480. // Found the right Lunar month.
  481. result.day += (int)(numDays - 1);
  482. }
  483. return GetResult(result, part);
  484. }
  485. public override DateTime AddMonths(DateTime time, int months)
  486. {
  487. try
  488. {
  489. int y = GetDatePart(time.Ticks, DatePartYear);
  490. int m = GetDatePart(time.Ticks, DatePartMonth);
  491. int d = GetDatePart(time.Ticks, DatePartDay);
  492. int monthsInYear;
  493. int i;
  494. if (months >= 0)
  495. {
  496. i = m + months;
  497. while (i > (monthsInYear = GetMonthsInYear(y, CurrentEra)))
  498. {
  499. y++;
  500. i -= monthsInYear;
  501. }
  502. }
  503. else
  504. {
  505. if ((i = m + months) <= 0)
  506. {
  507. months = -months;
  508. months -= m;
  509. y--;
  510. while (months > (monthsInYear = GetMonthsInYear(y, CurrentEra)))
  511. {
  512. y--;
  513. months -= monthsInYear;
  514. }
  515. monthsInYear = GetMonthsInYear(y, CurrentEra);
  516. i = monthsInYear - months;
  517. }
  518. }
  519. int days = GetDaysInMonth(y, i);
  520. if (d > days)
  521. {
  522. d = days;
  523. }
  524. return new DateTime(ToDateTime(y, i, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay));
  525. }
  526. // We expect ArgumentException and ArgumentOutOfRangeException (which is subclass of ArgumentException)
  527. // If exception is thrown in the calls above, we are out of the supported range of this calendar.
  528. catch (ArgumentException)
  529. {
  530. throw new ArgumentOutOfRangeException(nameof(months), months, SR.ArgumentOutOfRange_AddValue);
  531. }
  532. }
  533. public override DateTime AddYears(DateTime time, int years)
  534. {
  535. int y = GetDatePart(time.Ticks, DatePartYear);
  536. int m = GetDatePart(time.Ticks, DatePartMonth);
  537. int d = GetDatePart(time.Ticks, DatePartDay);
  538. y += years;
  539. CheckHebrewYearValue(y, Calendar.CurrentEra, nameof(years));
  540. int months = GetMonthsInYear(y, CurrentEra);
  541. if (m > months)
  542. {
  543. m = months;
  544. }
  545. int days = GetDaysInMonth(y, m);
  546. if (d > days)
  547. {
  548. d = days;
  549. }
  550. long ticks = ToDateTime(y, m, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay);
  551. Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
  552. return new DateTime(ticks);
  553. }
  554. public override int GetDayOfMonth(DateTime time)
  555. {
  556. return GetDatePart(time.Ticks, DatePartDay);
  557. }
  558. public override DayOfWeek GetDayOfWeek(DateTime time)
  559. {
  560. // If we calculate back, the Hebrew day of week for Gregorian 0001/1/1 is Monday (1).
  561. // Therfore, the fomula is:
  562. return (DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7);
  563. }
  564. internal static int GetHebrewYearType(int year, int era)
  565. {
  566. CheckHebrewYearValue(year, era, nameof(year));
  567. // The HebrewTable is indexed by Gregorian year and starts from FirstGregorianYear.
  568. // So we need to convert year (Hebrew year value) to Gregorian Year below.
  569. return s_hebrewTable[(year - HebrewYearOf1AD - FirstGregorianTableYear) * 2 + 1];
  570. }
  571. public override int GetDayOfYear(DateTime time)
  572. {
  573. // Get Hebrew year value of the specified time.
  574. int year = GetYear(time);
  575. DateTime beginOfYearDate;
  576. if (year == 5343)
  577. {
  578. // Gregorian 1583/01/01 corresponds to Hebrew 5343/04/07 (MinSupportedDateTime)
  579. // To figure out the Gregorian date associated with Hebrew 5343/01/01, we need to
  580. // count the days from 5343/01/01 to 5343/04/07 and subtract that from Gregorian
  581. // 1583/01/01.
  582. // 1. Tishri (30 days)
  583. // 2. Heshvan (30 days since 5343 has 355 days)
  584. // 3. Kislev (30 days since 5343 has 355 days)
  585. // 96 days to get from 5343/01/01 to 5343/04/07
  586. // Gregorian 1583/01/01 - 96 days = 1582/9/27
  587. // the beginning of Hebrew year 5343 corresponds to Gregorian September 27, 1582.
  588. beginOfYearDate = new DateTime(1582, 9, 27);
  589. }
  590. else
  591. {
  592. // following line will fail when year is 5343 (first supported year)
  593. beginOfYearDate = ToDateTime(year, 1, 1, 0, 0, 0, 0, CurrentEra);
  594. }
  595. return (int)((time.Ticks - beginOfYearDate.Ticks) / TicksPerDay) + 1;
  596. }
  597. public override int GetDaysInMonth(int year, int month, int era)
  598. {
  599. CheckEraRange(era);
  600. int hebrewYearType = GetHebrewYearType(year, era);
  601. CheckHebrewMonthValue(year, month, era);
  602. Debug.Assert(hebrewYearType >= 1 && hebrewYearType <= 6,
  603. "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year);
  604. int monthDays = s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + month];
  605. if (monthDays == 0)
  606. {
  607. throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month);
  608. }
  609. return monthDays;
  610. }
  611. public override int GetDaysInYear(int year, int era)
  612. {
  613. CheckEraRange(era);
  614. // normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
  615. // Leap years : 4 = 383 5 384 6 = 385 days.
  616. // LunarYearType is from 1 to 6
  617. int LunarYearType = GetHebrewYearType(year, era);
  618. if (LunarYearType < 4)
  619. {
  620. // common year: LunarYearType = 1, 2, 3
  621. return 352 + LunarYearType;
  622. }
  623. return 382 + (LunarYearType - 3);
  624. }
  625. public override int GetEra(DateTime time) => HebrewEra;
  626. public override int[] Eras => new int[] { HebrewEra };
  627. public override int GetMonth(DateTime time)
  628. {
  629. return GetDatePart(time.Ticks, DatePartMonth);
  630. }
  631. public override int GetMonthsInYear(int year, int era)
  632. {
  633. return IsLeapYear(year, era) ? 13 : 12;
  634. }
  635. public override int GetYear(DateTime time)
  636. {
  637. return GetDatePart(time.Ticks, DatePartYear);
  638. }
  639. public override bool IsLeapDay(int year, int month, int day, int era)
  640. {
  641. if (IsLeapMonth(year, month, era))
  642. {
  643. // Every day in a leap month is a leap day.
  644. CheckHebrewDayValue(year, month, day, era);
  645. return true;
  646. }
  647. else if (IsLeapYear(year, Calendar.CurrentEra))
  648. {
  649. // There is an additional day in the 6th month in the leap year (the extra day is the 30th day in the 6th month),
  650. // so we should return true for 6/30 if that's in a leap year.
  651. if (month == 6 && day == 30)
  652. {
  653. return true;
  654. }
  655. }
  656. CheckHebrewDayValue(year, month, day, era);
  657. return false;
  658. }
  659. public override int GetLeapMonth(int year, int era)
  660. {
  661. // Year/era values are checked in IsLeapYear().
  662. if (IsLeapYear(year, era))
  663. {
  664. // The 7th month in a leap year is a leap month.
  665. return 7;
  666. }
  667. return 0;
  668. }
  669. public override bool IsLeapMonth(int year, int month, int era)
  670. {
  671. // Year/era values are checked in IsLeapYear().
  672. bool isLeapYear = IsLeapYear(year, era);
  673. CheckHebrewMonthValue(year, month, era);
  674. // The 7th month in a leap year is a leap month.
  675. return isLeapYear && month == 7;
  676. }
  677. public override bool IsLeapYear(int year, int era)
  678. {
  679. CheckHebrewYearValue(year, era, nameof(year));
  680. return ((7 * (long)year + 1) % 19) < 7;
  681. }
  682. /// <summary>
  683. /// (month1, day1) - (month2, day2)
  684. /// </summary>
  685. private static int GetDayDifference(int lunarYearType, int month1, int day1, int month2, int day2)
  686. {
  687. if (month1 == month2)
  688. {
  689. return (day1 - day2);
  690. }
  691. // Make sure that (month1, day1) < (month2, day2)
  692. bool swap = (month1 > month2);
  693. if (swap)
  694. {
  695. // (month1, day1) < (month2, day2). Swap the values.
  696. // The result will be a negative number.
  697. int tempMonth, tempDay;
  698. tempMonth = month1; tempDay = day1;
  699. month1 = month2; day1 = day2;
  700. month2 = tempMonth; day2 = tempDay;
  701. }
  702. // Get the number of days from (month1,day1) to (month1, end of month1)
  703. int days = s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + month1] - day1;
  704. // Move to next month.
  705. month1++;
  706. // Add up the days.
  707. while (month1 < month2)
  708. {
  709. days += s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + month1++];
  710. }
  711. days += day2;
  712. return swap ? days : -days;
  713. }
  714. /// <summary>
  715. /// Convert Hebrew date to Gregorian date.
  716. /// The algorithm is like this:
  717. /// The hebrew year has an offset to the Gregorian year, so we can guess the Gregorian year for
  718. /// the specified Hebrew year. That is, GreogrianYear = HebrewYear - FirstHebrewYearOf1AD.
  719. ///
  720. /// From the Gregorian year and HebrewTable, we can get the Hebrew month/day value
  721. /// of the Gregorian date January 1st. Let's call this month/day value [hebrewDateForJan1]
  722. ///
  723. /// If the requested Hebrew month/day is less than [hebrewDateForJan1], we know the result
  724. /// Gregorian date falls in previous year. So we decrease the Gregorian year value, and
  725. /// retrieve the Hebrew month/day value of the Gregorian date january 1st again.
  726. ///
  727. /// Now, we get the answer of the Gregorian year.
  728. ///
  729. /// The next step is to get the number of days between the requested Hebrew month/day
  730. /// and [hebrewDateForJan1]. When we get that, we can create the DateTime by adding/subtracting
  731. /// the ticks value of the number of days.
  732. /// </summary>
  733. private static DateTime HebrewToGregorian(int hebrewYear, int hebrewMonth, int hebrewDay, int hour, int minute, int second, int millisecond)
  734. {
  735. // Get the rough Gregorian year for the specified hebrewYear.
  736. int gregorianYear = hebrewYear - HebrewYearOf1AD;
  737. DateBuffer hebrewDateOfJan1 = new DateBuffer(); // year value is unused.
  738. int lunarYearType = GetLunarMonthDay(gregorianYear, hebrewDateOfJan1);
  739. if ((hebrewMonth == hebrewDateOfJan1.month) && (hebrewDay == hebrewDateOfJan1.day))
  740. {
  741. return new DateTime(gregorianYear, 1, 1, hour, minute, second, millisecond);
  742. }
  743. int days = GetDayDifference(lunarYearType, hebrewMonth, hebrewDay, hebrewDateOfJan1.month, hebrewDateOfJan1.day);
  744. DateTime gregorianNewYear = new DateTime(gregorianYear, 1, 1);
  745. return new DateTime(gregorianNewYear.Ticks + days * TicksPerDay + TimeToTicks(hour, minute, second, millisecond));
  746. }
  747. public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
  748. {
  749. CheckHebrewYearValue(year, era, nameof(year));
  750. CheckHebrewMonthValue(year, month, era);
  751. CheckHebrewDayValue(year, month, day, era);
  752. DateTime dt = HebrewToGregorian(year, month, day, hour, minute, second, millisecond);
  753. CheckTicksRange(dt.Ticks);
  754. return dt;
  755. }
  756. private const int DefaultTwoDigitYearMax = 5790;
  757. public override int TwoDigitYearMax
  758. {
  759. get
  760. {
  761. if (_twoDigitYearMax == -1)
  762. {
  763. _twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DefaultTwoDigitYearMax);
  764. }
  765. return _twoDigitYearMax;
  766. }
  767. set
  768. {
  769. VerifyWritable();
  770. if (value == 99)
  771. {
  772. // Do nothing here. Year 99 is allowed so that TwoDitYearMax is disabled.
  773. }
  774. else
  775. {
  776. CheckHebrewYearValue(value, HebrewEra, nameof(value));
  777. }
  778. _twoDigitYearMax = value;
  779. }
  780. }
  781. public override int ToFourDigitYear(int year)
  782. {
  783. if (year < 0)
  784. {
  785. throw new ArgumentOutOfRangeException(nameof(year), year, SR.ArgumentOutOfRange_NeedNonNegNum);
  786. }
  787. if (year < 100)
  788. {
  789. return base.ToFourDigitYear(year);
  790. }
  791. if (year > MaxHebrewYear || year < MinHebrewYear)
  792. {
  793. throw new ArgumentOutOfRangeException(
  794. nameof(year),
  795. year,
  796. SR.Format(SR.ArgumentOutOfRange_Range, MinHebrewYear, MaxHebrewYear));
  797. }
  798. return year;
  799. }
  800. internal class DateBuffer
  801. {
  802. internal int year;
  803. internal int month;
  804. internal int day;
  805. }
  806. }
  807. }