PersianCalendar.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. /// <summary>
  8. /// Modern Persian calendar is a solar observation based calendar. Each new year begins on the day when the vernal equinox occurs before noon.
  9. /// The epoch is the date of the vernal equinox prior to the epoch of the Islamic calendar (March 19, 622 Julian or March 22, 622 Gregorian)
  10. /// There is no Persian year 0. Ordinary years have 365 days. Leap years have 366 days with the last month (Esfand) gaining the extra day.
  11. /// </summary>
  12. /// <remarks>
  13. /// Calendar support range:
  14. /// Calendar Minimum Maximum
  15. /// ========== ========== ==========
  16. /// Gregorian 0622/03/22 9999/12/31
  17. /// Persian 0001/01/01 9378/10/13
  18. /// </remarks>
  19. public class PersianCalendar : Calendar
  20. {
  21. public static readonly int PersianEra = 1;
  22. private static readonly long s_persianEpoch = new DateTime(622, 3, 22).Ticks / GregorianCalendar.TicksPerDay;
  23. private const int ApproximateHalfYear = 180;
  24. private const int DatePartYear = 0;
  25. private const int DatePartDayOfYear = 1;
  26. private const int DatePartMonth = 2;
  27. private const int DatePartDay = 3;
  28. private const int MonthsPerYear = 12;
  29. private static readonly int[] s_daysToMonth = { 0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336, 366 };
  30. private const int MaxCalendarYear = 9378;
  31. private const int MaxCalendarMonth = 10;
  32. private const int MaxCalendarDay = 13;
  33. // Persian calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 3, day: 22)
  34. // This is the minimal Gregorian date that we support in the PersianCalendar.
  35. private static readonly DateTime s_minDate = new DateTime(622, 3, 22);
  36. private static readonly DateTime s_maxDate = DateTime.MaxValue;
  37. public override DateTime MinSupportedDateTime => s_minDate;
  38. public override DateTime MaxSupportedDateTime => s_maxDate;
  39. public override CalendarAlgorithmType AlgorithmType => CalendarAlgorithmType.SolarCalendar;
  40. public PersianCalendar()
  41. {
  42. }
  43. internal override CalendarId BaseCalendarID => CalendarId.GREGORIAN;
  44. internal override CalendarId ID => CalendarId.PERSIAN;
  45. private long GetAbsoluteDatePersian(int year, int month, int day)
  46. {
  47. if (year < 1 || year > MaxCalendarYear || month < 1 || month > 12)
  48. {
  49. throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
  50. }
  51. // day is one based, make 0 based since this will be the number of days we add to beginning of year below
  52. int ordinalDay = DaysInPreviousMonths(month) + day - 1;
  53. int approximateDaysFromEpochForYearStart = (int)(CalendricalCalculationsHelper.MeanTropicalYearInDays * (year - 1));
  54. long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(s_persianEpoch + approximateDaysFromEpochForYearStart + ApproximateHalfYear);
  55. yearStart += ordinalDay;
  56. return yearStart;
  57. }
  58. internal static void CheckTicksRange(long ticks)
  59. {
  60. if (ticks < s_minDate.Ticks || ticks > s_maxDate.Ticks)
  61. {
  62. throw new ArgumentOutOfRangeException(
  63. "time",
  64. ticks,
  65. SR.Format(SR.ArgumentOutOfRange_CalendarRange, s_minDate, s_maxDate));
  66. }
  67. }
  68. internal static void CheckEraRange(int era)
  69. {
  70. if (era != CurrentEra && era != PersianEra)
  71. {
  72. throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue);
  73. }
  74. }
  75. internal static void CheckYearRange(int year, int era)
  76. {
  77. CheckEraRange(era);
  78. if (year < 1 || year > MaxCalendarYear)
  79. {
  80. throw new ArgumentOutOfRangeException(
  81. nameof(year),
  82. year,
  83. SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarYear));
  84. }
  85. }
  86. internal static void CheckYearMonthRange(int year, int month, int era)
  87. {
  88. CheckYearRange(year, era);
  89. if (year == MaxCalendarYear)
  90. {
  91. if (month > MaxCalendarMonth)
  92. {
  93. throw new ArgumentOutOfRangeException(
  94. nameof(month),
  95. month,
  96. SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarMonth));
  97. }
  98. }
  99. if (month < 1 || month > 12)
  100. {
  101. throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month);
  102. }
  103. }
  104. private static int MonthFromOrdinalDay(int ordinalDay)
  105. {
  106. Debug.Assert(ordinalDay <= 366);
  107. int index = 0;
  108. while (ordinalDay > s_daysToMonth[index])
  109. {
  110. index++;
  111. }
  112. return index;
  113. }
  114. private static int DaysInPreviousMonths(int month)
  115. {
  116. Debug.Assert(1 <= month && month <= 12);
  117. // months are one based but for calculations use 0 based
  118. --month;
  119. return s_daysToMonth[month];
  120. }
  121. internal int GetDatePart(long ticks, int part)
  122. {
  123. CheckTicksRange(ticks);
  124. // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
  125. // 1/1/0001 is absolute date 1.
  126. long numDays = ticks / GregorianCalendar.TicksPerDay + 1;
  127. // Calculate the appromixate Persian Year.
  128. long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(numDays);
  129. int y = (int)(Math.Floor(((yearStart - s_persianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDays) + 0.5)) + 1;
  130. Debug.Assert(y >= 1);
  131. if (part == DatePartYear)
  132. {
  133. return y;
  134. }
  135. // Calculate the Persian Month.
  136. int ordinalDay = (int)(numDays - CalendricalCalculationsHelper.GetNumberOfDays(this.ToDateTime(y, 1, 1, 0, 0, 0, 0, 1)));
  137. if (part == DatePartDayOfYear)
  138. {
  139. return ordinalDay;
  140. }
  141. int m = MonthFromOrdinalDay(ordinalDay);
  142. Debug.Assert(ordinalDay >= 1);
  143. Debug.Assert(m >= 1 && m <= 12);
  144. if (part == DatePartMonth)
  145. {
  146. return m;
  147. }
  148. int d = ordinalDay - DaysInPreviousMonths(m);
  149. Debug.Assert(1 <= d);
  150. Debug.Assert(d <= 31);
  151. // Calculate the Persian Day.
  152. if (part == DatePartDay)
  153. {
  154. return d;
  155. }
  156. // Incorrect part value.
  157. throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
  158. }
  159. public override DateTime AddMonths(DateTime time, int months)
  160. {
  161. if (months < -120000 || months > 120000)
  162. {
  163. throw new ArgumentOutOfRangeException(
  164. nameof(months),
  165. months,
  166. SR.Format(SR.ArgumentOutOfRange_Range, -120000, 120000));
  167. }
  168. // Get the date in Persian calendar.
  169. int y = GetDatePart(time.Ticks, DatePartYear);
  170. int m = GetDatePart(time.Ticks, DatePartMonth);
  171. int d = GetDatePart(time.Ticks, DatePartDay);
  172. int i = m - 1 + months;
  173. if (i >= 0)
  174. {
  175. m = i % 12 + 1;
  176. y = y + i / 12;
  177. }
  178. else
  179. {
  180. m = 12 + (i + 1) % 12;
  181. y = y + (i - 11) / 12;
  182. }
  183. int days = GetDaysInMonth(y, m);
  184. if (d > days)
  185. {
  186. d = days;
  187. }
  188. long ticks = GetAbsoluteDatePersian(y, m, d) * TicksPerDay + time.Ticks % TicksPerDay;
  189. Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
  190. return new DateTime(ticks);
  191. }
  192. public override DateTime AddYears(DateTime time, int years)
  193. {
  194. return AddMonths(time, years * 12);
  195. }
  196. public override int GetDayOfMonth(DateTime time)
  197. {
  198. return GetDatePart(time.Ticks, DatePartDay);
  199. }
  200. public override DayOfWeek GetDayOfWeek(DateTime time)
  201. {
  202. return (DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7);
  203. }
  204. public override int GetDayOfYear(DateTime time)
  205. {
  206. return GetDatePart(time.Ticks, DatePartDayOfYear);
  207. }
  208. public override int GetDaysInMonth(int year, int month, int era)
  209. {
  210. CheckYearMonthRange(year, month, era);
  211. if ((month == MaxCalendarMonth) && (year == MaxCalendarYear))
  212. {
  213. return MaxCalendarDay;
  214. }
  215. int daysInMonth = s_daysToMonth[month] - s_daysToMonth[month - 1];
  216. if ((month == MonthsPerYear) && !IsLeapYear(year))
  217. {
  218. Debug.Assert(daysInMonth == 30);
  219. --daysInMonth;
  220. }
  221. return daysInMonth;
  222. }
  223. public override int GetDaysInYear(int year, int era)
  224. {
  225. CheckYearRange(year, era);
  226. if (year == MaxCalendarYear)
  227. {
  228. return s_daysToMonth[MaxCalendarMonth - 1] + MaxCalendarDay;
  229. }
  230. return IsLeapYear(year, CurrentEra) ? 366 : 365;
  231. }
  232. public override int GetEra(DateTime time)
  233. {
  234. CheckTicksRange(time.Ticks);
  235. return PersianEra;
  236. }
  237. public override int[] Eras => new int[] { PersianEra };
  238. public override int GetMonth(DateTime time)
  239. {
  240. return GetDatePart(time.Ticks, DatePartMonth);
  241. }
  242. public override int GetMonthsInYear(int year, int era)
  243. {
  244. CheckYearRange(year, era);
  245. if (year == MaxCalendarYear)
  246. {
  247. return MaxCalendarMonth;
  248. }
  249. return 12;
  250. }
  251. public override int GetYear(DateTime time)
  252. {
  253. return GetDatePart(time.Ticks, DatePartYear);
  254. }
  255. public override bool IsLeapDay(int year, int month, int day, int era)
  256. {
  257. // The year/month/era value checking is done in GetDaysInMonth().
  258. int daysInMonth = GetDaysInMonth(year, month, era);
  259. if (day < 1 || day > daysInMonth)
  260. {
  261. throw new ArgumentOutOfRangeException(
  262. nameof(day),
  263. day,
  264. SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
  265. }
  266. return IsLeapYear(year, era) && month == 12 && day == 30;
  267. }
  268. public override int GetLeapMonth(int year, int era)
  269. {
  270. CheckYearRange(year, era);
  271. return 0;
  272. }
  273. public override bool IsLeapMonth(int year, int month, int era)
  274. {
  275. CheckYearMonthRange(year, month, era);
  276. return false;
  277. }
  278. public override bool IsLeapYear(int year, int era)
  279. {
  280. CheckYearRange(year, era);
  281. if (year == MaxCalendarYear)
  282. {
  283. return false;
  284. }
  285. return (GetAbsoluteDatePersian(year + 1, 1, 1) - GetAbsoluteDatePersian(year, 1, 1)) == 366;
  286. }
  287. public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
  288. {
  289. // The year/month/era checking is done in GetDaysInMonth().
  290. int daysInMonth = GetDaysInMonth(year, month, era);
  291. if (day < 1 || day > daysInMonth)
  292. {
  293. throw new ArgumentOutOfRangeException(
  294. nameof(day),
  295. day,
  296. SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
  297. }
  298. long lDate = GetAbsoluteDatePersian(year, month, day);
  299. if (lDate < 0)
  300. {
  301. throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
  302. }
  303. return new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond));
  304. }
  305. private const int DefaultTwoDigitYearMax = 1410;
  306. public override int TwoDigitYearMax
  307. {
  308. get
  309. {
  310. if (_twoDigitYearMax == -1)
  311. {
  312. _twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DefaultTwoDigitYearMax);
  313. }
  314. return _twoDigitYearMax;
  315. }
  316. set
  317. {
  318. VerifyWritable();
  319. if (value < 99 || value > MaxCalendarYear)
  320. {
  321. throw new ArgumentOutOfRangeException(
  322. nameof(value),
  323. value,
  324. SR.Format(SR.ArgumentOutOfRange_Range, 99, MaxCalendarYear));
  325. }
  326. _twoDigitYearMax = value;
  327. }
  328. }
  329. public override int ToFourDigitYear(int year)
  330. {
  331. if (year < 0)
  332. {
  333. throw new ArgumentOutOfRangeException(nameof(year), year, SR.ArgumentOutOfRange_NeedNonNegNum);
  334. }
  335. if (year < 100)
  336. {
  337. return base.ToFourDigitYear(year);
  338. }
  339. if (year > MaxCalendarYear)
  340. {
  341. throw new ArgumentOutOfRangeException(
  342. nameof(year),
  343. year,
  344. SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarYear));
  345. }
  346. return year;
  347. }
  348. }
  349. }