893503413c9033fcbf2055a9027af827066c1657
[mono.git] / mcs / class / referencesource / mscorlib / system / globalization / eastasianlunisolarcalendar.cs
1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 //
5 // ==--==
6 namespace System.Globalization {
7     using System;
8     using System.Diagnostics.Contracts;
9
10     ////////////////////////////////////////////////////////////////////////////
11     //
12     //  Notes about EastAsianLunisolarCalendar
13     //
14     ////////////////////////////////////////////////////////////////////////////
15
16
17     [Serializable]
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;
24
25         // # of days so far in the solar year
26         internal static readonly int[] DaysToMonth365 =
27         {
28             0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
29         };
30
31         internal static readonly int[] DaysToMonth366 =
32         {
33             0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
34         };
35
36         internal const int DatePartYear = 0;
37         internal const int DatePartDayOfYear = 1;
38         internal const int DatePartMonth = 2;
39         internal const int DatePartDay = 3;
40
41         // Return the type of the East Asian Lunisolar calendars.
42         //
43
44         public override CalendarAlgorithmType AlgorithmType {
45             get {
46                 return CalendarAlgorithmType.LunisolarCalendar;
47             }
48         }
49
50         // Return the year number in the 60-year cycle.
51         //
52
53         public virtual int GetSexagenaryYear (DateTime time) {
54             CheckTicksRange(time.Ticks);
55
56             int year = 0, month = 0, day = 0;
57             TimeToLunar(time, ref year, ref month, ref day);
58
59             return ((year - 4) % 60) + 1;
60         }
61
62         // Return the celestial year from the 60-year cycle.
63         // The returned value is from 1 ~ 10.
64         //
65
66         public int GetCelestialStem(int sexagenaryYear) {
67             if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
68                 throw new ArgumentOutOfRangeException(
69                                 "sexagenaryYear",
70                                 Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
71             }
72             Contract.EndContractBlock();
73
74          return ((sexagenaryYear - 1) % 10) + 1;
75         }
76
77         // Return the Terrestial Branch from the the 60-year cycle.
78         // The returned value is from 1 ~ 12.
79         //
80
81         public int GetTerrestrialBranch(int sexagenaryYear) {
82             if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
83                 throw new ArgumentOutOfRangeException(
84                                 "sexagenaryYear",
85                                 Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
86             }
87             Contract.EndContractBlock();
88
89             return ((sexagenaryYear - 1) % 12) + 1;
90         }
91
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);
95
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;}
101
102         internal const int MaxCalendarMonth = 13;
103         internal const int MaxCalendarDay = 30;
104
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;
110             }
111
112             if (era == Calendar.CurrentEra) {
113                 era = CurrentEraValue;
114             }
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));
118             }
119
120             for (int i = 0; i < mEraInfo.Length; i++) {
121                 if (era == mEraInfo[i].era) {
122                     return (mEraInfo[i].minEraYear);
123                 }
124             }
125             throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
126         }
127
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;
133             }
134
135             if (era == Calendar.CurrentEra) {
136                 era = CurrentEraValue;
137             }
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));
141             }
142
143             for (int i = 0; i < mEraInfo.Length; i++) {
144                 if (era == mEraInfo[i].era) {
145                     return (mEraInfo[i].maxEraYear);
146                 }
147             }
148             throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
149         }
150
151         // Construct an instance of EastAsianLunisolar calendar.
152
153         internal EastAsianLunisolarCalendar() {
154         }
155
156         internal void CheckTicksRange(long ticks) {
157             if (ticks < MinSupportedDateTime.Ticks || ticks > MaxSupportedDateTime.Ticks) {
158                 throw new ArgumentOutOfRangeException(
159                                 "time",
160                                 String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
161                                 MinSupportedDateTime, MaxSupportedDateTime));
162             }
163             Contract.EndContractBlock();
164         }
165
166         internal void CheckEraRange (int era) {
167             if (era == Calendar.CurrentEra) {
168                 era = CurrentEraValue;
169             }
170
171             if ((era <GetEra(MinDate)) || (era > GetEra(MaxDate))) {
172                 throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
173             }
174         }
175
176         internal int CheckYearRange(int year, int era) {
177          CheckEraRange(era);
178          year = GetGregorianYear(year, era);
179
180             if ((year < MinCalendarYear) || (year > MaxCalendarYear)) {
181                 throw new ArgumentOutOfRangeException(
182                                 "year",
183                                 Environment.GetResourceString("ArgumentOutOfRange_Range", MinEraCalendarYear(era), MaxEraCalendarYear(era)));
184             }
185             return year;
186         }
187
188         internal int CheckYearMonthRange(int year, int month, int era) {
189             year = CheckYearRange(year, era);
190
191             if (month == 13)
192             {
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"));
196             }
197
198             if (month < 1 || month > 13) {
199                 throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
200             }
201             return year;
202         }
203
204         internal int InternalGetDaysInMonth(int year, int month) {
205             int nDays;
206             int mask;        // mask for extracting bits
207
208             mask = 0x8000;
209             // convert the lunar day into a lunar month/date
210             mask >>= (month-1);
211             if ((GetYearInfo(year, nDaysPerMonth) & mask)== 0)
212                 nDays = 29;
213             else
214                 nDays = 30;
215             return nDays;
216         }
217
218         // Returns the number of days in the month given by the year and
219         // month arguments.
220         //
221
222         public override int GetDaysInMonth(int year, int month, int era) {
223             year = CheckYearMonthRange(year, month, era);
224             return InternalGetDaysInMonth(year, month);
225         }
226
227         static int GregorianIsLeapYear(int y) {
228             return ((((y)%4)!=0)?0:((((y)%100)!=0)?1:((((y)%400)!=0)?0:1)));
229         }
230
231         // Returns the date and time converted to a DateTime value.  Throws an exception if the n-tuple is invalid.
232         //
233
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(
240                             "day",
241                             Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
242             }
243
244             int gy=0; int gm=0; int gd=0;
245
246             if (LunarToGregorian(year, month, day, ref gy, ref gm, ref gd)) {
247                 return new DateTime(gy, gm, gd, hour, minute, second, millisecond);
248             } else {
249                 throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
250             }
251         }
252
253
254         //
255         // GregorianToLunar calculates lunar calendar info for the given gregorian year, month, date.
256         // The input date should be validated before calling this method.
257         //
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;
267
268             // calc the solar day of year
269             fLeap = GregorianIsLeapYear(nSYear);
270             nSolarDay = (fLeap==1) ? DaysToMonth366[nSMonth-1]: DaysToMonth365[nSMonth-1] ;
271             nSolarDay += nSDate;
272
273             // init lunar year info
274             nLunarDay = nSolarDay;
275             nLYear = nSYear;
276             if (nLYear == (MaxCalendarYear + 1)) {
277                 nLYear--;
278                 nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
279                 nJan1Month = GetYearInfo(nLYear, Jan1Month);
280                 nJan1Date = GetYearInfo(nLYear,Jan1Date);
281             } else {
282
283                 nJan1Month = GetYearInfo(nLYear, Jan1Month);
284                 nJan1Date = GetYearInfo(nLYear,Jan1Date);
285
286                 // check if this solar date is actually part of the previous
287                 // lunar year
288                 if ((nSMonth < nJan1Month) ||
289                     (nSMonth == nJan1Month && nSDate < nJan1Date)) {
290                     // the corresponding lunar day is actually part of the previous
291                     // lunar year
292                     nLYear--;
293
294                     // add a solar year to the lunar day #
295                     nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
296
297                     // update the new start of year
298                     nJan1Month = GetYearInfo(nLYear, Jan1Month);
299                     nJan1Date = GetYearInfo(nLYear, Jan1Date);
300                 }
301             }
302
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
307             // and later).
308             nLunarDay -= DaysToMonth365[nJan1Month-1];
309             nLunarDay -= (nJan1Date - 1);
310
311             // convert the lunar day into a lunar month/date
312             mask = 0x8000;
313             LDpM = GetYearInfo(nLYear, nDaysPerMonth);
314             nDays = ((LDpM & mask) != 0) ? 30 : 29;
315             nLMonth = 1;
316             while (nLunarDay > nDays) {
317                 nLunarDay -= nDays;
318                 nLMonth++;
319                 mask >>= 1;
320                 nDays = ((LDpM & mask) != 0) ? 30 : 29;
321             }
322             nLDate = nLunarDay;
323         }
324
325         /*
326         //Convert from Lunar to Gregorian
327         //Highly inefficient, but it works based on the forward conversion
328         */
329         internal bool LunarToGregorian(int nLYear, int nLMonth, int nLDate, ref int nSolarYear, ref int nSolarMonth, ref int nSolarDay) {
330             int numLunarDays;
331
332             if (nLDate < 1 || nLDate > 30)
333                 return false;
334
335             numLunarDays = nLDate-1;
336
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);
340             }
341
342             //Get Gregorian First of year
343             int nJan1Month = GetYearInfo(nLYear, Jan1Month);
344             int nJan1Date = GetYearInfo(nLYear, Jan1Date);
345
346             // calc the solar day of year of 1 Lunar day
347             int fLeap = GregorianIsLeapYear(nLYear);
348             int[] days = (fLeap==1)? DaysToMonth366: DaysToMonth365;
349
350             nSolarDay  = nJan1Date;
351
352             if (nJan1Month > 1)
353                 nSolarDay += days [nJan1Month-1];
354
355             // Add the actual lunar day to get the solar day we want
356             nSolarDay = nSolarDay + numLunarDays;// - 1;
357
358             if ( nSolarDay > (fLeap + 365)) {
359                 nSolarYear = nLYear + 1;
360                 nSolarDay -= (fLeap + 365);
361             } else {
362                 nSolarYear = nLYear;
363             }
364
365             for (nSolarMonth = 1; nSolarMonth < 12; nSolarMonth++) {
366                 if (days[nSolarMonth] >= nSolarDay)
367                     break;
368             }
369
370             nSolarDay -= days[nSolarMonth-1];
371             return true;
372         }
373
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));
378         }
379
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;
382
383             Calendar Greg = GregorianCalendar.GetDefaultInstance();
384             gy = Greg.GetYear(time);
385             gm = Greg.GetMonth(time);
386             gd = Greg.GetDayOfMonth(time);
387
388             GregorianToLunar(gy, gm, gd, ref year, ref month, ref day);
389         }
390
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.
398         //
399
400         public override DateTime AddMonths(DateTime time, int months) {
401             if (months < -120000 || months > 120000) {
402                 throw new ArgumentOutOfRangeException(
403                             "months",
404                             Environment.GetResourceString("ArgumentOutOfRange_Range", -120000, 120000));
405             }
406             Contract.EndContractBlock();
407
408             CheckTicksRange(time.Ticks);
409
410             int y=0;  int m=0; int d=0;
411             TimeToLunar(time, ref y, ref m, ref d);
412
413             int i = m + months;
414             if (i > 0) {
415                 int monthsInYear = InternalIsLeapYear(y)?13:12;
416
417                 while (i-monthsInYear > 0) {
418                     i -= monthsInYear;
419                     y++;
420                     monthsInYear = InternalIsLeapYear(y)?13:12;
421                 }
422                 m = i;
423             } else {
424                 int monthsInYear;
425                 while (i <= 0) {
426                     monthsInYear = InternalIsLeapYear(y-1)?13:12;
427                     i += monthsInYear;
428                     y--;
429                 }
430                 m = i;
431             }
432
433             int days = InternalGetDaysInMonth(y, m);
434             if (d > days) {
435                 d = days;
436             }
437             DateTime dt = LunarToTime(time, y, m, d);
438
439             CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
440             return (dt);
441         }
442
443
444         public override DateTime AddYears(DateTime time, int years) {
445             CheckTicksRange(time.Ticks);
446
447             int y=0;  int m=0; int d=0;
448             TimeToLunar(time, ref y, ref m, ref d);
449
450             y += years;
451
452             if (m==13 && !InternalIsLeapYear(y)) {
453                 m = 12;
454                 d = InternalGetDaysInMonth(y, m);
455             }
456             int DaysInMonths = InternalGetDaysInMonth(y, m);
457             if (d > DaysInMonths) {
458                 d = DaysInMonths;
459             }
460
461             DateTime dt = LunarToTime(time, y, m, d);
462             CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
463             return (dt);
464         }
465
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].
468         //
469
470         public override int GetDayOfYear(DateTime time) {
471             CheckTicksRange(time.Ticks);
472
473             int y=0;  int m=0; int d=0;
474             TimeToLunar(time, ref y, ref m, ref d);
475
476             for (int i=1; i<m ;i++)
477             {
478                 d = d + InternalGetDaysInMonth(y, i);
479             }
480             return d;
481         }
482
483         // Returns the day-of-month part of the specified DateTime. The returned
484         // value is an integer between 1 and 29 or 30.
485         //
486
487         public override int GetDayOfMonth(DateTime time) {
488             CheckTicksRange(time.Ticks);
489
490             int y=0;  int m=0; int d=0;
491             TimeToLunar(time, ref y, ref m, ref d);
492
493             return d;
494         }
495
496         // Returns the number of days in the year given by the year argument for the current era.
497         //
498
499         public override int GetDaysInYear(int year, int era) {
500             year = CheckYearRange(year, era);
501
502             int Days = 0;
503             int monthsInYear = InternalIsLeapYear(year) ? 13 : 12;
504
505             while (monthsInYear != 0)
506                 Days += InternalGetDaysInMonth(year, monthsInYear--);
507
508             return Days;
509         }
510
511         // Returns the month part of the specified DateTime. The returned value is an
512         // integer between 1 and 13.
513         //
514
515         public override int GetMonth(DateTime time) {
516             CheckTicksRange(time.Ticks);
517
518             int y=0;  int m=0; int d=0;
519             TimeToLunar(time, ref y, ref m, ref d);
520
521             return m;
522         }
523
524         // Returns the year part of the specified DateTime. The returned value is an
525         // integer between 1 and MaxCalendarYear.
526         //
527
528         public override int GetYear(DateTime time) {
529             CheckTicksRange(time.Ticks);
530
531             int y=0; int m=0; int d=0;
532             TimeToLunar(time, ref y, ref m, ref d);
533
534             return GetYear(y, time);
535         }
536
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.
541         //
542
543         public override DayOfWeek GetDayOfWeek(DateTime time) {
544             CheckTicksRange(time.Ticks);
545             return ((DayOfWeek)((int)(time.Ticks / Calendar.TicksPerDay + 1) % 7));
546         }
547
548         // Returns the number of months in the specified year and era.
549
550         public override int GetMonthsInYear(int year, int era) {
551             year = CheckYearRange(year, era);
552             return (InternalIsLeapYear(year)?13:12);
553         }
554
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.
557         //
558
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);
562
563             if (day < 1 || day > daysInMonth) {
564                 throw new ArgumentOutOfRangeException(
565                             "day",
566                             Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
567             }
568             int m = GetYearInfo(year, LeapMonth);
569             return ((m!=0) && (month == (m+1)));
570         }
571
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.
574         //
575
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)));
580         }
581
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.
584         //
585
586         public override int  GetLeapMonth(int year, int era) {
587             year = CheckYearRange(year, era);
588             int month = GetYearInfo(year, LeapMonth);
589             if (month>0)
590             {
591                 return (month+1);
592             }
593             return 0;
594         }
595
596         internal bool InternalIsLeapYear(int year) {
597             return (GetYearInfo(year, LeapMonth)!=0);
598         }
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.
601         //
602
603         public override bool IsLeapYear(int year, int era) {
604             year = CheckYearRange(year, era);
605             return InternalIsLeapYear(year);
606         }
607
608         private const int DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX = 2029;
609
610
611         public override int TwoDigitYearMax {
612             get {
613                 if (twoDigitYearMax == -1) {
614                     twoDigitYearMax = GetSystemTwoDigitYearSetting(BaseCalendarID, GetYear(new DateTime(DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX, 1, 1)));
615                 }
616                 return (twoDigitYearMax);
617             }
618
619             set {
620                 VerifyWritable();
621                 if (value < 99 || value > MaxCalendarYear)
622                 {
623                     throw new ArgumentOutOfRangeException(
624                                 "value",
625                                 Environment.GetResourceString("ArgumentOutOfRange_Range", 99, MaxCalendarYear));
626                 }
627                 twoDigitYearMax = value;
628             }
629         }
630
631
632         public override int ToFourDigitYear(int year) {
633             if (year < 0) {
634                 throw new ArgumentOutOfRangeException("year",
635                     Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
636             }
637             Contract.EndContractBlock();
638
639             year = base.ToFourDigitYear(year);
640             CheckYearRange(year, CurrentEra);
641             return (year);
642         }
643
644     }
645 }