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