3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 namespace System.Globalization {
8 using System.Diagnostics.Contracts;
10 ////////////////////////////////////////////////////////////////////////////
12 // Notes about EastAsianLunisolarCalendar
14 ////////////////////////////////////////////////////////////////////////////
18 [System.Runtime.InteropServices.ComVisible(true)]
19 public abstract class EastAsianLunisolarCalendar : Calendar {
20 internal const int LeapMonth = 0;
21 internal const int Jan1Month = 1;
22 internal const int Jan1Date = 2;
23 internal const int nDaysPerMonth = 3;
25 // # of days so far in the solar year
26 internal static readonly int[] DaysToMonth365 =
28 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
31 internal static readonly int[] DaysToMonth366 =
33 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
36 internal const int DatePartYear = 0;
37 internal const int DatePartDayOfYear = 1;
38 internal const int DatePartMonth = 2;
39 internal const int DatePartDay = 3;
41 // Return the type of the East Asian Lunisolar calendars.
44 public override CalendarAlgorithmType AlgorithmType {
46 return CalendarAlgorithmType.LunisolarCalendar;
50 // Return the year number in the 60-year cycle.
53 public virtual int GetSexagenaryYear (DateTime time) {
54 CheckTicksRange(time.Ticks);
56 int year = 0, month = 0, day = 0;
57 TimeToLunar(time, ref year, ref month, ref day);
59 return ((year - 4) % 60) + 1;
62 // Return the celestial year from the 60-year cycle.
63 // The returned value is from 1 ~ 10.
66 public int GetCelestialStem(int sexagenaryYear) {
67 if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
68 throw new ArgumentOutOfRangeException(
70 Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
72 Contract.EndContractBlock();
74 return ((sexagenaryYear - 1) % 10) + 1;
77 // Return the Terrestial Branch from the the 60-year cycle.
78 // The returned value is from 1 ~ 12.
81 public int GetTerrestrialBranch(int sexagenaryYear) {
82 if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
83 throw new ArgumentOutOfRangeException(
85 Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
87 Contract.EndContractBlock();
89 return ((sexagenaryYear - 1) % 12) + 1;
92 internal abstract int GetYearInfo(int LunarYear, int Index);
93 internal abstract int GetYear(int year, DateTime time);
94 internal abstract int GetGregorianYear(int year, int era);
96 internal abstract int MinCalendarYear {get;}
97 internal abstract int MaxCalendarYear {get;}
98 internal abstract EraInfo[] CalEraInfo{get;}
99 internal abstract DateTime MinDate {get;}
100 internal abstract DateTime MaxDate {get;}
102 internal const int MaxCalendarMonth = 13;
103 internal const int MaxCalendarDay = 30;
105 internal int MinEraCalendarYear (int era) {
106 EraInfo[] mEraInfo = CalEraInfo;
107 //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
108 if (mEraInfo == null) {
109 return MinCalendarYear;
112 if (era == Calendar.CurrentEra) {
113 era = CurrentEraValue;
115 //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
116 if (era == GetEra(MinDate)) {
117 return (GetYear(MinCalendarYear, MinDate));
120 for (int i = 0; i < mEraInfo.Length; i++) {
121 if (era == mEraInfo[i].era) {
122 return (mEraInfo[i].minEraYear);
125 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
128 internal int MaxEraCalendarYear (int era) {
129 EraInfo[] mEraInfo = CalEraInfo;
130 //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
131 if (mEraInfo == null) {
132 return MaxCalendarYear;
135 if (era == Calendar.CurrentEra) {
136 era = CurrentEraValue;
138 //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
139 if (era == GetEra(MaxDate)) {
140 return (GetYear(MaxCalendarYear, MaxDate));
143 for (int i = 0; i < mEraInfo.Length; i++) {
144 if (era == mEraInfo[i].era) {
145 return (mEraInfo[i].maxEraYear);
148 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
151 // Construct an instance of EastAsianLunisolar calendar.
153 internal EastAsianLunisolarCalendar() {
156 internal void CheckTicksRange(long ticks) {
157 if (ticks < MinSupportedDateTime.Ticks || ticks > MaxSupportedDateTime.Ticks) {
158 throw new ArgumentOutOfRangeException(
160 String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
161 MinSupportedDateTime, MaxSupportedDateTime));
163 Contract.EndContractBlock();
166 internal void CheckEraRange (int era) {
167 if (era == Calendar.CurrentEra) {
168 era = CurrentEraValue;
171 if ((era <GetEra(MinDate)) || (era > GetEra(MaxDate))) {
172 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
176 internal int CheckYearRange(int year, int era) {
178 year = GetGregorianYear(year, era);
180 if ((year < MinCalendarYear) || (year > MaxCalendarYear)) {
181 throw new ArgumentOutOfRangeException(
183 Environment.GetResourceString("ArgumentOutOfRange_Range", MinEraCalendarYear(era), MaxEraCalendarYear(era)));
188 internal int CheckYearMonthRange(int year, int month, int era) {
189 year = CheckYearRange(year, era);
193 //Reject if there is no leap month this year
194 if (GetYearInfo(year , LeapMonth) == 0)
195 throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
198 if (month < 1 || month > 13) {
199 throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
204 internal int InternalGetDaysInMonth(int year, int month) {
206 int mask; // mask for extracting bits
209 // convert the lunar day into a lunar month/date
211 if ((GetYearInfo(year, nDaysPerMonth) & mask)== 0)
218 // Returns the number of days in the month given by the year and
222 public override int GetDaysInMonth(int year, int month, int era) {
223 year = CheckYearMonthRange(year, month, era);
224 return InternalGetDaysInMonth(year, month);
227 static int GregorianIsLeapYear(int y) {
228 return ((((y)%4)!=0)?0:((((y)%100)!=0)?1:((((y)%400)!=0)?0:1)));
231 // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
234 public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
235 year = CheckYearMonthRange(year, month, era);
236 int daysInMonth = InternalGetDaysInMonth(year, month);
237 if (day < 1 || day > daysInMonth) {
238 BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day);
239 throw new ArgumentOutOfRangeException(
241 Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
244 int gy=0; int gm=0; int gd=0;
246 if (LunarToGregorian(year, month, day, ref gy, ref gm, ref gd)) {
247 return new DateTime(gy, gm, gd, hour, minute, second, millisecond);
249 throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
255 // GregorianToLunar calculates lunar calendar info for the given gregorian year, month, date.
256 // The input date should be validated before calling this method.
258 internal void GregorianToLunar(int nSYear, int nSMonth, int nSDate, ref int nLYear, ref int nLMonth, ref int nLDate) {
259 // unsigned int nLYear, nLMonth, nLDate; // lunar ymd
260 int nSolarDay; // day # in solar year
261 int nLunarDay; // day # in lunar year
262 int fLeap; // is it a solar leap year?
263 int LDpM; // lunar days/month bitfield
264 int mask; // mask for extracting bits
265 int nDays; // # days this lunar month
266 int nJan1Month, nJan1Date;
268 // calc the solar day of year
269 fLeap = GregorianIsLeapYear(nSYear);
270 nSolarDay = (fLeap==1) ? DaysToMonth366[nSMonth-1]: DaysToMonth365[nSMonth-1] ;
273 // init lunar year info
274 nLunarDay = nSolarDay;
276 if (nLYear == (MaxCalendarYear + 1)) {
278 nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
279 nJan1Month = GetYearInfo(nLYear, Jan1Month);
280 nJan1Date = GetYearInfo(nLYear,Jan1Date);
283 nJan1Month = GetYearInfo(nLYear, Jan1Month);
284 nJan1Date = GetYearInfo(nLYear,Jan1Date);
286 // check if this solar date is actually part of the previous
288 if ((nSMonth < nJan1Month) ||
289 (nSMonth == nJan1Month && nSDate < nJan1Date)) {
290 // the corresponding lunar day is actually part of the previous
294 // add a solar year to the lunar day #
295 nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
297 // update the new start of year
298 nJan1Month = GetYearInfo(nLYear, Jan1Month);
299 nJan1Date = GetYearInfo(nLYear, Jan1Date);
303 // convert solar day into lunar day.
304 // subtract off the beginning part of the solar year which is not
305 // part of the lunar year. since this part is always in Jan or Feb,
306 // we don't need to handle Leap Year (LY only affects Microsoft
308 nLunarDay -= DaysToMonth365[nJan1Month-1];
309 nLunarDay -= (nJan1Date - 1);
311 // convert the lunar day into a lunar month/date
313 LDpM = GetYearInfo(nLYear, nDaysPerMonth);
314 nDays = ((LDpM & mask) != 0) ? 30 : 29;
316 while (nLunarDay > nDays) {
320 nDays = ((LDpM & mask) != 0) ? 30 : 29;
326 //Convert from Lunar to Gregorian
327 //Highly inefficient, but it works based on the forward conversion
329 internal bool LunarToGregorian(int nLYear, int nLMonth, int nLDate, ref int nSolarYear, ref int nSolarMonth, ref int nSolarDay) {
332 if (nLDate < 1 || nLDate > 30)
335 numLunarDays = nLDate-1;
337 //Add previous months days to form the total num of days from the first of the month.
338 for (int i = 1; i < nLMonth; i++) {
339 numLunarDays += InternalGetDaysInMonth(nLYear, i);
342 //Get Gregorian First of year
343 int nJan1Month = GetYearInfo(nLYear, Jan1Month);
344 int nJan1Date = GetYearInfo(nLYear, Jan1Date);
346 // calc the solar day of year of 1 Lunar day
347 int fLeap = GregorianIsLeapYear(nLYear);
348 int[] days = (fLeap==1)? DaysToMonth366: DaysToMonth365;
350 nSolarDay = nJan1Date;
353 nSolarDay += days [nJan1Month-1];
355 // Add the actual lunar day to get the solar day we want
356 nSolarDay = nSolarDay + numLunarDays;// - 1;
358 if ( nSolarDay > (fLeap + 365)) {
359 nSolarYear = nLYear + 1;
360 nSolarDay -= (fLeap + 365);
365 for (nSolarMonth = 1; nSolarMonth < 12; nSolarMonth++) {
366 if (days[nSolarMonth] >= nSolarDay)
370 nSolarDay -= days[nSolarMonth-1];
374 internal DateTime LunarToTime(DateTime time, int year, int month, int day) {
375 int gy=0; int gm=0; int gd=0;
376 LunarToGregorian(year, month, day, ref gy, ref gm, ref gd);
377 return (GregorianCalendar.GetDefaultInstance().ToDateTime(gy,gm,gd,time.Hour,time.Minute,time.Second,time.Millisecond));
380 internal void TimeToLunar(DateTime time, ref int year, ref int month, ref int day) {
381 int gy=0; int gm=0; int gd=0;
383 Calendar Greg = GregorianCalendar.GetDefaultInstance();
384 gy = Greg.GetYear(time);
385 gm = Greg.GetMonth(time);
386 gd = Greg.GetDayOfMonth(time);
388 GregorianToLunar(gy, gm, gd, ref year, ref month, ref day);
391 // Returns the DateTime resulting from adding the given number of
392 // months to the specified DateTime. The result is computed by incrementing
393 // (or decrementing) the year and month parts of the specified DateTime by
394 // value months, and, if required, adjusting the day part of the
395 // resulting date downwards to the last day of the resulting month in the
396 // resulting year. The time-of-day part of the result is the same as the
397 // time-of-day part of the specified DateTime.
400 public override DateTime AddMonths(DateTime time, int months) {
401 if (months < -120000 || months > 120000) {
402 throw new ArgumentOutOfRangeException(
404 Environment.GetResourceString("ArgumentOutOfRange_Range", -120000, 120000));
406 Contract.EndContractBlock();
408 CheckTicksRange(time.Ticks);
410 int y=0; int m=0; int d=0;
411 TimeToLunar(time, ref y, ref m, ref d);
415 int monthsInYear = InternalIsLeapYear(y)?13:12;
417 while (i-monthsInYear > 0) {
420 monthsInYear = InternalIsLeapYear(y)?13:12;
426 monthsInYear = InternalIsLeapYear(y-1)?13:12;
433 int days = InternalGetDaysInMonth(y, m);
437 DateTime dt = LunarToTime(time, y, m, d);
439 CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
444 public override DateTime AddYears(DateTime time, int years) {
445 CheckTicksRange(time.Ticks);
447 int y=0; int m=0; int d=0;
448 TimeToLunar(time, ref y, ref m, ref d);
452 if (m==13 && !InternalIsLeapYear(y)) {
454 d = InternalGetDaysInMonth(y, m);
456 int DaysInMonths = InternalGetDaysInMonth(y, m);
457 if (d > DaysInMonths) {
461 DateTime dt = LunarToTime(time, y, m, d);
462 CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
466 // Returns the day-of-year part of the specified DateTime. The returned value
467 // is an integer between 1 and [354|355 |383|384].
470 public override int GetDayOfYear(DateTime time) {
471 CheckTicksRange(time.Ticks);
473 int y=0; int m=0; int d=0;
474 TimeToLunar(time, ref y, ref m, ref d);
476 for (int i=1; i<m ;i++)
478 d = d + InternalGetDaysInMonth(y, i);
483 // Returns the day-of-month part of the specified DateTime. The returned
484 // value is an integer between 1 and 29 or 30.
487 public override int GetDayOfMonth(DateTime time) {
488 CheckTicksRange(time.Ticks);
490 int y=0; int m=0; int d=0;
491 TimeToLunar(time, ref y, ref m, ref d);
496 // Returns the number of days in the year given by the year argument for the current era.
499 public override int GetDaysInYear(int year, int era) {
500 year = CheckYearRange(year, era);
503 int monthsInYear = InternalIsLeapYear(year) ? 13 : 12;
505 while (monthsInYear != 0)
506 Days += InternalGetDaysInMonth(year, monthsInYear--);
511 // Returns the month part of the specified DateTime. The returned value is an
512 // integer between 1 and 13.
515 public override int GetMonth(DateTime time) {
516 CheckTicksRange(time.Ticks);
518 int y=0; int m=0; int d=0;
519 TimeToLunar(time, ref y, ref m, ref d);
524 // Returns the year part of the specified DateTime. The returned value is an
525 // integer between 1 and MaxCalendarYear.
528 public override int GetYear(DateTime time) {
529 CheckTicksRange(time.Ticks);
531 int y=0; int m=0; int d=0;
532 TimeToLunar(time, ref y, ref m, ref d);
534 return GetYear(y, time);
537 // Returns the day-of-week part of the specified DateTime. The returned value
538 // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
539 // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
540 // Thursday, 5 indicates Friday, and 6 indicates Saturday.
543 public override DayOfWeek GetDayOfWeek(DateTime time) {
544 CheckTicksRange(time.Ticks);
545 return ((DayOfWeek)((int)(time.Ticks / Calendar.TicksPerDay + 1) % 7));
548 // Returns the number of months in the specified year and era.
550 public override int GetMonthsInYear(int year, int era) {
551 year = CheckYearRange(year, era);
552 return (InternalIsLeapYear(year)?13:12);
555 // Checks whether a given day in the specified era is a leap day. This method returns true if
556 // the date is a leap day, or false if not.
559 public override bool IsLeapDay(int year, int month, int day, int era) {
560 year = CheckYearMonthRange(year, month, era);
561 int daysInMonth = InternalGetDaysInMonth(year, month);
563 if (day < 1 || day > daysInMonth) {
564 throw new ArgumentOutOfRangeException(
566 Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
568 int m = GetYearInfo(year, LeapMonth);
569 return ((m!=0) && (month == (m+1)));
572 // Checks whether a given month in the specified era is a leap month. This method returns true if
573 // month is a leap month, or false if not.
576 public override bool IsLeapMonth(int year, int month, int era) {
577 year = CheckYearMonthRange(year, month, era);
578 int m = GetYearInfo(year, LeapMonth);
579 return ((m!=0) && (month == (m+1)));
582 // Returns the leap month in a calendar year of the specified era. This method returns 0
583 // if this this year is not a leap year.
586 public override int GetLeapMonth(int year, int era) {
587 year = CheckYearRange(year, era);
588 int month = GetYearInfo(year, LeapMonth);
596 internal bool InternalIsLeapYear(int year) {
597 return (GetYearInfo(year, LeapMonth)!=0);
599 // Checks whether a given year in the specified era is a leap year. This method returns true if
600 // year is a leap year, or false if not.
603 public override bool IsLeapYear(int year, int era) {
604 year = CheckYearRange(year, era);
605 return InternalIsLeapYear(year);
608 private const int DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX = 2029;
611 public override int TwoDigitYearMax {
613 if (twoDigitYearMax == -1) {
614 twoDigitYearMax = GetSystemTwoDigitYearSetting(BaseCalendarID, GetYear(new DateTime(DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX, 1, 1)));
616 return (twoDigitYearMax);
621 if (value < 99 || value > MaxCalendarYear)
623 throw new ArgumentOutOfRangeException(
625 Environment.GetResourceString("ArgumentOutOfRange_Range", 99, MaxCalendarYear));
627 twoDigitYearMax = value;
632 public override int ToFourDigitYear(int year) {
634 throw new ArgumentOutOfRangeException("year",
635 Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
637 Contract.EndContractBlock();
639 year = base.ToFourDigitYear(year);
640 CheckYearRange(year, CurrentEra);