1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2005 John Chapman. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Mid 2005: Initial release
8                         Apr 2007: reshaped                        
9 
10         author:         John Chapman, Kris, schveiguy
11 
12 ******************************************************************************/
13 
14 module tango.time.chrono.Gregorian;
15 
16 private import tango.time.chrono.Calendar;
17 
18 private import tango.core.Exception;
19 
20 /**
21  * $(ANCHOR _Gregorian)
22  * Represents the Gregorian calendar.
23  *
24  * Note that this is the Proleptic Gregorian calendar.  Most calendars assume
25  * that dates before 9/14/1752 were Julian Dates.  Julian differs from
26  * Gregorian in that leap years occur every 4 years, even on 100 year
27  * increments.  The Proleptic Gregorian calendar applies the Gregorian leap
28  * year rules to dates before 9/14/1752, making the calculation of dates much
29  * easier.
30  */
31 class Gregorian : Calendar 
32 {
33         // import baseclass toTime()
34         alias Calendar.toTime toTime;
35 
36         /// static shared instance
37         public static __gshared Gregorian generic;
38 
39         enum Type 
40         {
41                 Localized = 1,               /// Refers to the localized version of the Gregorian calendar.
42                 USEnglish = 2,               /// Refers to the US English version of the Gregorian calendar.
43                 MiddleEastFrench = 9,        /// Refers to the Middle East French version of the Gregorian calendar.
44                 Arabic = 10,                 /// Refers to the _Arabic version of the Gregorian calendar.
45                 TransliteratedEnglish = 11,  /// Refers to the transliterated English version of the Gregorian calendar.
46                 TransliteratedFrench = 12    /// Refers to the transliterated French version of the Gregorian calendar.
47         }
48 
49         private Type type_;                 
50 
51         /**
52         * Represents the current era.
53         */
54         enum {AD_ERA = 1, BC_ERA = 2, MAX_YEAR = 9999};
55 
56         private __gshared immutable uint[] DaysToMonthCommon = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
57 
58         private __gshared immutable uint[] DaysToMonthLeap   = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
59 
60         /**
61         * create a generic instance of this calendar
62         */
63         shared static this()
64         {       
65                 generic = new Gregorian;
66         }
67 
68         /**
69         * Initializes an instance of the Gregorian class using the specified GregorianTypes value. If no value is 
70         * specified, the default is Gregorian.Types.Localized.
71         */
72         this (Type type = Type.Localized) 
73         {
74                 type_ = type;
75         }
76 
77         /**
78         * Overridden. Returns a Time value set to the specified date and time in the specified _era.
79         * Params:
80         *   year = An integer representing the _year.
81         *   month = An integer representing the _month.
82         *   day = An integer representing the _day.
83         *   hour = An integer representing the _hour.
84         *   minute = An integer representing the _minute.
85         *   second = An integer representing the _second.
86         *   millisecond = An integer representing the _millisecond.
87         *   era = An integer representing the _era.
88         * Returns: A Time set to the specified date and time.
89         */
90         override const Time toTime (uint year, uint month, uint day, uint hour, uint minute, uint second, uint millisecond, uint era)
91         {
92                 return Time (getDateTicks(year, month, day, era) + getTimeTicks(hour, minute, second)) + TimeSpan.fromMillis(millisecond);
93         }
94 
95         /**
96         * Overridden. Returns the day of the week in the specified Time.
97         * Params: time = A Time value.
98         * Returns: A DayOfWeek value representing the day of the week of time.
99         */
100         override const DayOfWeek getDayOfWeek(const(Time) time) 
101         {
102                 auto ticks = time.ticks;
103                 int offset = 1;
104                 if (ticks < 0)
105                 {
106                     ++ticks;
107                     offset = 0;
108                 }
109        
110                 auto dow = cast(int) ((ticks / TimeSpan.TicksPerDay + offset) % 7);
111                 if (dow < 0)
112                     dow += 7;
113                 return cast(DayOfWeek) dow;
114         }
115 
116         /**
117         * Overridden. Returns the day of the month in the specified Time.
118         * Params: time = A Time value.
119         * Returns: An integer representing the day of the month of time.
120         */
121         override const uint getDayOfMonth(const(Time) time) 
122         {
123                 return extractPart(time.ticks, DatePart.Day);
124         }
125 
126         /**
127         * Overridden. Returns the day of the year in the specified Time.
128         * Params: time = A Time value.
129         * Returns: An integer representing the day of the year of time.
130         */
131         override const uint getDayOfYear(const(Time) time) 
132         {
133                 return extractPart(time.ticks, DatePart.DayOfYear);
134         }
135 
136         /**
137         * Overridden. Returns the month in the specified Time.
138         * Params: time = A Time value.
139         * Returns: An integer representing the month in time.
140         */
141         override const uint getMonth(const(Time) time) 
142         {
143                 return extractPart(time.ticks, DatePart.Month);
144         }
145 
146         /**
147         * Overridden. Returns the year in the specified Time.
148         * Params: time = A Time value.
149         * Returns: An integer representing the year in time.
150         */
151         override const uint getYear(const(Time) time) 
152         {
153                 return extractPart(time.ticks, DatePart.Year);
154         }
155 
156         /**
157         * Overridden. Returns the era in the specified Time.
158         * Params: time = A Time value.
159         * Returns: An integer representing the era in time.
160         */
161         override const uint getEra(const(Time) time) 
162         {
163                 if(time < time.epoch)
164                         return BC_ERA;
165                 else
166                         return AD_ERA;
167         }
168 
169         /**
170         * Overridden. Returns the number of days in the specified _year and _month of the specified _era.
171         * Params:
172         *   year = An integer representing the _year.
173         *   month = An integer representing the _month.
174         *   era = An integer representing the _era.
175         * Returns: The number of days in the specified _year and _month of the specified _era.
176         */
177         override const uint getDaysInMonth(uint year, uint month, uint era) 
178         {
179                 //
180                 // verify args.  isLeapYear verifies the year is valid.
181                 //
182                 if(month < 1 || month > 12)
183                         argumentError("months out of range");
184                 auto monthDays = isLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
185                 return monthDays[month] - monthDays[month - 1];
186         }
187 
188         /**
189         * Overridden. Returns the number of days in the specified _year of the specified _era.
190         * Params:
191         *   year = An integer representing the _year.
192         *   era = An integer representing the _era.
193         * Returns: The number of days in the specified _year in the specified _era.
194         */
195         override const uint getDaysInYear(uint year, uint era) 
196         {
197                 return isLeapYear(year, era) ? 366 : 365;
198         }
199 
200         /**
201         * Overridden. Returns the number of months in the specified _year of the specified _era.
202         * Params:
203         *   year = An integer representing the _year.
204         *   era = An integer representing the _era.
205         * Returns: The number of months in the specified _year in the specified _era.
206         */
207         override const uint getMonthsInYear(uint year, uint era) 
208         {
209                 return 12;
210         }
211 
212         /**
213         * Overridden. Indicates whether the specified _year in the specified _era is a leap _year.
214         * Params: year = An integer representing the _year.
215         * Params: era = An integer representing the _era.
216         * Returns: true is the specified _year is a leap _year; otherwise, false.
217         */
218         override const bool isLeapYear(uint year, uint era) 
219         {
220                 return staticIsLeapYear(year, era);
221         }
222 
223         /**
224         * $(I Property.) Retrieves the GregorianTypes value indicating the language version of the Gregorian.
225         * Returns: The Gregorian.Type value indicating the language version of the Gregorian.
226         */
227         @property const Type calendarType() 
228         {
229                 return type_;
230         }
231 
232         /**
233         * $(I Property.) Overridden. Retrieves the list of eras in the current calendar.
234         * Returns: An integer array representing the eras in the current calendar.
235         */
236         @property override const uint[] eras() 
237         {       
238                 uint[] tmp = [AD_ERA, BC_ERA];
239                 return tmp.dup;
240         }
241 
242         /**
243         * $(I Property.) Overridden. Retrieves the identifier associated with the current calendar.
244         * Returns: An integer representing the identifier of the current calendar.
245         */
246         @property override const uint id() 
247         {
248                 return cast(int) type_;
249         }
250 
251         /**
252          * Overridden.  Get the components of a Time structure using the rules
253          * of the calendar.  This is useful if you want more than one of the
254          * given components.  Note that this doesn't handle the time of day,
255          * as that is calculated directly from the Time struct.
256          */
257         override const void split(const(Time) time, ref uint year, ref uint month, ref uint day, ref uint doy, ref uint dow, ref uint era)
258         {
259             splitDate(time.ticks, year, month, day, doy, era);
260             dow = getDayOfWeek(time);
261         }
262 
263         /**
264          * Overridden. Returns a new Time with the specified number of months
265          * added.  If the months are negative, the months are subtracted.
266          *
267          * If the target month does not support the day component of the input
268          * time, then an error will be thrown, unless truncateDay is set to
269          * true.  If truncateDay is set to true, then the day is reduced to
270          * the maximum day of that month.
271          *
272          * For example, adding one month to 1/31/2000 with truncateDay set to
273          * true results in 2/28/2000.
274          *
275          * Params: t = A time to add the months to
276          * Params: nMonths = The number of months to add.  This can be
277          * negative.
278          * Params: truncateDay = Round the day down to the maximum day of the
279          * target month if necessary.
280          *
281          * Returns: A Time that represents the provided time with the number
282          * of months added.
283          */
284         override const Time addMonths(const(Time) t, int nMonths, bool truncateDay=false)
285         {
286                 //
287                 // We know all years are 12 months, so use the to/from date
288                 // methods to make the calculation an O(1) operation
289                 //
290                 auto date = toDate(t);
291                 nMonths += date.month - 1;
292                 int nYears = nMonths / 12;
293                 nMonths %= 12;
294                 if(nMonths < 0)
295                 {
296                         nYears--;
297                         nMonths += 12;
298                 }
299                 int realYear = date.year;
300                 if(date.era == BC_ERA)
301                         realYear = -realYear + 1;
302                 realYear += nYears;
303                 if(realYear < 1)
304                 {
305                         date.year = -realYear + 1;
306                         date.era = BC_ERA;
307                 }
308                 else
309                 {
310                         date.year = realYear;
311                         date.era = AD_ERA;
312                 }
313                 date.month = nMonths + 1;
314                 //
315                 // truncate the day if necessary
316                 //
317                 if(truncateDay)
318                 {
319                     uint maxday = getDaysInMonth(date.year, date.month, date.era);
320                     if(date.day > maxday)
321                         date.day = maxday;
322                 }
323                 auto tod = t.ticks % TimeSpan.TicksPerDay;
324                 if(tod < 0)
325                         tod += TimeSpan.TicksPerDay;
326                 return toTime(date) + TimeSpan(tod);
327         }
328 
329         /**
330          * Overridden.  Add the specified number of years to the given Time.
331          *
332          * Note that the Gregorian calendar takes into account that BC time
333          * is negative, and supports crossing from BC to AD.
334          *
335          * Params: t = A time to add the years to
336          * Params: nYears = The number of years to add.  This can be negative.
337          *
338          * Returns: A Time that represents the provided time with the number
339          * of years added.
340          */
341         override const Time addYears(const(Time) t, int nYears)
342         {
343                 return addMonths(t, nYears * 12);
344         }
345 
346         package static void splitDate (long ticks, ref uint year, ref uint month, ref uint day, ref uint dayOfYear, ref uint era) 
347         {
348                 int numDays;
349 
350                 void calculateYear()
351                 {
352                         auto whole400Years = numDays / cast(int) TimeSpan.DaysPer400Years;
353                         numDays -= whole400Years * cast(int) TimeSpan.DaysPer400Years;
354                         auto whole100Years = numDays / cast(int) TimeSpan.DaysPer100Years;
355                         if (whole100Years == 4)
356                                 whole100Years = 3;
357 
358                         numDays -= whole100Years * cast(int) TimeSpan.DaysPer100Years;
359                         auto whole4Years = numDays / cast(int) TimeSpan.DaysPer4Years;
360                         numDays -= whole4Years * cast(int) TimeSpan.DaysPer4Years;
361                         auto wholeYears = numDays / cast(int) TimeSpan.DaysPerYear;
362                         if (wholeYears == 4)
363                                 wholeYears = 3;
364 
365                         year = whole400Years * 400 + whole100Years * 100 + whole4Years * 4 + wholeYears + era;
366                         numDays -= wholeYears * TimeSpan.DaysPerYear;
367                 }
368 
369                 if(ticks < 0)
370                 {
371                         // in the BC era
372                         era = BC_ERA;
373                         //
374                         // set up numDays to be like AD.  AD days start at
375                         // year 1.  However, in BC, year 1 is like AD year 0,
376                         // so we must subtract one year.
377                         //
378                         numDays = cast(int)((-ticks - 1) / TimeSpan.TicksPerDay);
379                         if(numDays < 366)
380                         {
381                                 // in the year 1 B.C.  This is a special case
382                                 // leap year
383                                 year = 1;
384                         }
385                         else
386                         {
387                                 numDays -= 366;
388                                 calculateYear();
389                         }
390                         //
391                         // numDays is the number of days back from the end of
392                         // the year, because the original ticks were negative
393                         //
394                         numDays = (staticIsLeapYear(year, era) ? 366 : 365) - numDays - 1;
395                 }
396                 else
397                 {
398                         era = AD_ERA;
399                         numDays = cast(int)(ticks / TimeSpan.TicksPerDay);
400                         calculateYear();
401                 }
402                 dayOfYear = numDays + 1;
403 
404                 auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
405                 month = numDays >> 5 + 1;
406                 while (numDays >= monthDays[month])
407                        month++;
408 
409                 day = numDays - monthDays[month - 1] + 1;
410         }
411 
412         package static uint extractPart (long ticks, DatePart part) 
413         {
414                 uint year, month, day, dayOfYear, era;
415 
416                 splitDate(ticks, year, month, day, dayOfYear, era);
417 
418                 if (part is DatePart.Year)
419                     return year;
420 
421                 if (part is DatePart.Month)
422                     return month;
423 
424                 if (part is DatePart.DayOfYear)
425                     return dayOfYear;
426 
427                 return day;
428         }
429 
430         package static long getDateTicks (uint year, uint month, uint day, uint era) 
431         {
432                 //
433                 // verify arguments, getDaysInMonth verifies the year and
434                 // month is valid.
435                 //
436                 if(day < 1 || day > generic.getDaysInMonth(year, month, era))
437                         argumentError("days out of range");
438 
439                 auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
440                 if(era == BC_ERA)
441                 {
442                         year += 2;
443                         return -cast(long)( (year - 3) * 365 + year / 4 - year / 100 + year / 400 + monthDays[12] - (monthDays[month - 1] + day - 1)) * TimeSpan.TicksPerDay;
444                 }
445                 else
446                 {
447                         year--;
448                         return (year * 365 + year / 4 - year / 100 + year / 400 + monthDays[month - 1] + day - 1) * TimeSpan.TicksPerDay;
449                 }
450         }
451 
452         package static bool staticIsLeapYear(uint year, uint era)
453         {
454                 if(year < 1)
455                         argumentError("year cannot be 0");
456                 if(era == BC_ERA)
457                 {
458                         if(year == 1)
459                                 return true;
460                         return staticIsLeapYear(year - 1, AD_ERA);
461                 }
462                 if(era == AD_ERA || era == CURRENT_ERA)
463                         return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
464                 return false;
465         }
466 
467         package static void argumentError(const(char[]) str)
468         {
469                 throw new IllegalArgumentException(str.idup);
470         }
471 }
472 
473 debug(Gregorian)
474 {
475         import tango.io.Stdout;
476 
477         void output(Time t)
478         {
479                 Date d = Gregorian.generic.toDate(t);
480                 TimeOfDay tod = t.time;
481                 Stdout.format("{}/{}/{:d4} {} {}:{:d2}:{:d2}.{:d3} dow:{}",
482                                 d.month, d.day, d.year, d.era == Gregorian.AD_ERA ? "AD" : "BC",
483                                 tod.hours, tod.minutes, tod.seconds, tod.millis, d.dow).newline;
484         }
485 
486         void main()
487         {
488                 Time t = Time(365 * TimeSpan.TicksPerDay);
489                 output(t);
490                 for(int i = 0; i < 366 + 365; i++)
491                 {
492                         t -= TimeSpan.fromDays(1);
493                         output(t);
494                 }
495         }
496 }
497 
498 debug(UnitTest)
499 {
500         unittest
501         {
502                 //
503                 // check Gregorian date handles positive time.
504                 //
505                 Time t = Time.epoch + TimeSpan.fromDays(365);
506                 Date d = Gregorian.generic.toDate(t);
507                 assert(d.year == 2);
508                 assert(d.month == 1);
509                 assert(d.day == 1);
510                 assert(d.era == Gregorian.AD_ERA);
511                 assert(d.doy == 1);
512                 //
513                 // note that this is in disagreement with the Julian Calendar
514                 //
515                 assert(d.dow == Gregorian.DayOfWeek.Tuesday);
516 
517                 //
518                 // check that it handles negative time
519                 //
520                 t = Time.epoch - TimeSpan.fromDays(366);
521                 d = Gregorian.generic.toDate(t);
522                 assert(d.year == 1);
523                 assert(d.month == 1);
524                 assert(d.day == 1);
525                 assert(d.era == Gregorian.BC_ERA);
526                 assert(d.doy == 1);
527                 assert(d.dow == Gregorian.DayOfWeek.Saturday);
528 
529                 //
530                 // check that addMonths works properly, add 15 months to
531                 // 2/3/2004, 04:05:06.007008, then subtract 15 months again.
532                 //
533                 t = Gregorian.generic.toTime(2004, 2, 3, 4, 5, 6, 7) + TimeSpan.fromMicros(8);
534                 d = Gregorian.generic.toDate(t);
535                 assert(d.year == 2004);
536                 assert(d.month == 2);
537                 assert(d.day == 3);
538                 assert(d.era == Gregorian.AD_ERA);
539                 assert(d.doy == 34);
540                 assert(d.dow == Gregorian.DayOfWeek.Tuesday);
541 
542                 auto t2 = Gregorian.generic.addMonths(t, 15);
543                 d = Gregorian.generic.toDate(t2);
544                 assert(d.year == 2005);
545                 assert(d.month == 5);
546                 assert(d.day == 3);
547                 assert(d.era == Gregorian.AD_ERA);
548                 assert(d.doy == 123);
549                 assert(d.dow == Gregorian.DayOfWeek.Tuesday);
550 
551                 t2 = Gregorian.generic.addMonths(t2, -15);
552                 d = Gregorian.generic.toDate(t2);
553                 assert(d.year == 2004);
554                 assert(d.month == 2);
555                 assert(d.day == 3);
556                 assert(d.era == Gregorian.AD_ERA);
557                 assert(d.doy == 34);
558                 assert(d.dow == Gregorian.DayOfWeek.Tuesday);
559 
560                 assert(t == t2);
561 
562                 //
563                 // verify that illegal argument exceptions occur
564                 //
565                 try
566                 {
567                         t = Gregorian.generic.toTime (0, 1, 1, 0, 0, 0, 0, Gregorian.AD_ERA);
568                         assert(false, "Did not throw illegal argument exception");
569                 }
570                 catch(Exception iae)
571                 {
572                 }
573                 try
574                 {
575                         t = Gregorian.generic.toTime (1, 0, 1, 0, 0, 0, 0, Gregorian.AD_ERA);
576                         assert(false, "Did not throw illegal argument exception");
577                 }
578                 catch(IllegalArgumentException iae)
579                 {
580                 }
581                 try
582                 {
583                         t = Gregorian.generic.toTime (1, 1, 0, 0, 0, 0, 0, Gregorian.BC_ERA);
584                         assert(false, "Did not throw illegal argument exception");
585                 }
586                 catch(IllegalArgumentException iae)
587                 {
588                 }
589 
590                 try
591                 {
592                     t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0);
593                     t = Gregorian.generic.addMonths(t, 1);
594                     assert(false, "Did not throw illegal argument exception");
595                 }
596                 catch(IllegalArgumentException iae)
597                 {
598                 }
599 
600                 try
601                 {
602                     t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0);
603                     t = Gregorian.generic.addMonths(t, 1, true);
604                     assert(Gregorian.generic.getDayOfMonth(t) == 29);
605                 }
606                 catch(IllegalArgumentException iae)
607                 {
608                     assert(false, "Should not throw illegal argument exception");
609                 }
610 
611 
612 
613         }
614 }