The OpenD Programming Language

1 /++
2 Fast BetterC Date type with Boost ABI and mangling compatability.
3 
4 $(SCRIPT inhibitQuickIndex = 1;)
5 $(DIVC quickindex,
6 $(BOOKTABLE,
7 $(TR $(TH Category) $(TH Functions))
8 $(TR $(TD Main date types) $(TD
9     $(LREF Date)
10     $(LREF YearMonth)
11     $(LREF YearQuarter)
12 ))
13 $(TR $(TD Other date types) $(TD
14     $(LREF Month)
15     $(LREF Quarter)
16     $(LREF DayOfWeek)
17 ))
18 $(TR $(TD Date checking) $(TD
19     $(LREF valid)
20     $(LREF yearIsLeapYear)
21 ))
22 $(TR $(TD Date conversion) $(TD
23     $(LREF daysToDayOfWeek)
24     $(LREF quarter)
25 ))
26 $(TR $(TD Other) $(TD
27     $(LREF AllowDayOverflow)
28     $(LREF DateTimeException)
29     $(LREF AssumePeriod)
30 ))
31 ))
32 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
33 Authors: $(HTTP jmdavisprog.com, Jonathan M Davis), Ilia Ki (boost-like and BetterC rework)
34 +/
35 module mir.date;
36 
37 import mir.primitives: isOutputRange;
38 import mir.serde: serdeProxy;
39 import mir.timestamp: Timestamp;
40 import std.traits: isSomeChar, Unqual;
41 
42 version(mir_test)
43 version(D_Exceptions)
44 version(unittest) import std.exception : assertThrown;
45 
46 version(test_with_asdf)
47 unittest
48 {
49     import asdf.serialization;
50 
51     assert(Date(2020, 3, 19).serializeToJson == `"2020-03-19"`);
52     assert(`"2020-03-19"`.deserialize!Date == Date(2020, 3, 19));
53     assert(`"20200319"`.deserialize!Date == Date(2020, 3, 19));
54     assert(`"2020-Mar-19"`.deserialize!Date == Date(2020, 3, 19));
55 }
56 
57 /++
58 Returns whether the given value is valid for the given unit type when in a
59 time point. Naturally, a duration is not held to a particular range, but
60 the values in a time point are (e.g. a month must be in the range of
61 1 - 12 inclusive).
62 Params:
63     units = The units of time to validate.
64     value = The number to validate.
65 +/
66 bool valid(string units)(int value) @safe pure nothrow @nogc
67 if (units == "months" ||
68     units == "hours" ||
69     units == "minutes" ||
70     units == "seconds")
71 {
72     static if (units == "months")
73         return value >= Month.jan && value <= Month.dec;
74     else static if (units == "hours")
75         return value >= 0 && value <= 23;
76     else static if (units == "minutes")
77         return value >= 0 && value <= 59;
78     else static if (units == "seconds")
79         return value >= 0 && value <= 59;
80 }
81 
82 ///
83 version (mir_test)
84 @safe unittest
85 {
86     assert(valid!"hours"(12));
87     assert(!valid!"hours"(32));
88     assert(valid!"months"(12));
89     assert(!valid!"months"(13));
90 }
91 
92 /++
93 Returns whether the given day is valid for the given year and month.
94 Params:
95     units = The units of time to validate.
96     year  = The year of the day to validate.
97     month = The month of the day to validate (January is 1).
98     day   = The day to validate.
99 +/
100 bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc
101     if (units == "days")
102 {
103     return day > 0 && day <= maxDay(year, month);
104 }
105 
106 ///
107 version (mir_test)
108 @safe pure nothrow @nogc unittest
109 {
110     assert(valid!"days"(2016, 2, 29));
111     assert(!valid!"days"(2016, 2, 30));
112     assert(valid!"days"(2017, 2, 20));
113     assert(!valid!"days"(2017, 2, 29));
114 }
115 
116 ///
117 enum AllowDayOverflow : bool
118 {
119     ///
120     no,
121     ///
122     yes
123 }
124 
125 /++
126 Whether the given Gregorian Year is a leap year.
127 Params:
128     year = The year to to be tested.
129  +/
130 bool yearIsLeapYear(int year) @safe pure nothrow @nogc
131 {
132     if (year % 400 == 0)
133         return true;
134     if (year % 100 == 0)
135         return false;
136     return year % 4 == 0;
137 }
138 
139 ///
140 version (mir_test)
141 @safe unittest
142 {
143     foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010])
144     {
145         assert(!yearIsLeapYear(year));
146         assert(!yearIsLeapYear(-year));
147     }
148 
149     foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
150     {
151         assert(yearIsLeapYear(year));
152         assert(yearIsLeapYear(-year));
153     }
154 }
155 
156 version (mir_test)
157 @safe unittest
158 {
159     import std.format : format;
160     foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999,
161                     2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011])
162     {
163         assert(!yearIsLeapYear(year), format("year: %s.", year));
164         assert(!yearIsLeapYear(-year), format("year: %s.", year));
165     }
166 
167     foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
168     {
169         assert(yearIsLeapYear(year), format("year: %s.", year));
170         assert(yearIsLeapYear(-year), format("year: %s.", year));
171     }
172 }
173 
174 ///
175 enum Month : short
176 {
177     ///
178     jan = 1,
179     ///
180     feb,
181     ///
182     mar,
183     ///
184     apr,
185     ///
186     may,
187     ///
188     jun,
189     ///
190     jul,
191     ///
192     aug,
193     ///
194     sep,
195     ///
196     oct,
197     ///
198     nov,
199     ///
200     dec,
201 }
202 
203 version(D_Exceptions)
204 ///
205 class DateTimeException : Exception
206 {
207     ///
208     @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
209     {
210         super(msg, file, line, nextInChain);
211     }
212 
213     /// ditto
214     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
215     {
216         super(msg, file, line, nextInChain);
217     }
218 }
219 
220 version(D_Exceptions)
221 {
222     private static immutable InvalidMonth = new DateTimeException("Date: Invalid Month");
223     private static immutable InvalidDay = new DateTimeException("Date: Invalid Day");
224     private static immutable InvalidISOString = new DateTimeException("Date: Invalid ISO String");
225     private static immutable InvalidISOExtendedString = new DateTimeException("Date: Invalid ISO Extended String");
226     private static immutable InvalidSimpleString = new DateTimeException("Date: Invalid Simple String");
227     private static immutable InvalidString = new DateTimeException("Date: Invalid String");
228 }
229 
230 version (mir_test)
231 @safe unittest
232 {
233     initializeTests();
234 }
235 
236 /++
237     Represents the 7 days of the Gregorian week (Monday is 0).
238   +/
239 extern(C++, "mir")
240 enum DayOfWeek
241 {
242     mon = 0, ///
243     tue,     ///
244     wed,     ///
245     thu,     ///
246     fri,     ///
247     sat,     ///
248     sun,     ///
249 }
250 
251 ///
252 @serdeProxy!Timestamp
253 struct YearMonthDay
254 {
255     short year  = 1;
256     Month month = Month.jan;
257     ubyte day   = 1;
258 
259     ///
260     Quarter quarter() @safe pure nothrow @nogc @property
261     {
262         return month.quarter;
263     }
264 
265     ///
266     Timestamp timestamp() @safe pure nothrow @nogc const @property
267     {
268         return Timestamp(year, cast(ubyte)month, day);
269     }
270 
271     ///
272     alias opCast(T : Timestamp) = timestamp;
273 
274 @safe pure @nogc:
275 
276     ///
277     YearQuarter yearQuarter() @safe pure nothrow @nogc @property
278     {
279         return YearQuarter(year, this.quarter);
280     }
281 
282     ///
283     alias opCast(T : YearQuarter) = yearQuarter;
284 
285     ///
286     version(mir_test)
287     unittest
288     {
289         import mir.timestamp;
290         auto timestamp = cast(Timestamp) YearMonthDay(2020, Month.may, 12);
291     }
292 
293     ///
294     this(short year, Month month, ubyte day) @safe pure nothrow @nogc
295     {
296         this.year = year;
297         this.month = month;
298         this.day = day;
299     }
300 
301     ///
302     this(Date date) @safe pure nothrow @nogc
303     {
304         this = date.yearMonthDay;
305     }
306 
307     ///
308     version(mir_test)
309     @safe unittest
310     {
311         auto d = YearMonthDay(2020, Month.may, 31);
312         auto ym = d.YearMonth;
313         assert(ym.year == 2020);
314         assert(ym.month == Month.may);
315     }
316 
317     //
318     version(mir_test)
319     @safe unittest
320     {
321         auto d = YearMonthDay(2050, Month.dec, 31);
322         auto ym = d.YearMonth;
323         assert(ym.year == 2050);
324         assert(ym.month == Month.dec);
325     }
326 
327     ///
328     this(YearMonth yearMonth, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc
329     {
330         with(yearMonth) this(year, month, day(assumePeriod));
331     }
332 
333     ///
334     this(YearQuarter yearQuarter, AssumePeriod assumePeriodMonth = AssumePeriod.begin, AssumePeriod assumePeriodDay = AssumePeriod.begin) @safe pure nothrow @nogc
335     {
336         with(yearQuarter) this(year, month(assumePeriodMonth), day(assumePeriodDay));
337     }
338 
339     version(D_Exceptions)
340     ///
341     this(Timestamp timestamp) @safe pure @nogc
342     {
343         if (timestamp.precision != Timestamp.Precision.day)
344         {
345             static immutable exc = new Exception("YearMonthDay: invalid timestamp precision");
346             { import mir.exception : toMutable; throw exc.toMutable; }
347         }
348         with(timestamp) this(year, cast(Month)month, day);
349     }
350 
351     ///
352     @safe pure nothrow @nogc
353     ref YearMonthDay add(string units : "months")(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
354     {
355         auto years = months / 12;
356         months %= 12;
357         auto newMonth = month + months;
358 
359         if (months < 0)
360         {
361             if (newMonth < 1)
362             {
363                 newMonth += 12;
364                 --years;
365             }
366         }
367         else if (newMonth > 12)
368         {
369             newMonth -= 12;
370             ++years;
371         }
372 
373         year += years;
374         month = cast(Month) newMonth;
375 
376         immutable currMaxDay = maxDay(year, month);
377         immutable overflow = day - currMaxDay;
378 
379         if (overflow > 0)
380         {
381             if (allowOverflow == AllowDayOverflow.yes)
382             {
383                 ++month;
384                 day = cast(ubyte) overflow;
385             }
386             else
387                 day = cast(ubyte) currMaxDay;
388         }
389 
390         return this;
391     }
392 
393     ///
394     @safe pure nothrow @nogc
395     ref YearMonthDay add(string units : "quarters")(long quarters)
396     {
397         return add!"months"(quarters * 4);
398     }
399 
400     // Shares documentation with "years" version.
401     @safe pure nothrow @nogc
402     ref YearMonthDay add(string units : "years")(long years, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
403     {
404         year += years;
405 
406         immutable currMaxDay = maxDay(year, month);
407         immutable overflow = day - currMaxDay;
408 
409         if (overflow > 0)
410         {
411             if (allowOverflow == AllowDayOverflow.yes)
412             {
413                 ++month;
414                 day = cast(ubyte) overflow;
415             }
416             else
417                 day = cast(ubyte) currMaxDay;
418         }
419         return this;
420     }
421 
422     /++
423         Day of the year this $(LREF Date) is on.
424       +/
425     @property int dayOfYear() const @safe pure nothrow @nogc
426     {
427         if (month >= Month.jan && month <= Month.dec)
428         {
429             immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
430             auto monthIndex = month - Month.jan;
431 
432             return lastDay[monthIndex] + day;
433         }
434         assert(0, "Invalid month.");
435     }
436 
437     ///
438     version (mir_test)
439     @safe unittest
440     {
441         assert(YearMonthDay(1999, cast(Month) 1, 1).dayOfYear == 1);
442         assert(YearMonthDay(1999, cast(Month) 12, 31).dayOfYear == 365);
443         assert(YearMonthDay(2000, cast(Month) 12, 31).dayOfYear == 366);
444     }
445 
446     /++
447         Whether this $(LREF Date) is in a leap year.
448      +/
449     @property bool isLeapYear() const @safe pure nothrow @nogc
450     {
451         return yearIsLeapYear(year);
452     }
453 
454     private void setDayOfYear(bool useExceptions = false)(int days)
455     {
456         immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
457 
458         bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear);
459 
460         static if (useExceptions)
461         {
462             if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; }
463         }
464         else
465         {
466             assert(!dayOutOfRange, "Invalid Day");
467         }
468 
469         foreach (i; 1 .. lastDay.length)
470         {
471             if (days <= lastDay[i])
472             {
473                 month = cast(Month)(cast(int) Month.jan + i - 1);
474                 day = cast(ubyte)(days - lastDay[i - 1]);
475                 return;
476             }
477         }
478         assert(0, "Invalid day of the year.");
479     }
480 
481     /++
482         The last day in the month that this $(LREF Date) is in.
483       +/
484     @property ubyte daysInMonth() const @safe pure nothrow @nogc
485     {
486         return maxDay(year, month);
487     }
488 
489     /++
490         Whether the current year is a date in A.D.
491       +/
492     @property bool isAD() const @safe pure nothrow @nogc
493     {
494         return year > 0;
495     }
496 }
497 
498 /++
499 Controls the assumed start period of days for `YearMonth` or days and quarters
500 for `YearQuarter`
501 +/
502 enum AssumePeriod {
503     ///
504     begin,
505     ///
506     end
507 }
508 
509 /// Represents a date as a pair of years and months. 
510 @serdeProxy!Timestamp
511 struct YearMonth
512 {
513     short year  = 1;
514     Month month = Month.jan;
515 
516     version(D_BetterC){} else
517     {
518         private string toStringImpl(alias fun)() const @safe pure nothrow
519         {
520             import mir.small_string : SmallString;
521             SmallString!16 w;
522             try
523                 fun(w);
524             catch (Exception e)
525                 assert(0, __traits(identifier, fun) ~ " threw.");
526             return w[].idup;
527         }
528 
529         string toISOExtString() const @safe pure nothrow
530         {
531             return toStringImpl!toISOExtString;
532         }
533 
534         alias toString = toISOExtString;
535     }
536 
537     ///
538     void toISOExtString(W)(scope ref W w) const scope
539         if (isOutputRange!(W, char))
540     {
541         import mir.format: printZeroPad;
542         if (year >= 10_000)
543             w.put('+');
544         w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
545         w.put('-');
546         w.printZeroPad(cast(uint)month, 2);
547     }
548 
549     ///
550     version (mir_test)
551     @safe unittest
552     {
553         auto ym = YearMonth(1999, Month.jan);
554         assert(ym.toISOExtString == "1999-01");
555     }
556 
557     //
558     version (mir_test)
559     @safe unittest
560     {
561         auto ym = YearMonth(10_001, Month.jan);
562         assert(ym.toISOExtString == "+10001-01");
563     }
564 
565     @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
566     {
567         final switch (assumePeriod)
568         {
569             case AssumePeriod.begin:
570                 return 1;
571             case AssumePeriod.end:
572                 return daysInMonth;
573         }
574     }
575 
576     ///
577     version (mir_test)
578     @safe unittest
579     {
580         assert(YearMonth(1999, cast(Month) 1).day(AssumePeriod.begin) == 1);
581         assert(YearMonth(1999, cast(Month) 12).day(AssumePeriod.end) == 31);
582     }
583 
584     ///
585     Quarter quarter() @safe pure nothrow @nogc @property
586     {
587         return month.quarter;
588     }
589 
590     ///
591     version (mir_test)
592     @safe unittest
593     {
594         assert(YearMonth(1999, Month.jan).quarter == 1);
595     }
596 
597     ///
598     Timestamp timestamp() @safe pure nothrow @nogc const @property
599     {
600         return Timestamp(year, cast(ubyte)month);
601     }
602 
603     ///
604     alias opCast(T : Timestamp) = timestamp;
605 
606     ///
607     version(mir_test)
608     unittest
609     {
610         import mir.timestamp;
611         auto ym0 = YearMonth(2020, Month.may);
612         auto timestamp1 = cast(Timestamp) ym0;
613         auto ym1 = YearMonth(timestamp1);
614     }
615 
616     ///
617     this(short year, Month month) @safe pure nothrow @nogc
618     {
619         this.year = year;
620         this.month = month;
621     }
622 
623     ///
624     version (mir_test)
625     @safe unittest
626     {
627         auto ym = YearMonth(2000, Month.dec);
628     }
629 
630     ///
631     this(short year, ushort month) @safe pure @nogc
632     {
633         static immutable exc = new Exception("Month out of bounds [1, 12]");
634         if (1 > month || month > 12)
635             { import mir.exception : toMutable; throw exc.toMutable; }
636         this.year = year;
637         this.month = cast(Month)month;
638     }
639 
640     ///
641     version (mir_test)
642     @safe unittest
643     {
644         auto ym = YearMonth(2000, 12);
645     }
646 
647     ///
648     this(Date date) @safe pure nothrow @nogc
649     {
650         this(date.YearMonthDay);
651     }
652 
653     ///
654     version (mir_test)
655     @safe unittest
656     {
657         auto ym = YearMonth(Date(2000, Month.dec, 31));
658     }
659 
660     ///
661     version(mir_test)
662     @safe unittest
663     {
664         auto d = Date(2020, Month.may, 31);
665         auto ym = d.YearMonth;
666         assert(ym.year == 2020);
667         assert(ym.month == Month.may);
668     }
669 
670     //
671     version(mir_test)
672     @safe unittest
673     {
674         auto d = Date(2050, Month.dec, 31);
675         auto ym = d.YearMonth;
676         assert(ym.year == 2050);
677         assert(ym.month == Month.dec);
678     }
679 
680     ///
681     this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc
682     {
683         with(yearMonthDay) this(year, month);
684     }
685 
686     ///
687     version (mir_test)
688     @safe unittest
689     {
690         auto ym = YearMonth(YearMonthDay(2000, Month.dec, 31));
691     }
692 
693     ///
694     this(YearQuarter yearQuarter, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc
695     {
696         with(yearQuarter) this(year, month(assumePeriod));
697     }
698 
699     ///
700     version (mir_test)
701     @safe unittest
702     {
703         auto ym1 = YearMonth(YearQuarter(2000, Quarter.q1));
704         auto ym2 = YearMonth(YearQuarter(2000, Quarter.q1), AssumePeriod.end);
705     }
706 
707     version(D_Exceptions)
708     ///
709     this(Timestamp timestamp) @safe pure @nogc
710     {
711         if (timestamp.precision != Timestamp.Precision.month)
712         {
713             static immutable exc = new Exception("YearMonth: invalid timestamp precision");
714             { import mir.exception : toMutable; throw exc.toMutable; }
715         }
716         with(timestamp) this(year, cast(Month)month);
717     }
718 
719     Date nthWeekday(int n, DayOfWeek dow) const @safe pure nothrow @nogc
720     {
721         auto d = trustedWithDayOfMonth(1);
722         auto dc = d.dayOfWeek.daysToDayOfWeek(dow) + (n - 1) * 7;
723         d = d + dc;
724         return d;
725     }
726 
727     ///
728     version (mir_test)
729     @safe unittest
730     {
731         auto ym = YearMonth(2000, Month.nov);
732         assert(ym.nthWeekday(1, DayOfWeek.mon) == Date(2000, 11, 6));
733         assert(ym.nthWeekday(5, DayOfWeek.mon) == Date(2000, 12, 4));
734     }
735 
736     ///
737     Date trustedWithDayOfMonth(int days) const @safe pure nothrow @nogc
738     {
739         assert(days <= lengthOfMonth);
740         return Date.trustedCreate(year, month, days);
741     }
742 
743     ///
744     version (mir_test)
745     @safe unittest
746     {
747         auto ym = YearMonth(2000, Month.nov);
748         assert(ym.trustedWithDayOfMonth(6) == Date(2000, 11, 6));
749     }
750 
751     ///
752     int opCmp(YearMonth rhs) const pure nothrow @nogc @safe
753     {
754         if (auto d = this.year - rhs.year)
755             return d;
756         return this.month - rhs.month;
757     }
758 
759     ///
760     version (mir_test)
761     @safe unittest
762     {
763         auto ym = YearMonth(2000, Month.nov);
764         assert(ym.opCmp(YearMonth(2000, Month.nov)) == 0);
765         assert(ym.opCmp(YearMonth(2000, Month.oct)) == 1);
766         assert(ym.opCmp(YearMonth(2000, Month.dec)) == -1);
767         assert(ym.opCmp(YearMonth(2001, Month.nov)) == -1);
768     }
769 
770     ///
771     size_t toHash() const pure nothrow @nogc @safe
772     {
773         return year * 16 + month;
774     }
775 
776     ///
777     version (mir_test)
778     @safe unittest
779     {
780         assert(YearMonth(2000, Month.dec).toHash == 32012);
781     }
782 
783     ///
784     Date endOfMonth() const nothrow @property @nogc @safe pure
785     {
786         return Date.trustedCreate(year, month, lengthOfMonth);
787     }
788 
789     ///
790     version (mir_test)
791     @safe unittest
792     {
793         assert(YearMonth(2000, Month.dec).endOfMonth == Date(2000, Month.dec, 31));
794     }
795 
796     ///
797     ushort lengthOfMonth() const pure nothrow @property @nogc @safe
798     {
799         return maxDay(year, month);
800     }
801 
802     ///
803     version (mir_test)
804     @safe unittest
805     {
806         assert(YearMonth(2000, Month.dec).lengthOfMonth == 31);
807     }
808 
809     ///
810     this(scope const(char)[] str) @safe pure @nogc
811     {
812         this = fromISOExtString(str);
813     }
814 
815     ///
816     version (mir_test)
817     @safe unittest
818     {
819         auto ym = YearMonth("1999-01");
820         assert(ym.year == 1999);
821         assert(ym.month == 1);
822     }
823 
824     static bool fromISOExtString(C)(scope const(C)[] str, out YearMonth value) @safe pure @nogc
825         if (isSomeChar!C)
826     {
827         import mir.parse: fromString;
828         if (str.length < 7 || str[$-3] != '-')
829             return false;
830 
831         auto yearStr = str[0 .. $ - 3];
832 
833         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
834             return false;
835 
836         short year;
837         ushort month;
838 
839         const ret =
840          fromString(str[$ - 2 .. $], month)
841          && fromString(yearStr, year);
842 
843         value = YearMonth(year, month);
844         return ret;
845     }
846 
847     static YearMonth fromISOExtString(C)(scope const(C)[] str) @safe pure
848         if (isSomeChar!C)
849     {
850         YearMonth ret;
851         if (fromISOExtString(str, ret))
852             return ret;
853         static immutable exc = new Exception("Invalid YearMonth string");
854         { import mir.exception : toMutable; throw exc.toMutable; }
855     }
856 
857 nothrow:
858 
859     ///
860     deprecated("please use addMonths instead")
861     @safe pure nothrow @nogc
862     ref YearMonth add(string units : "months")(long months)
863     {
864         auto years = months / 12;
865         months %= 12;
866         auto newMonth = month + months;
867 
868         if (months < 0)
869         {
870             if (newMonth < 1)
871             {
872                 newMonth += 12;
873                 --years;
874             }
875         }
876         else if (newMonth > 12)
877         {
878             newMonth -= 12;
879             ++years;
880         }
881 
882         year += years;
883         month = cast(Month) newMonth;
884 
885         return this;
886     }
887 
888     ///
889     version(mir_test_deprecated)
890     @safe unittest
891     {
892         auto ym0 = YearMonth(2020, Month.jan);
893 
894         ym0.add!"months"(1);
895         assert(ym0.year == 2020);
896         assert(ym0.month == Month.feb);
897 
898         auto ym1 = ym0.add!"months"(1);
899         assert(ym1.year == 2020);
900         assert(ym1.month == Month.mar);
901 
902         // also changes ym0
903         assert(ym0.year == 2020);
904         assert(ym0.month == Month.mar);
905 
906         ym1.add!"months"(10);
907         assert(ym1.year == 2021);
908         assert(ym1.month == Month.jan);
909 
910         ym1.add!"months"(-13);
911         assert(ym1.year == 2019);
912         assert(ym1.month == Month.dec);
913     }
914 
915     ///
916     deprecated("please use addQuarters instead")
917     @safe pure nothrow @nogc
918     ref YearMonth add(string units : "quarters")(long quarters)
919     {
920         return add!"months"(quarters * 3);
921     }
922 
923     ///
924     version(mir_test_deprecated)
925     @safe unittest
926     {
927         auto yq0 = YearMonth(2020, Month.jan);
928 
929         yq0.add!"quarters"(1);
930         assert(yq0.year == 2020);
931         assert(yq0.month == Month.apr);
932 
933         auto yq1 = yq0.add!"quarters"(1);
934         assert(yq1.year == 2020);
935         assert(yq1.month == Month.jul);
936 
937         // also changes yq0
938         assert(yq0.year == 2020);
939         assert(yq0.month == Month.jul);
940 
941         yq1.add!"quarters"(2);
942         assert(yq1.year == 2021);
943         assert(yq1.month == Month.jan);
944 
945         yq1.add!"quarters"(-5);
946         assert(yq1.year == 2019);
947         assert(yq1.month == Month.oct);
948     }
949 
950     ///
951     deprecated("please use addYears instead")
952     @safe pure nothrow @nogc
953     ref YearMonth add(string units : "years")(long years)
954     {
955         year += years;
956         return this;
957     }
958 
959     ///
960     version(mir_test_deprecated)
961     @safe unittest
962     {
963         auto ym0 = YearMonth(2020, Month.jan);
964 
965         ym0.add!"years"(1);
966         assert(ym0.year == 2021);
967         assert(ym0.month == Month.jan);
968 
969         auto ym1 = ym0.add!"years"(1);
970         assert(ym1.year == 2022);
971         assert(ym1.month == Month.jan);
972 
973         // also changes ym0
974         assert(ym0.year == 2022);
975         assert(ym0.month == Month.jan);
976     }
977 
978     ///
979     @safe pure nothrow @nogc
980     YearMonth addMonths(long months)
981     {
982         auto newYear = year;
983         newYear += months / 12;
984         months %= 12;
985         auto newMonth = month;
986         newMonth += months;
987 
988         if (months < 0)
989         {
990             if (newMonth < 1)
991             {
992                 newMonth += 12;
993                 --newYear;
994             }
995         }
996         else if (newMonth > 12)
997         {
998             newMonth -= 12;
999             ++newYear;
1000         }
1001 
1002         return YearMonth(newYear, newMonth);
1003     }
1004 
1005     ///
1006     version(mir_test)
1007     @safe unittest
1008     {
1009         auto ym0 = YearMonth(2020, Month.jan);
1010 
1011         auto ym1 = ym0.addMonths(15);
1012         assert(ym1.year == 2021);
1013         assert(ym1.month == Month.apr);
1014 
1015         auto ym2 = ym1.addMonths(-6);
1016         assert(ym2.year == 2020);
1017         assert(ym2.month == Month.oct);
1018 
1019         auto ym3 = YearMonth(2020, Month.dec).addMonths(3);
1020         assert(ym3.year == 2021);
1021         assert(ym3.month == Month.mar);
1022 
1023         // ym0 is left unchagned
1024         assert(ym0.year == 2020);
1025         assert(ym0.month == Month.jan);
1026     }
1027 
1028     ///
1029     @safe pure nothrow @nogc
1030     YearMonth addQuarters(long quarters)
1031     {
1032         return addMonths(quarters * 3);
1033     }
1034 
1035     ///
1036     version(mir_test)
1037     @safe unittest
1038     {
1039         auto ym0 = YearMonth(2020, Month.jan);
1040 
1041         auto ym1 = ym0.addQuarters(5);
1042         assert(ym1.year == 2021);
1043         assert(ym1.month == Month.apr);
1044 
1045         auto ym2 = ym1.addQuarters(-2);
1046         assert(ym2.year == 2020);
1047         assert(ym2.month == Month.oct);
1048 
1049         auto ym3 = YearMonth(2020, Month.dec).addQuarters(1);
1050         assert(ym3.year == 2021);
1051         assert(ym3.month == Month.mar);
1052 
1053         // ym0 is left unchagned
1054         assert(ym0.year == 2020);
1055         assert(ym0.month == Month.jan);
1056     }
1057 
1058     ///
1059     @safe pure nothrow @nogc
1060     YearMonth addYears(long years)
1061     {
1062         auto newYear = this.year;
1063         newYear += years;
1064         return YearMonth(newYear, month);
1065     }
1066 
1067     ///
1068     version(mir_test)
1069     @safe unittest
1070     {
1071         auto ym0 = YearMonth(2020, Month.jan);
1072 
1073         auto ym1 = ym0.addYears(1);
1074         assert(ym1.year == 2021);
1075         assert(ym1.month == Month.jan);
1076  
1077         // leaves ym0 unchanged
1078         assert(ym0.year == 2020);
1079         assert(ym0.month == Month.jan);
1080     }
1081 
1082     private void setMonthOfYear(bool useExceptions = false)(int days)
1083     {
1084         immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
1085 
1086         bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear);
1087 
1088         static if (useExceptions)
1089         {
1090             if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; }
1091         }
1092         else
1093         {
1094             assert(!dayOutOfRange, "Invalid Day");
1095         }
1096 
1097         foreach (i; 1 .. lastDay.length)
1098         {
1099             if (days <= lastDay[i])
1100             {
1101                 month = cast(Month)(cast(int) Month.jan + i - 1);
1102                 return;
1103             }
1104         }
1105         assert(0, "Invalid day of the year.");
1106     }
1107 
1108     ///
1109     version(mir_test)
1110     @safe unittest
1111     {
1112         auto ym = YearMonth(2020, Month.feb);
1113         ym.setMonthOfYear(10);
1114         assert(ym.year == 2020);
1115         assert(ym.month == Month.jan);
1116         ym.setMonthOfYear(100);
1117         assert(ym.year == 2020);
1118         assert(ym.month == Month.apr);
1119         ym.setMonthOfYear(200);
1120         assert(ym.year == 2020);
1121         assert(ym.month == Month.jul);
1122         ym.setMonthOfYear(300);
1123         assert(ym.year == 2020);
1124         assert(ym.month == Month.oct);
1125     }
1126 
1127     ///
1128     int opBinary(string op : "-")(YearMonth rhs)
1129     {
1130         alias a = this;
1131         alias b = rhs;
1132         return (a.year - b.year) * 12 + a.month - b.month;
1133     }
1134 
1135     ///
1136     YearMonth opBinary(string op)(int rhs)
1137         if (op == "+" || op == "-")
1138     {
1139         static if (op == "+")
1140            return addMonths(rhs);
1141         else
1142            return addMonths(-rhs);
1143     }
1144 
1145     ///
1146     alias opBinaryRight(string op : "+") = opBinary!"+";
1147 
1148     ///
1149     ref YearMonth opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc
1150         if (op == "+" || op == "-")
1151     {
1152         static if (op == "+")
1153            this = addMonths(rhs);
1154         else
1155            this = addMonths(-rhs);
1156         return this;
1157     }
1158 
1159     ///
1160     @safe pure @nogc nothrow
1161     version(mir_test)
1162     unittest
1163     {
1164         auto x = YearMonth(2020, Month.mar);
1165         auto x1 = x + 1;
1166         assert(x1 == YearMonth(2020, Month.apr));
1167         auto x2 = x + 2;
1168         assert(x2 == YearMonth(2020, Month.may));
1169         auto x3 = x + 3;
1170         assert(x3 == YearMonth(2020, Month.jun));
1171     }
1172 
1173     ///
1174     @safe pure @nogc nothrow
1175     version(mir_test)
1176     unittest {
1177         auto ym = YearMonth(2020, Month.mar);
1178         ym += 2;
1179         assert(ym == YearMonth(2020, Month.may));
1180         ym -= 1;
1181         assert(ym == YearMonth(2020, Month.apr));
1182     }
1183 
1184     /// Get a slice of YearMonths
1185     @safe pure @nogc nothrow
1186     version(mir_test)
1187     unittest {
1188         import mir.ndslice.topology: iota;
1189 
1190         static immutable result1 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.apr), YearMonth(2020, Month.may), YearMonth(2020, Month.jun)];
1191         static immutable result2 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.may), YearMonth(2020, Month.jul), YearMonth(2020, Month.sep)];
1192 
1193         auto ym = YearMonth(2020, Month.mar);
1194 
1195         auto x = ym + 4.iota!uint;
1196         assert(x == result1);
1197 
1198         // every other month
1199         auto y = ym + iota!uint([4], 0, 2);
1200         assert(y == result2);
1201     }
1202 
1203     /++
1204         Day of the year this $(LREF Date) is on.
1205       +/
1206     @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1207     {
1208         if (month >= Month.jan && month <= Month.dec)
1209         {
1210             immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
1211             auto monthIndex = month - Month.jan;
1212 
1213             return lastDay[monthIndex] + day(assumePeriod);
1214         }
1215         assert(0, "Invalid month.");
1216     }
1217 
1218     ///
1219     version (mir_test)
1220     @safe unittest
1221     {
1222         assert(YearMonth(1999, cast(Month) 1).dayOfYear == 1);
1223         assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 335);
1224         assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.end) == 365);
1225         assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 336);
1226         assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.end) == 366);
1227     }
1228 
1229     /++
1230         Whether this $(LREF Date) is in a leap year.
1231      +/
1232     @property bool isLeapYear() const @safe pure nothrow @nogc
1233     {
1234         return yearIsLeapYear(year);
1235     }
1236 
1237     ///
1238     version (mir_test)
1239     @safe unittest
1240     {
1241         assert(YearMonth(1999, cast(Month) 12).isLeapYear == false);
1242         assert(YearMonth(2000, cast(Month) 12).isLeapYear == true);
1243     }
1244 
1245     /++
1246         The last day in the month that this $(LREF Date) is in.
1247       +/
1248     @property ubyte daysInMonth() const @safe pure nothrow @nogc
1249     {
1250         return maxDay(year, month);
1251     }
1252 
1253     ///
1254     version(mir_test)
1255     @safe unittest
1256     {
1257         assert(YearMonth(2020, Month.dec).daysInMonth == 31);
1258     }
1259 
1260     /++
1261         Whether the current year is a date in A.D.
1262       +/
1263     @property bool isAD() const @safe pure nothrow @nogc
1264     {
1265         return year > 0;
1266     }
1267     
1268     ///
1269     version(mir_test)
1270     @safe unittest
1271     {
1272         assert(YearMonth(2020, Month.jan).isAD == true);
1273     }
1274 }
1275 
1276 ///
1277 enum Quarter : short
1278 {
1279     ///
1280     q1 = 1,
1281     ///
1282     q2,
1283     ///
1284     q3,
1285     ///
1286     q4,
1287 }
1288 
1289 /++
1290 Returns the quarter for a given month.
1291 
1292 Params:
1293     month = month
1294 
1295 +/
1296 @safe pure @nogc nothrow
1297 Quarter quarter(Month month)
1298 {
1299     return cast(Quarter)((cast(ubyte)month - 1) / 3 + 1);
1300 }
1301 
1302 ///
1303 version(mir_test)
1304 @safe pure @nogc nothrow
1305 unittest {
1306     assert(Month.jan.quarter == Quarter.q1);
1307     assert(Month.feb.quarter == Quarter.q1);
1308     assert(Month.mar.quarter == Quarter.q1);
1309     assert(Month.apr.quarter == Quarter.q2);
1310     assert(Month.may.quarter == Quarter.q2);
1311     assert(Month.jun.quarter == Quarter.q2);
1312     assert(Month.jul.quarter == Quarter.q3);
1313     assert(Month.aug.quarter == Quarter.q3);
1314     assert(Month.sep.quarter == Quarter.q3);
1315     assert(Month.oct.quarter == Quarter.q4);
1316     assert(Month.nov.quarter == Quarter.q4);
1317     assert(Month.dec.quarter == Quarter.q4);
1318 }
1319 
1320 private
1321 @safe pure @nogc nothrow
1322 Month monthInQuarter(Quarter quarter, AssumePeriod assumePeriod = AssumePeriod.begin)
1323 {
1324     assert (assumePeriod == AssumePeriod.begin || assumePeriod == AssumePeriod.end);
1325     return cast(Month) ((cast(byte)quarter - 1) * 3 + 1 + 2 * assumePeriod);
1326 }
1327 
1328 version(mir_test)
1329 @safe pure @nogc nothrow
1330 unittest {
1331     assert(Quarter.q1.monthInQuarter == Month.jan);
1332     assert(Quarter.q1.monthInQuarter(AssumePeriod.end) == Month.mar);
1333     assert(Quarter.q2.monthInQuarter == Month.apr);
1334     assert(Quarter.q2.monthInQuarter(AssumePeriod.end) == Month.jun);
1335     assert(Quarter.q3.monthInQuarter == Month.jul);
1336     assert(Quarter.q3.monthInQuarter(AssumePeriod.end) == Month.sep);
1337     assert(Quarter.q4.monthInQuarter == Month.oct);
1338     assert(Quarter.q4.monthInQuarter(AssumePeriod.end) == Month.dec);
1339 }
1340 
1341 /// Represents a date as a pair of years and quarters. 
1342 @serdeProxy!Timestamp
1343 struct YearQuarter
1344 {
1345     short year  = 1;
1346     Quarter quarter = Quarter.q1;
1347     
1348     ///
1349     @property Month month(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1350     {
1351         return quarter.monthInQuarter(assumePeriod);
1352     }
1353 
1354     ///
1355     version (mir_test)
1356     @safe unittest
1357     {
1358         auto yq = YearQuarter(2000, Quarter.q4);
1359         assert(yq.month == 10);
1360         assert(yq.month(AssumePeriod.end) == 12);
1361     }
1362 
1363     ///
1364     @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1365     {
1366         final switch (assumePeriod)
1367         {
1368             case AssumePeriod.begin:
1369                 return 1;
1370             case AssumePeriod.end:
1371                 return daysInMonth;
1372         }
1373     }
1374 
1375     ///
1376     version (mir_test)
1377     @safe unittest
1378     {
1379         auto yq = YearQuarter(2000, Quarter.q4);
1380         assert(yq.day == 1);
1381         assert(yq.day(AssumePeriod.end) == 31);
1382     }
1383 
1384     ///
1385     Timestamp timestamp() @safe pure nothrow @nogc const @property
1386     {
1387         return Timestamp(year, cast(ubyte)month);
1388     }
1389 
1390     ///
1391     version(mir_test)
1392     unittest
1393     {
1394         import mir.timestamp;
1395         auto yq = YearQuarter(2020, Quarter.q2);
1396         auto ts = yq.timestamp;
1397     }
1398 
1399     ///
1400     alias opCast(T : Timestamp) = timestamp;
1401 
1402     ///
1403     version(mir_test)
1404     unittest
1405     {
1406         import mir.timestamp;
1407         auto yq = YearQuarter(2020, Quarter.q2);
1408         auto timestamp = cast(Timestamp) yq;
1409     }
1410 
1411     ///
1412     this(short year, Quarter quarter) @safe pure nothrow @nogc
1413     {
1414         this.year = year;
1415         this.quarter = quarter;
1416     }
1417 
1418     ///
1419     version (mir_test)
1420     @safe unittest
1421     {
1422         auto yq = YearQuarter(2000, Quarter.q4);
1423     }
1424 
1425     ///
1426     this(short year, Month month) @safe pure nothrow @nogc
1427     {
1428         this.year = year;
1429         this.quarter = month.quarter;
1430     }
1431 
1432     ///
1433     version (mir_test)
1434     @safe unittest
1435     {
1436         auto yq = YearQuarter(2000, Month.dec);
1437     }
1438 
1439     ///
1440     this(Date date) @safe pure nothrow @nogc
1441     {
1442         this = date.yearQuarter;
1443     }
1444 
1445     ///
1446     version (mir_test)
1447     @safe unittest
1448     {
1449         auto yq = YearQuarter(Date(2000, Month.dec, 31));
1450     }
1451 
1452     ///
1453     this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc
1454     {
1455         with(yearMonthDay) this(year, quarter);
1456     }
1457 
1458     ///
1459     version (mir_test)
1460     @safe unittest
1461     {
1462         auto ym = YearQuarter(YearMonthDay(2000, Month.dec, 31));
1463     }
1464 
1465     ///
1466     this(YearMonth yearMonth) @safe pure nothrow @nogc
1467     {
1468         with(yearMonth) this(year, quarter);
1469     }
1470 
1471     ///
1472     version (mir_test)
1473     @safe unittest
1474     {
1475         auto yq = YearQuarter(YearMonth(2000, Month.dec));
1476     }
1477 
1478     version(D_Exceptions)
1479     ///
1480     this(Timestamp timestamp) @safe pure @nogc
1481     {
1482         if (timestamp.precision != Timestamp.Precision.month)
1483         {
1484             static immutable exc = new Exception("YearMonth: invalid timestamp precision");
1485             { import mir.exception : toMutable; throw exc.toMutable; }
1486         }
1487         with(timestamp) this(year, cast(Month)month);
1488     }
1489 
1490     ///
1491     version(mir_test)
1492     @safe unittest
1493     {
1494         import mir.timestamp;
1495         auto ts = Timestamp(2020, 4);
1496         auto yq = YearQuarter(ts);
1497     }
1498 
1499     ///
1500     deprecated("please use addQuarters instead")
1501     @safe pure nothrow @nogc
1502     ref YearQuarter add(string units : "quarters")(long quarters)
1503     {
1504         auto years = quarters / 4;
1505         quarters %= 4;
1506         auto newQuarter = quarter + quarters;
1507 
1508         if (quarters < 0)
1509         {
1510             if (newQuarter < 1)
1511             {
1512                 newQuarter += 4;
1513                 --years;
1514             }
1515         }
1516         else if (newQuarter > 4)
1517         {
1518             newQuarter -= 4;
1519             ++years;
1520         }
1521 
1522         year += years;
1523         quarter = cast(Quarter) newQuarter;
1524 
1525         return this;
1526     }
1527 
1528     ///
1529     version(mir_test_deprecated)
1530     @safe unittest
1531     {
1532         auto yq0 = YearQuarter(2020, Quarter.q1);
1533 
1534         yq0.add!"quarters"(1);
1535         assert(yq0.year == 2020);
1536         assert(yq0.quarter == Quarter.q2);
1537 
1538         auto yq1 = yq0.add!"quarters"(1);
1539         assert(yq1.year == 2020);
1540         assert(yq1.quarter == Quarter.q3);
1541 
1542         // also changes yq0
1543         assert(yq0.year == 2020);
1544         assert(yq0.quarter == Quarter.q3);
1545 
1546         yq1.add!"quarters"(2);
1547         assert(yq1.year == 2021);
1548         assert(yq1.quarter == Quarter.q1);
1549 
1550         yq1.add!"quarters"(-5);
1551         assert(yq1.year == 2019);
1552         assert(yq1.quarter == Quarter.q4);
1553     }
1554 
1555     ///
1556     deprecated("please use addYears instead")
1557     @safe pure nothrow @nogc
1558     ref YearQuarter add(string units : "years")(long years)
1559     {
1560         year += years;
1561         return this;
1562     }
1563 
1564     ///
1565     version(mir_test_deprecated)
1566     @safe unittest
1567     {
1568         auto yq0 = YearQuarter(2020, Quarter.q1);
1569 
1570         yq0.add!"years"(1);
1571         assert(yq0.year == 2021);
1572         assert(yq0.quarter == Quarter.q1);
1573 
1574         auto yq1 = yq0.add!"years"(1);
1575         assert(yq1.year == 2022);
1576         assert(yq1.quarter == Quarter.q1);
1577 
1578         // also changes yq0
1579         assert(yq0.year == 2022);
1580         assert(yq0.quarter == Quarter.q1);
1581     }
1582 
1583     ///
1584     @safe pure nothrow @nogc
1585     YearQuarter addQuarters(long quarters)
1586     {
1587         auto years = quarters / 4;
1588         auto newYear = year;
1589         newYear += years;
1590         quarters %= 4;
1591         auto newQuarter = quarter + quarters;
1592 
1593         if (quarters < 0)
1594         {
1595             if (newQuarter < 1)
1596             {
1597                 newQuarter += 4;
1598                 --newYear;
1599             }
1600         }
1601         else if (newQuarter > 4)
1602         {
1603             newQuarter -= 4;
1604             ++newYear;
1605         }
1606 
1607         return YearQuarter(newYear, cast(Quarter) newQuarter);
1608     }
1609 
1610     ///
1611     version(mir_test)
1612     @safe unittest
1613     {
1614         auto yq0 = YearQuarter(2020, Quarter.q1);
1615 
1616         auto yq1 = yq0.addQuarters(5);
1617         assert(yq1.year == 2021);
1618         assert(yq1.quarter == Quarter.q2);
1619 
1620         auto yq2 = yq1.addQuarters(-2);
1621         assert(yq2.year == 2020);
1622         assert(yq2.quarter == Quarter.q4);
1623 
1624         auto yq3 = YearQuarter(2020, Quarter.q4).addQuarters(1);
1625         assert(yq3.year == 2021);
1626         assert(yq3.quarter == Quarter.q1);
1627 
1628         // yq0 is left unchagned
1629         assert(yq0.year == 2020);
1630         assert(yq0.quarter == Quarter.q1);
1631     }
1632 
1633     ///
1634     @safe pure nothrow @nogc
1635     YearQuarter addYears(long years)
1636     {
1637         auto newYear = this.year;
1638         newYear += years;
1639         return YearQuarter(newYear, quarter);
1640     }
1641 
1642     ///
1643     version(mir_test)
1644     @safe unittest
1645     {
1646         auto yq0 = YearQuarter(2020, Quarter.q1);
1647 
1648         auto yq1 = yq0.addYears(1);
1649         assert(yq1.year == 2021);
1650         assert(yq1.quarter == Quarter.q1);
1651  
1652         // leaves yq0 unchanged
1653         assert(yq0.year == 2020);
1654         assert(yq0.quarter == Quarter.q1);
1655     }
1656 
1657     private void setQuarterOfYear(bool useExceptions = false)(int days)
1658     {
1659         immutable int[] lastDay = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap;
1660 
1661         bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear);
1662 
1663         static if (useExceptions)
1664         {
1665             if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; }
1666         }
1667         else
1668         {
1669             assert(!dayOutOfRange, "Invalid Day");
1670         }
1671 
1672         foreach (i; 1 .. lastDay.length)
1673         {
1674             if (days <= lastDay[i])
1675             {
1676                 quarter = cast(Quarter)(cast(int) Quarter.q1 + i - 1);
1677                 return;
1678             }
1679         }
1680         assert(0, "Invalid day of the year.");
1681     }
1682 
1683     ///
1684     version(mir_test)
1685     @safe unittest
1686     {
1687         auto yq = YearQuarter(2020, Quarter.q3);
1688         yq.setQuarterOfYear(10);
1689         assert(yq.year == 2020);
1690         assert(yq.quarter == Quarter.q1);
1691         yq.setQuarterOfYear(100);
1692         assert(yq.year == 2020);
1693         assert(yq.quarter == Quarter.q2);
1694         yq.setQuarterOfYear(200);
1695         assert(yq.year == 2020);
1696         assert(yq.quarter == Quarter.q3);
1697         yq.setQuarterOfYear(300);
1698         assert(yq.year == 2020);
1699         assert(yq.quarter == Quarter.q4);
1700     }
1701 
1702     ///
1703     int opBinary(string op : "-")(YearQuarter rhs)
1704     {
1705         alias a = this;
1706         alias b = rhs;
1707         return (a.year - b.year) * 4 + a.quarter - b.quarter;
1708     }
1709 
1710     ///
1711     YearQuarter opBinary(string op)(int rhs)
1712         if (op == "+" || op == "-")
1713     {
1714         static if (op == "+")
1715            return addQuarters(rhs);
1716         else
1717            return addQuarters(-rhs);
1718     }
1719 
1720     ///
1721     alias opBinaryRight(string op : "+") = opBinary!"+";
1722 
1723     ///
1724     ref YearQuarter opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc
1725         if (op == "+" || op == "-")
1726     {
1727         static if (op == "+")
1728            this = addQuarters(rhs);
1729         else
1730            this = addQuarters(-rhs);
1731         return this;
1732     }
1733 
1734     ///
1735     @safe pure @nogc nothrow
1736     version(mir_test)
1737     unittest
1738     {
1739         auto x = YearQuarter(2020, Quarter.q1);
1740         auto x1 = x + 1;
1741         assert(x1 == YearQuarter(2020, Quarter.q2));
1742         auto x2 = x + 2;
1743         assert(x2 == YearQuarter(2020, Quarter.q3));
1744         auto x3 = x + 3;
1745         assert(x3 == YearQuarter(2020, Quarter.q4));
1746     }
1747 
1748     ///
1749     @safe pure @nogc nothrow
1750     version(mir_test)
1751     unittest {
1752         auto yq = YearQuarter(2020, Quarter.q1);
1753         yq += 2;
1754         assert(yq == YearQuarter(2020, Quarter.q3));
1755         yq -= 1;
1756         assert(yq == YearQuarter(2020, Quarter.q2));
1757     }
1758 
1759     /// Get a slice of YearQuarters
1760     @safe pure @nogc nothrow
1761     version(mir_test)
1762     unittest {
1763         import mir.ndslice.topology: iota;
1764 
1765         static immutable result1 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q2), YearQuarter(2020, Quarter.q3), YearQuarter(2020, Quarter.q4)];
1766         static immutable result2 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q3), YearQuarter(2021, Quarter.q1), YearQuarter(2021, Quarter.q3)];
1767 
1768         auto yq = YearQuarter(2020, Quarter.q1);
1769 
1770         auto x = yq + 4.iota!uint;
1771         assert(x == result1);
1772 
1773         // every other quarter
1774         auto y = yq + iota!uint([4], 0, 2);
1775         assert(y == result2);
1776     }
1777 
1778     /++
1779         Day of the quarter this $(LREF Date) is on.
1780       +/
1781     @property int dayOfQuarter(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc
1782     {
1783         if (quarter >= Quarter.q1 && quarter <= Quarter.q4)
1784         {
1785             immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap;
1786             auto quarterIndex = quarter - Quarter.q1;
1787             immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
1788             auto monthIndex = month(assumePeriodMonth) - Month.jan;
1789 
1790             return lastDay[monthIndex] - lastDayQuarter[quarterIndex] + day(assumePeriodDay);
1791         }
1792         assert(0, "Invalid quarter.");
1793     }
1794 
1795     /// ditto
1796     @property int dayOfQuarter(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1797     {
1798         return dayOfQuarter(assumePeriod, assumePeriod);
1799     }
1800 
1801     ///
1802     version (mir_test)
1803     @safe unittest
1804     {
1805         assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter == 1);
1806         assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31);
1807         assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 90);
1808 
1809         assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31);
1810         assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 91);
1811 
1812         assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter == 1);
1813         assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31);
1814         assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.end) == 92);
1815     }
1816 
1817     /++
1818         Day of the year this $(LREF Date) is on.
1819       +/
1820     @property int dayOfYear(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc
1821     {
1822         if (quarter >= Quarter.q1 && quarter <= Quarter.q4)
1823         {
1824             immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap;
1825             auto quarterIndex = quarter - Quarter.q1;
1826 
1827             return lastDayQuarter[quarterIndex] + dayOfQuarter(assumePeriodMonth, assumePeriodDay);
1828         }
1829         assert(0, "Invalid quarter.");
1830     }
1831     
1832     /// ditto
1833     @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1834     {
1835         return dayOfYear(assumePeriod, assumePeriod);
1836     }
1837 
1838     ///
1839     version (mir_test)
1840     @safe unittest
1841     {
1842         assert(YearQuarter(1999, cast(Quarter) 1).dayOfYear == 1);
1843         assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear == 274);
1844         assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 304);
1845         assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 365);
1846         assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear == 275);
1847         assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 305);
1848         assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 366);
1849     }
1850 
1851     /++
1852         Whether this $(LREF Date) is in a leap year.
1853      +/
1854     @property bool isLeapYear() const @safe pure nothrow @nogc
1855     {
1856         return yearIsLeapYear(year);
1857     }
1858 
1859     ///
1860     version (mir_test)
1861     @safe unittest
1862     {
1863         assert(YearQuarter(1999, cast(Quarter) 4).isLeapYear == false);
1864         assert(YearQuarter(2000, cast(Quarter) 4).isLeapYear == true);
1865     }
1866 
1867     /++
1868         The last day in the month that this $(LREF Date) is in.
1869       +/
1870     @property ubyte daysInMonth(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc
1871     {
1872         return maxDay(year, month(assumePeriod));
1873     }
1874 
1875     ///
1876     version(mir_test)
1877     @safe unittest
1878     {
1879         auto yq = YearQuarter(2020, Quarter.q3);
1880         assert(yq.daysInMonth == 31);
1881         assert(yq.daysInMonth(AssumePeriod.end) == 30);
1882     }
1883 
1884     /++
1885         Whether the current year is a date in A.D.
1886       +/
1887     @property bool isAD() const @safe pure nothrow @nogc
1888     {
1889         return year > 0;
1890     }
1891 
1892     ///
1893     version(mir_test)
1894     @safe unittest
1895     {
1896         assert(YearQuarter(2020, Quarter.q1).isAD == true);
1897     }
1898 }
1899 
1900 /++
1901     Represents a date in the
1902     $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic
1903     Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years
1904     are A.D. Non-positive years are B.C.
1905 
1906     Year, month, and day are kept separately internally so that $(D Date) is
1907     optimized for calendar-based operations.
1908 
1909     $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian
1910     leap year calculations for its entire length. As per
1911     $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as
1912     year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C.
1913     as a positive integer with 1 B.C. being the year prior to 1 A.D.
1914 
1915     Year 0 is a leap year.
1916  +/
1917 // extern(C++, "boost", "gregorian")
1918 // extern(C++, class)
1919 extern(C++, "mir")
1920 @serdeProxy!YearMonthDay
1921 struct Date
1922 {
1923 extern(D):
1924 public:
1925 
1926     private enum _julianShift = 1_721_425;
1927 
1928     ///
1929     uint toHash() @safe pure nothrow @nogc const scope
1930     {
1931         return _dayNumber;
1932     }
1933 
1934     /++
1935         Throws:
1936             $(LREF DateTimeException) if the resulting
1937             $(LREF Date) would not be valid.
1938 
1939         Params:
1940             _year  = Year of the Gregorian Calendar. Positive values are A.D.
1941                     Non-positive values are B.C. with year 0 being the year
1942                     prior to 1 A.D.
1943             _month = Month of the year (January is 1).
1944             _day   = Day of the month.
1945      +/
1946     pragma(inline, false)
1947     static Date trustedCreate(int _year, int _month, int _day) @safe pure @nogc nothrow
1948     {
1949         Date ret;
1950         immutable int[] lastDay = yearIsLeapYear(_year) ? lastDayLeap : lastDayNonLeap;
1951         auto monthIndex = _month - Month.jan;
1952 
1953         const dayOfYear = lastDay[monthIndex] + _day;
1954 
1955         if (_month >= Month.jan && _month <= Month.dec) {} else
1956             assert(0, "Invalid month.");
1957         if (_year > 0)
1958         {
1959             if (_year == 1)
1960             {
1961                 ret._dayNumber = dayOfYear;
1962                 goto R;
1963             }
1964 
1965             int years = _year - 1;
1966             auto days = (years / 400) * daysIn400Years;
1967             years %= 400;
1968 
1969             days += (years / 100) * daysIn100Years;
1970             years %= 100;
1971 
1972             days += (years / 4) * daysIn4Years;
1973             years %= 4;
1974 
1975             days += years * daysInYear;
1976 
1977             days += dayOfYear;
1978 
1979             ret._dayNumber = days;
1980         }
1981         else if (_year == 0)
1982         {
1983             ret._dayNumber = dayOfYear - daysInLeapYear;
1984         }
1985         else
1986         {
1987             int years = _year;
1988             auto days = (years / 400) * daysIn400Years;
1989             years %= 400;
1990 
1991             days += (years / 100) * daysIn100Years;
1992             years %= 100;
1993 
1994             days += (years / 4) * daysIn4Years;
1995             years %= 4;
1996 
1997             if (years < 0)
1998             {
1999                 days -= daysInLeapYear;
2000                 ++years;
2001 
2002                 days += years * daysInYear;
2003 
2004                 days -= daysInYear - dayOfYear;
2005             }
2006             else
2007                 days -= daysInLeapYear - dayOfYear;
2008 
2009             ret._dayNumber = days;
2010         }
2011     R:
2012         ret._dayNumber -= 1;
2013         return ret;
2014     }
2015 
2016     ///
2017     Timestamp timestamp() @safe pure nothrow @nogc const @property
2018     {
2019         return yearMonthDay.timestamp;
2020     }
2021 
2022     ///
2023     version(mir_test)
2024     @safe unittest
2025     {
2026         import mir.timestamp;
2027         auto d1 = Date(2020, Month.may, 15);
2028         auto ts2 = d1.timestamp;
2029     }
2030 
2031     version(D_Exceptions)
2032     ///
2033     this(Timestamp timestamp) @safe pure @nogc
2034     {
2035         if (timestamp.precision != Timestamp.Precision.day)
2036         {
2037             static immutable exc = new Exception("Date: invalid timestamp precision");
2038             { import mir.exception : toMutable; throw exc.toMutable; }
2039         }
2040         with(timestamp) this(year, cast(Month)month, day);
2041     }
2042 
2043     ///
2044     version(mir_test)
2045     @safe unittest
2046     {
2047         import mir.timestamp;
2048         auto ts = Date(2020, Month.may, 15).timestamp;
2049         auto d2 = Date(ts);
2050     }
2051 
2052     version(D_Exceptions)
2053     ///
2054     this(scope const(char)[] str) @safe pure @nogc
2055     {
2056         this = fromString(str);
2057     }
2058 
2059     ///
2060     version(mir_test)
2061     @safe unittest
2062     {
2063         auto d = Date("2020-12-31");
2064     }
2065 
2066     version(D_Exceptions)
2067     ///
2068     this(YearMonthDay ymd) @safe pure @nogc
2069     {
2070         with(ymd) this(year, month, day);
2071     }
2072 
2073     ///
2074     version(mir_test)
2075     @safe unittest
2076     {
2077         auto d = Date(YearMonthDay(2020, Month.may, 31));
2078     }
2079 
2080     version(D_Exceptions)
2081     ///
2082     this(YearQuarter yq, AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) @safe pure @nogc
2083     {
2084         with(yq) this(year, month(assumePeriodMonth), day(assumePeriodDay));
2085     }
2086 
2087     version(D_Exceptions)
2088     ///
2089     this(YearQuarter yq, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc
2090     {
2091         this(yq, assumePeriod, assumePeriod);
2092     }
2093 
2094     ///
2095     version(mir_test)
2096     @safe unittest
2097     {
2098         auto d1 = Date(YearQuarter(2020, Quarter.q2));
2099         auto d2 = Date(YearQuarter(2020, Quarter.q2), AssumePeriod.end);
2100     }
2101 
2102     version(D_Exceptions)
2103     ///
2104     this(YearMonth ym, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc nothrow
2105     {
2106         with(ym) this = trustedCreate(year, month, day(assumePeriod));
2107     }
2108 
2109     ///
2110     version(mir_test)
2111     @safe unittest
2112     {
2113         auto d1 = Date(YearMonth(2020, Month.may));
2114         auto d2 = Date(YearMonth(2020, Month.may), AssumePeriod.end);
2115     }
2116 
2117     version(D_Exceptions)
2118     ///
2119     this(int _year, int _month, int _day) @safe pure @nogc
2120     {
2121         if (!valid!"months"(_month))
2122             { import mir.exception : toMutable; throw InvalidMonth.toMutable; }
2123         if (!valid!"days"(_year, cast(Month) _month, _day))
2124             { import mir.exception : toMutable; throw InvalidDay.toMutable; }
2125         this = trustedCreate(_year, _month, _day);
2126     }
2127 
2128     ///
2129     static bool fromYMD(int _year, int _month, int _day, out Date value) @safe pure nothrow @nogc
2130     {
2131         if (valid!"months"(_month) && valid!"days"(_year, cast(Month) _month, _day))
2132         {
2133             value = trustedCreate(_year, _month, _day);
2134             return true;
2135         }
2136         return false;
2137     }
2138 
2139     version (mir_test)
2140     @safe unittest
2141     {
2142         import std.exception : assertNotThrown;
2143         // assert(Date(0, 12, 31) == Date.init);
2144 
2145         // Test A.D.
2146         assertThrown!DateTimeException(Date(1, 0, 1));
2147         assertThrown!DateTimeException(Date(1, 1, 0));
2148         assertThrown!DateTimeException(Date(1999, 13, 1));
2149         assertThrown!DateTimeException(Date(1999, 1, 32));
2150         assertThrown!DateTimeException(Date(1999, 2, 29));
2151         assertThrown!DateTimeException(Date(2000, 2, 30));
2152         assertThrown!DateTimeException(Date(1999, 3, 32));
2153         assertThrown!DateTimeException(Date(1999, 4, 31));
2154         assertThrown!DateTimeException(Date(1999, 5, 32));
2155         assertThrown!DateTimeException(Date(1999, 6, 31));
2156         assertThrown!DateTimeException(Date(1999, 7, 32));
2157         assertThrown!DateTimeException(Date(1999, 8, 32));
2158         assertThrown!DateTimeException(Date(1999, 9, 31));
2159         assertThrown!DateTimeException(Date(1999, 10, 32));
2160         assertThrown!DateTimeException(Date(1999, 11, 31));
2161         assertThrown!DateTimeException(Date(1999, 12, 32));
2162 
2163         assertNotThrown!DateTimeException(Date(1999, 1, 31));
2164         assertNotThrown!DateTimeException(Date(1999, 2, 28));
2165         assertNotThrown!DateTimeException(Date(2000, 2, 29));
2166         assertNotThrown!DateTimeException(Date(1999, 3, 31));
2167         assertNotThrown!DateTimeException(Date(1999, 4, 30));
2168         assertNotThrown!DateTimeException(Date(1999, 5, 31));
2169         assertNotThrown!DateTimeException(Date(1999, 6, 30));
2170         assertNotThrown!DateTimeException(Date(1999, 7, 31));
2171         assertNotThrown!DateTimeException(Date(1999, 8, 31));
2172         assertNotThrown!DateTimeException(Date(1999, 9, 30));
2173         assertNotThrown!DateTimeException(Date(1999, 10, 31));
2174         assertNotThrown!DateTimeException(Date(1999, 11, 30));
2175         assertNotThrown!DateTimeException(Date(1999, 12, 31));
2176 
2177         // Test B.C.
2178         assertNotThrown!DateTimeException(Date(0, 1, 1));
2179         assertNotThrown!DateTimeException(Date(-1, 1, 1));
2180         assertNotThrown!DateTimeException(Date(-1, 12, 31));
2181         assertNotThrown!DateTimeException(Date(-1, 2, 28));
2182         assertNotThrown!DateTimeException(Date(-4, 2, 29));
2183 
2184         assertThrown!DateTimeException(Date(-1, 2, 29));
2185         assertThrown!DateTimeException(Date(-2, 2, 29));
2186         assertThrown!DateTimeException(Date(-3, 2, 29));
2187     }
2188 
2189 
2190     /++
2191         Params:
2192             day = Julian day.
2193      +/
2194     deprecated("Use `fromDayNumber` adjusted by -1_721_426")
2195     this(int day) @safe pure nothrow @nogc
2196     {
2197         _dayNumber = day - (1 + _julianShift);
2198     }
2199 
2200     version (mir_test)
2201     @safe unittest
2202     {
2203         import std.range : chain;
2204 
2205         // Test A.D.
2206         // foreach (gd; chain(testGregDaysBC, testGregDaysAD))
2207         //     assert(Date(gd.day) == gd.date);
2208     }
2209 
2210 
2211     /++
2212         Compares this $(LREF Date) with the given $(LREF Date).
2213 
2214         Returns:
2215             $(BOOKTABLE,
2216             $(TR $(TD this &lt; rhs) $(TD &lt; 0))
2217             $(TR $(TD this == rhs) $(TD 0))
2218             $(TR $(TD this &gt; rhs) $(TD &gt; 0))
2219             )
2220      +/
2221     int opCmp(Date rhs) const @safe pure nothrow @nogc
2222     {
2223         return this._dayNumber - rhs._dayNumber;
2224     }
2225 
2226     version (mir_test)
2227     @safe unittest
2228     {
2229         // Test A.D.
2230         // assert(Date(0, 12, 31).opCmp(Date.init) == 0);
2231 
2232         assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0);
2233         assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0);
2234         assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0);
2235 
2236         assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0);
2237         assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0);
2238 
2239         assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0);
2240 
2241         assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0);
2242         assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0);
2243         assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0);
2244         assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0);
2245         assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0);
2246         assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0);
2247 
2248         assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0);
2249         assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
2250         assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0);
2251         assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0);
2252         assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0);
2253         assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
2254 
2255         // Test B.C.
2256         assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0);
2257         assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0);
2258         assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0);
2259         assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0);
2260 
2261         assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0);
2262         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0);
2263 
2264         assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0);
2265 
2266         assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0);
2267         assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0);
2268         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0);
2269         assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0);
2270         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
2271         assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0);
2272 
2273         assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0);
2274         assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0);
2275         assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
2276         assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0);
2277         assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0);
2278         assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0);
2279 
2280         // Test Both
2281         assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0);
2282         assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0);
2283 
2284         assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0);
2285         assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0);
2286 
2287         assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0);
2288         assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0);
2289 
2290         assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0);
2291         assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0);
2292 
2293         assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0);
2294         assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0);
2295 
2296         auto date = Date(1999, 7, 6);
2297         const cdate = Date(1999, 7, 6);
2298         immutable idate = Date(1999, 7, 6);
2299         assert(date.opCmp(date) == 0);
2300         assert(date.opCmp(cdate) == 0);
2301         assert(date.opCmp(idate) == 0);
2302         assert(cdate.opCmp(date) == 0);
2303         assert(cdate.opCmp(cdate) == 0);
2304         assert(cdate.opCmp(idate) == 0);
2305         assert(idate.opCmp(date) == 0);
2306         assert(idate.opCmp(cdate) == 0);
2307         assert(idate.opCmp(idate) == 0);
2308     }
2309 
2310     /++
2311         Day of the week this $(LREF Date) is on.
2312       +/
2313     @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc
2314     {
2315         return getDayOfWeek(_dayNumber);
2316     }
2317 
2318     version (mir_test)
2319     @safe unittest
2320     {
2321         const cdate = Date(1999, 7, 6);
2322         immutable idate = Date(1999, 7, 6);
2323         assert(cdate.dayOfWeek == DayOfWeek.tue);
2324         static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun));
2325         assert(idate.dayOfWeek == DayOfWeek.tue);
2326         static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun));
2327     }
2328 
2329     /++
2330     Params:
2331         dayNumber = Day Of Gregorian Calendar Minus One
2332     +/
2333     static Date fromDayNumber(int dayNumber) @safe pure nothrow @nogc
2334     {
2335         Date date;
2336         date._dayNumber = dayNumber;
2337         return date;
2338     }
2339 
2340     /++
2341     Returns;
2342         Day Of Gregorian Calendar Minus One
2343     +/
2344     int dayNumber() @safe pure nothrow @nogc const @property
2345     {
2346         return _dayNumber;
2347     }
2348 
2349     /++
2350         The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
2351      +/
2352     @property int dayOfGregorianCal() const @safe pure nothrow @nogc
2353     {
2354         return _dayNumber + 1;
2355     }
2356 
2357     ///
2358     version (mir_test)
2359     @safe unittest
2360     {
2361         assert(Date(1, 1, 1).dayOfGregorianCal == 1);
2362         assert(Date(1, 12, 31).dayOfGregorianCal == 365);
2363         assert(Date(2, 1, 1).dayOfGregorianCal == 366);
2364 
2365         assert(Date(0, 12, 31).dayOfGregorianCal == 0);
2366         assert(Date(0, 1, 1).dayOfGregorianCal == -365);
2367         assert(Date(-1, 12, 31).dayOfGregorianCal == -366);
2368 
2369         assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120);
2370         assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137);
2371     }
2372 
2373     version (mir_test)
2374     @safe unittest
2375     {
2376         import std.range : chain;
2377 
2378         foreach (gd; chain(testGregDaysBC, testGregDaysAD))
2379             assert(gd.date.dayOfGregorianCal == gd.day);
2380 
2381         auto date = Date(1999, 7, 6);
2382         const cdate = Date(1999, 7, 6);
2383         immutable idate = Date(1999, 7, 6);
2384         assert(date.dayOfGregorianCal == 729_941);
2385         assert(cdate.dayOfGregorianCal == 729_941);
2386         assert(idate.dayOfGregorianCal == 729_941);
2387     }
2388 
2389     /++
2390         The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
2391 
2392         Params:
2393             day = The day of the Gregorian Calendar to set this $(LREF Date) to.
2394         
2395         Note:
2396             Zero value corresponds to 
2397      +/
2398     @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc
2399     {
2400         _dayNumber = day - 1;
2401     }
2402 
2403     ///
2404     version (mir_test)
2405     @safe unittest
2406     {
2407         import mir.test;
2408         auto date = Date.init;
2409         assert(date == Date(1, 1, 1));
2410 
2411         date.dayOfGregorianCal = 365;
2412         assert(date == Date(1, 12, 31));
2413 
2414         date.dayOfGregorianCal = 366;
2415         assert(date == Date(2, 1, 1));
2416 
2417         date.dayOfGregorianCal = 0;
2418         assert(date == Date(0, 12, 31));
2419 
2420         date.dayOfGregorianCal = -365;
2421         assert(date == Date(-0, 1, 1));
2422 
2423         date.dayOfGregorianCal = -366;
2424         assert(date == Date(-1, 12, 31));
2425 
2426         date.dayOfGregorianCal = 730_120;
2427         assert(date == Date(2000, 1, 1));
2428 
2429         date.dayOfGregorianCal = 734_137;
2430         assert(date == Date(2010, 12, 31));
2431     }
2432 
2433     version (mir_test)
2434     @safe unittest
2435     {
2436         auto date = Date(1999, 7, 6);
2437         const cdate = Date(1999, 7, 6);
2438         immutable idate = Date(1999, 7, 6);
2439         date.dayOfGregorianCal = 187;
2440         assert(date.dayOfGregorianCal == 187);
2441         static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187));
2442         static assert(!__traits(compiles, idate.dayOfGregorianCal = 187));
2443     }
2444 
2445     private enum uint _startDict = Date(1900, 1, 1)._dayNumber; // [
2446     private enum uint _endDict = Date(2040, 1, 1)._dayNumber; // )
2447     static immutable _dictYMD = ()
2448     {
2449         YearMonthDay[Date._endDict - Date._startDict] dict;
2450         foreach (uint i; 0 .. dict.length)
2451             dict[i] = Date.fromDayNumber(i + Date._startDict).yearMonthDayImpl;
2452         return dict;
2453     }();
2454 
2455     ///
2456     YearMonthDay yearMonthDay() const @safe pure nothrow @nogc @property
2457     {
2458         uint day = _dayNumber;
2459         if (day < _endDict)
2460         {
2461             import mir.checkedint: subu;
2462             bool overflow;
2463             auto index = subu(day, _startDict, overflow);
2464             if (!overflow)
2465                 return _dictYMD[index];
2466         }
2467         return yearMonthDayImpl;
2468     }
2469 
2470     ///
2471     YearQuarter yearQuarter() const @safe pure nothrow @nogc @property
2472     {
2473         uint day = _dayNumber;
2474         if (day < _endDict)
2475         {
2476             return yearMonthDay().YearQuarter;
2477         }
2478         return yearQuarterImpl;
2479     }
2480 
2481     ///
2482     version(mir_test)
2483     @safe unittest
2484     {
2485         auto d = Date(2020, Month.may, 31);
2486         auto yq = d.yearQuarter;
2487         assert(yq.year == 2020);
2488         assert(yq.quarter == Quarter.q2);
2489     }
2490 
2491     //
2492     version(mir_test)
2493     @safe unittest
2494     {
2495         auto d = Date(2050, Month.dec, 31);
2496         auto yq = d.yearQuarter;
2497         assert(yq.year == 2050);
2498         assert(yq.quarter == Quarter.q4);
2499     }
2500 
2501     ///
2502     short year() const @safe pure nothrow @nogc @property
2503     {
2504         return yearQuarter.year;
2505     }
2506 
2507     ///
2508     Quarter quarter() const @safe pure nothrow @nogc @property
2509     {
2510         return yearQuarter.quarter;
2511     }
2512 
2513     ///
2514     Month month() const @safe pure nothrow @nogc @property
2515     {
2516         return yearMonthDay.month;
2517     }
2518 
2519     ///
2520     ubyte day() const @safe pure nothrow @nogc @property
2521     {
2522         return yearMonthDay.day;
2523     }
2524 
2525     ///
2526     version(mir_test)
2527     @safe unittest
2528     {
2529         auto d = Date(2020, Month.may, 31);
2530         assert(d.year == 2020);
2531         assert(d.quarter == Quarter.q2);
2532         assert(d.month == Month.may);
2533         assert(d.day == 31);
2534     }
2535 
2536     pragma(inline, false)
2537     YearMonthDay yearMonthDayImpl() const @safe pure nothrow @nogc @property
2538     {
2539         YearMonthDay ymd;
2540         int days = dayOfGregorianCal;
2541         with(ymd)
2542         if (days > 0)
2543         {
2544             int years = (days / daysIn400Years) * 400 + 1;
2545             days %= daysIn400Years;
2546 
2547             {
2548                 immutable tempYears = days / daysIn100Years;
2549 
2550                 if (tempYears == 4)
2551                 {
2552                     years += 300;
2553                     days -= daysIn100Years * 3;
2554                 }
2555                 else
2556                 {
2557                     years += tempYears * 100;
2558                     days %= daysIn100Years;
2559                 }
2560             }
2561 
2562             years += (days / daysIn4Years) * 4;
2563             days %= daysIn4Years;
2564 
2565             {
2566                 immutable tempYears = days / daysInYear;
2567 
2568                 if (tempYears == 4)
2569                 {
2570                     years += 3;
2571                     days -= daysInYear * 3;
2572                 }
2573                 else
2574                 {
2575                     years += tempYears;
2576                     days %= daysInYear;
2577                 }
2578             }
2579 
2580             if (days == 0)
2581             {
2582                 year = cast(short)(years - 1);
2583                 month = Month.dec;
2584                 day = 31;
2585             }
2586             else
2587             {
2588                 year = cast(short) years;
2589 
2590                 setDayOfYear(days);
2591             }
2592         }
2593         else if (days <= 0 && -days < daysInLeapYear)
2594         {
2595             year = 0;
2596 
2597             setDayOfYear(daysInLeapYear + days);
2598         }
2599         else
2600         {
2601             days += daysInLeapYear - 1;
2602             int years = (days / daysIn400Years) * 400 - 1;
2603             days %= daysIn400Years;
2604 
2605             {
2606                 immutable tempYears = days / daysIn100Years;
2607 
2608                 if (tempYears == -4)
2609                 {
2610                     years -= 300;
2611                     days += daysIn100Years * 3;
2612                 }
2613                 else
2614                 {
2615                     years += tempYears * 100;
2616                     days %= daysIn100Years;
2617                 }
2618             }
2619 
2620             years += (days / daysIn4Years) * 4;
2621             days %= daysIn4Years;
2622 
2623             {
2624                 immutable tempYears = days / daysInYear;
2625 
2626                 if (tempYears == -4)
2627                 {
2628                     years -= 3;
2629                     days += daysInYear * 3;
2630                 }
2631                 else
2632                 {
2633                     years += tempYears;
2634                     days %= daysInYear;
2635                 }
2636             }
2637 
2638             if (days == 0)
2639             {
2640                 year = cast(short)(years + 1);
2641                 month = Month.jan;
2642                 day = 1;
2643             }
2644             else
2645             {
2646                 year = cast(short) years;
2647                 immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1;
2648 
2649                 setDayOfYear(newDoY);
2650             }
2651         }
2652         return ymd;
2653     }
2654 
2655 
2656 
2657     pragma(inline, false)
2658     YearQuarter yearQuarterImpl() const @safe pure nothrow @nogc @property
2659     {
2660         YearQuarter yq;
2661         int days = dayOfGregorianCal;
2662         with(yq)
2663         if (days > 0)
2664         {
2665             int years = (days / daysIn400Years) * 400 + 1;
2666             days %= daysIn400Years;
2667 
2668             {
2669                 immutable tempYears = days / daysIn100Years;
2670 
2671                 if (tempYears == 4)
2672                 {
2673                     years += 300;
2674                     days -= daysIn100Years * 3;
2675                 }
2676                 else
2677                 {
2678                     years += tempYears * 100;
2679                     days %= daysIn100Years;
2680                 }
2681             }
2682 
2683             years += (days / daysIn4Years) * 4;
2684             days %= daysIn4Years;
2685 
2686             {
2687                 immutable tempYears = days / daysInYear;
2688 
2689                 if (tempYears == 4)
2690                 {
2691                     years += 3;
2692                     days -= daysInYear * 3;
2693                 }
2694                 else
2695                 {
2696                     years += tempYears;
2697                     days %= daysInYear;
2698                 }
2699             }
2700 
2701             if (days == 0)
2702             {
2703                 year = cast(short)(years - 1);
2704                 quarter = Quarter.q4;
2705             }
2706             else
2707             {
2708                 year = cast(short) years;
2709                 setQuarterOfYear(days);
2710             }
2711         }
2712         else if (days <= 0 && -days < daysInLeapYear)
2713         {
2714             year = 0;
2715 
2716             setQuarterOfYear(daysInLeapYear + days);
2717         }
2718         else
2719         {
2720             days += daysInLeapYear - 1;
2721             int years = (days / daysIn400Years) * 400 - 1;
2722             days %= daysIn400Years;
2723 
2724             {
2725                 immutable tempYears = days / daysIn100Years;
2726 
2727                 if (tempYears == -4)
2728                 {
2729                     years -= 300;
2730                     days += daysIn100Years * 3;
2731                 }
2732                 else
2733                 {
2734                     years += tempYears * 100;
2735                     days %= daysIn100Years;
2736                 }
2737             }
2738 
2739             years += (days / daysIn4Years) * 4;
2740             days %= daysIn4Years;
2741 
2742             {
2743                 immutable tempYears = days / daysInYear;
2744 
2745                 if (tempYears == -4)
2746                 {
2747                     years -= 3;
2748                     days += daysInYear * 3;
2749                 }
2750                 else
2751                 {
2752                     years += tempYears;
2753                     days %= daysInYear;
2754                 }
2755             }
2756 
2757             if (days == 0)
2758             {
2759                 year = cast(short)(years + 1);
2760                 quarter = Quarter.q2;
2761             }
2762             else
2763             {
2764                 year = cast(short) years;
2765                 immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1;
2766 
2767                 setQuarterOfYear(newDoY);
2768             }
2769         }
2770         return yq;
2771     }
2772 
2773     version(mir_test)
2774     @safe unittest
2775     {
2776         auto d = Date(2020, Month.may, 31);
2777         auto yq = d.yearQuarterImpl;
2778     }
2779 
2780     /++
2781      $(LREF Date) for the last day in the quarter that this $(LREF Date) is in.
2782     +/
2783     @property Date endOfQuarter() const @safe pure nothrow @nogc
2784     {
2785         with(yearMonthDay)
2786         {
2787             int d = _dayNumber - day;
2788             final switch (month) with(Month)
2789             {
2790                 case jan: d += maxDay(year, jan); goto case;
2791                 case feb: d += maxDay(year, feb); goto case;
2792                 case mar: d += maxDay(year, mar); break;
2793 
2794                 case apr: d += maxDay(year, apr); goto case;
2795                 case may: d += maxDay(year, may); goto case;
2796                 case jun: d += maxDay(year, jun); break;
2797 
2798                 case jul: d += maxDay(year, jul); goto case;
2799                 case aug: d += maxDay(year, aug); goto case;
2800                 case sep: d += maxDay(year, sep); break;
2801 
2802                 case oct: d += maxDay(year, oct); goto case;
2803                 case nov: d += maxDay(year, nov); goto case;
2804                 case dec: d += maxDay(year, dec); break;
2805             }
2806             return Date.fromDayNumber(d);
2807         }
2808     }
2809 
2810     ///
2811     version (mir_test)
2812     @safe unittest
2813     {
2814         assert(Date(1999, 1, 6).endOfQuarter == Date(1999, 3, 31));
2815         assert(Date(1999, 2, 7).endOfQuarter == Date(1999, 3, 31));
2816         assert(Date(2000, 2, 7).endOfQuarter == Date(2000, 3, 31));
2817         assert(Date(2000, 6, 4).endOfQuarter == Date(2000, 6, 30));
2818     }
2819 
2820     /++
2821      $(LREF Date) for the last day in the month that this $(LREF Date) is in.
2822     +/
2823     @property Date endOfMonth() const @safe pure nothrow @nogc
2824     {
2825         with(yearMonthDay)
2826             return Date.fromDayNumber(_dayNumber + maxDay(year, month) - day);
2827     }
2828 
2829     ///
2830     version (mir_test)
2831     @safe unittest
2832     {
2833         assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31));
2834         assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28));
2835         assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29));
2836         assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30));
2837     }
2838 
2839     version (mir_test)
2840     @safe unittest
2841     {
2842         // Test A.D.
2843         assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31));
2844         assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28));
2845         assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29));
2846         assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31));
2847         assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30));
2848         assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31));
2849         assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30));
2850         assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31));
2851         assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31));
2852         assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30));
2853         assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31));
2854         assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30));
2855         assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31));
2856 
2857         // Test B.C.
2858         assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31));
2859         assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28));
2860         assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29));
2861         assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31));
2862         assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30));
2863         assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31));
2864         assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30));
2865         assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31));
2866         assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31));
2867         assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30));
2868         assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31));
2869         assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30));
2870         assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31));
2871 
2872         const cdate = Date(1999, 7, 6);
2873         immutable idate = Date(1999, 7, 6);
2874         static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30)));
2875         static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30)));
2876     }
2877 
2878     ///
2879     int opBinary(string op : "-")(Date rhs) const
2880     {
2881         return _dayNumber - rhs._dayNumber;
2882     }
2883 
2884     ///
2885     Date opBinary(string op : "+")(int rhs) const
2886     {
2887         return Date.fromDayNumber(_dayNumber + rhs);
2888     }
2889 
2890     ///
2891     Date opBinaryRight(string op : "+")(int rhs) const
2892     {
2893         return Date.fromDayNumber(_dayNumber + rhs);
2894     }
2895 
2896     ///
2897     Date opBinary(string op : "-")(int rhs) const
2898     {
2899         return Date.fromDayNumber(_dayNumber - rhs);
2900     }
2901 
2902     ///
2903     ref Date opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc
2904         if (op == "+" || op == "-")
2905     {
2906         static if (op == "+")
2907            this._addDays(rhs);
2908         else
2909            this._addDays(-rhs);
2910         return this;
2911     }
2912     
2913     ///
2914     @safe pure @nogc
2915     version(mir_test)
2916     unittest {
2917         auto d = Date(2020, 1, 1);
2918         d += 2;
2919         assert(d == Date(2020, 1, 3));
2920         d -= 1;
2921         assert(d == Date(2020, 1, 2));
2922     }
2923 
2924 
2925     /// Get a slice of Dates
2926     @safe pure @nogc
2927     version(mir_test)
2928     unittest {
2929         import mir.ndslice.topology: iota, map;
2930 
2931         static immutable result1 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 2), Date(2020, Month.mar, 3), Date(2020, Month.mar, 4)];
2932         static immutable result2 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 3), Date(2020, Month.mar, 5), Date(2020, Month.mar, 7)];
2933         static immutable result3 = [Date(2020, Month.mar, 1), Date(2020, Month.apr, 1), Date(2020, Month.may, 1), Date(2020, Month.jun, 1)];
2934         static immutable result4 = [Date(2020, Month.mar, 1), Date(2020, Month.jun, 1), Date(2020, Month.sep, 1), Date(2020, Month.dec, 1)];
2935         static immutable result5 = [Date(2020, Month.mar, 1), Date(2021, Month.mar, 1), Date(2022, Month.mar, 1), Date(2023, Month.mar, 1)];
2936 
2937         auto d = Date(2020, Month.mar, 1);
2938 
2939         auto x = d + 4.iota!uint;
2940         assert(x == result1);
2941 
2942         // every other date
2943         auto y = d + iota!uint([4], 0, 2);
2944         assert(y == result2);
2945 
2946         // every month
2947         auto z = (d.YearMonth + 4.iota!uint).map!Date;
2948         assert(z == result3);
2949 
2950         // every quarter
2951         auto a = (d.YearQuarter + 4.iota!uint).map!(a => a.Date(AssumePeriod.end, AssumePeriod.begin));
2952         assert(a == result4);
2953 
2954         // every year
2955         auto b = (d.year + 4.iota!uint).map!(a => YearMonthDay(cast(short) a, Month.mar, 1).Date);
2956         assert(b == result5);
2957     }
2958 
2959     const nothrow @nogc pure @safe
2960     Date add(string units)(long amount, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
2961     {
2962         with(yearMonthDay.add!units(amount)) return trustedCreate(year, month, day);
2963     }
2964 
2965     /++
2966         The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this
2967         $(LREF Date) at noon (since the Julian day changes at noon).
2968       +/
2969     @property int julianDay() const @safe pure nothrow @nogc
2970     {
2971         return _dayNumber + (1 + _julianShift);
2972     }
2973 
2974     version (mir_test)
2975     @safe unittest
2976     {
2977         assert(Date(-4713, 11, 24).julianDay == 0);
2978         assert(Date(0, 12, 31).julianDay == _julianShift);
2979         assert(Date(1, 1, 1).julianDay == 1_721_426);
2980         assert(Date(1582, 10, 15).julianDay == 2_299_161);
2981         assert(Date(1858, 11, 17).julianDay == 2_400_001);
2982         assert(Date(1982, 1, 4).julianDay == 2_444_974);
2983         assert(Date(1996, 3, 31).julianDay == 2_450_174);
2984         assert(Date(2010, 8, 24).julianDay == 2_455_433);
2985 
2986         const cdate = Date(1999, 7, 6);
2987         immutable idate = Date(1999, 7, 6);
2988         assert(cdate.julianDay == 2_451_366);
2989         assert(idate.julianDay == 2_451_366);
2990     }
2991 
2992 
2993     /++
2994         The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for
2995         any time on this date (since, the modified Julian day changes at
2996         midnight).
2997       +/
2998     @property long modJulianDay() const @safe pure nothrow @nogc
2999     {
3000         return julianDay - 2_400_001;
3001     }
3002 
3003     version (mir_test)
3004     @safe unittest
3005     {
3006         assert(Date(1858, 11, 17).modJulianDay == 0);
3007         assert(Date(2010, 8, 24).modJulianDay == 55_432);
3008 
3009         const cdate = Date(1999, 7, 6);
3010         immutable idate = Date(1999, 7, 6);
3011         assert(cdate.modJulianDay == 51_365);
3012         assert(idate.modJulianDay == 51_365);
3013     }
3014 
3015     version(D_BetterC){} else
3016     private string toStringImpl(alias fun)() const @safe pure nothrow
3017     {
3018         import mir.appender: UnsafeArrayBuffer;
3019         char[16] buffer = void;
3020         auto w = UnsafeArrayBuffer!char(buffer);
3021         fun(w);
3022         return w.data.idup;
3023     }
3024 
3025     version(D_BetterC){} else
3026     /++
3027         Converts this $(LREF Date) to a string with the format `YYYYMMDD`.
3028         If `writer` is set, the resulting string will be written directly
3029         to it.
3030 
3031         Returns:
3032             A `string` when not using an output range; `void` otherwise.
3033       +/
3034     string toISOString() const @safe pure nothrow
3035     {
3036         return toStringImpl!toISOString;
3037     }
3038 
3039     ///
3040     version (mir_test)
3041     @safe unittest
3042     {
3043         assert(Date.init.toISOString == "null");
3044         assert(Date(2010, 7, 4).toISOString == "20100704");
3045         assert(Date(1998, 12, 25).toISOString == "19981225");
3046         assert(Date(0, 1, 5).toISOString == "00000105");
3047         assert(Date(-4, 1, 5).toISOString == "-00040105", Date(-4, 1, 5).toISOString());
3048     }
3049 
3050     version (mir_test)
3051     @safe unittest
3052     {
3053         // Test A.D.
3054         assert(Date(9, 12, 4).toISOString == "00091204");
3055         assert(Date(99, 12, 4).toISOString == "00991204");
3056         assert(Date(999, 12, 4).toISOString == "09991204");
3057         assert(Date(9999, 7, 4).toISOString == "99990704");
3058         assert(Date(10000, 10, 20).toISOString == "+100001020");
3059 
3060         // Test B.C.
3061         assert(Date(0, 12, 4).toISOString == "00001204");
3062         assert(Date(-9, 12, 4).toISOString == "-00091204");
3063         assert(Date(-99, 12, 4).toISOString == "-00991204");
3064         assert(Date(-999, 12, 4).toISOString == "-09991204");
3065         assert(Date(-9999, 7, 4).toISOString == "-99990704");
3066         assert(Date(-10000, 10, 20).toISOString == "-100001020");
3067 
3068         const cdate = Date(1999, 7, 6);
3069         immutable idate = Date(1999, 7, 6);
3070         assert(cdate.toISOString == "19990706");
3071         assert(idate.toISOString == "19990706");
3072     }
3073 
3074     /// ditto
3075     void toISOString(W)(scope ref W w) const scope
3076         if (isOutputRange!(W, char))
3077     {
3078         import mir.format: printZeroPad;
3079         if(this == Date.init)
3080         {
3081             w.put("null");
3082             return;
3083         }
3084         with(yearMonthDay)
3085         {
3086             if (year >= 10_000)
3087                 w.put('+');
3088             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
3089             w.printZeroPad(cast(uint)month, 2);
3090             w.printZeroPad(day, 2);
3091         }
3092     }
3093 
3094     version (mir_test)
3095     @safe unittest
3096     {
3097         auto date = Date(1999, 7, 6);
3098         const cdate = Date(1999, 7, 6);
3099         immutable idate = Date(1999, 7, 6);
3100         assert(date.toString);
3101         assert(cdate.toString);
3102         assert(idate.toString);
3103     }
3104 
3105     version(D_BetterC){} else
3106     /++
3107     Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`.
3108     If `writer` is set, the resulting string will be written directly
3109     to it.
3110 
3111     Returns:
3112         A `string` when not using an output range; `void` otherwise.
3113       +/
3114     string toISOExtString() const @safe pure nothrow
3115     {
3116         return toStringImpl!toISOExtString;
3117     }
3118 
3119     ///ditto
3120     alias toString = toISOExtString;
3121 
3122     ///
3123     version (mir_test)
3124     @safe unittest
3125     {
3126         assert(Date.init.toISOExtString == "null");
3127         assert(Date(2010, 7, 4).toISOExtString == "2010-07-04");
3128         assert(Date(1998, 12, 25).toISOExtString == "1998-12-25");
3129         assert(Date(0, 1, 5).toISOExtString == "0000-01-05");
3130         assert(Date(-4, 1, 5).toISOExtString == "-0004-01-05");
3131     }
3132 
3133     version (mir_test)
3134     @safe pure unittest
3135     {
3136         import std.array : appender;
3137 
3138         auto w = appender!(char[])();
3139         Date(2010, 7, 4).toISOString(w);
3140         assert(w.data == "20100704");
3141         w.clear();
3142         Date(1998, 12, 25).toISOString(w);
3143         assert(w.data == "19981225");
3144     }
3145 
3146     version (mir_test)
3147     @safe unittest
3148     {
3149         // Test A.D.
3150         assert(Date(9, 12, 4).toISOExtString == "0009-12-04");
3151         assert(Date(99, 12, 4).toISOExtString == "0099-12-04");
3152         assert(Date(999, 12, 4).toISOExtString == "0999-12-04");
3153         assert(Date(9999, 7, 4).toISOExtString == "9999-07-04");
3154         assert(Date(10000, 10, 20).toISOExtString == "+10000-10-20");
3155 
3156         // Test B.C.
3157         assert(Date(0, 12, 4).toISOExtString == "0000-12-04");
3158         assert(Date(-9, 12, 4).toISOExtString == "-0009-12-04");
3159         assert(Date(-99, 12, 4).toISOExtString == "-0099-12-04");
3160         assert(Date(-999, 12, 4).toISOExtString == "-0999-12-04");
3161         assert(Date(-9999, 7, 4).toISOExtString == "-9999-07-04");
3162         assert(Date(-10000, 10, 20).toISOExtString == "-10000-10-20");
3163 
3164         const cdate = Date(1999, 7, 6);
3165         immutable idate = Date(1999, 7, 6);
3166         assert(cdate.toISOExtString == "1999-07-06");
3167         assert(idate.toISOExtString == "1999-07-06");
3168     }
3169 
3170     /// ditto
3171     void toISOExtString(W)(scope ref W w) const scope
3172         if (isOutputRange!(W, char))
3173     {
3174         import mir.format: printZeroPad;
3175         if(this == Date.init)
3176         {
3177             w.put("null");
3178             return;
3179         }
3180         with(yearMonthDay)
3181         {
3182             if (year >= 10_000)
3183                 w.put('+');
3184             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
3185             w.put('-');
3186             w.printZeroPad(cast(uint)month, 2);
3187             w.put('-');
3188             w.printZeroPad(day, 2);
3189         }
3190     }
3191 
3192     version (mir_test)
3193     @safe pure unittest
3194     {
3195         import std.array : appender;
3196 
3197         auto w = appender!(char[])();
3198         Date(2010, 7, 4).toISOExtString(w);
3199         assert(w.data == "2010-07-04");
3200         w.clear();
3201         Date(-4, 1, 5).toISOExtString(w);
3202         assert(w.data == "-0004-01-05");
3203     }
3204 
3205     version(D_BetterC){} else
3206     /++
3207         Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`.
3208         If `writer` is set, the resulting string will be written directly
3209         to it.
3210 
3211         Returns:
3212             A `string` when not using an output range; `void` otherwise.
3213       +/
3214     string toSimpleString() const @safe pure nothrow
3215     {
3216         return toStringImpl!toSimpleString;
3217     }
3218 
3219     ///
3220     version (mir_test)
3221     @safe unittest
3222     {
3223         assert(Date.init.toSimpleString == "null");
3224         assert(Date(2010, 7, 4).toSimpleString == "2010-Jul-04");
3225         assert(Date(1998, 12, 25).toSimpleString == "1998-Dec-25");
3226         assert(Date(0, 1, 5).toSimpleString == "0000-Jan-05");
3227         assert(Date(-4, 1, 5).toSimpleString == "-0004-Jan-05");
3228     }
3229 
3230     version (mir_test)
3231     @safe unittest
3232     {
3233         // Test A.D.
3234         assert(Date(9, 12, 4).toSimpleString == "0009-Dec-04");
3235         assert(Date(99, 12, 4).toSimpleString == "0099-Dec-04");
3236         assert(Date(999, 12, 4).toSimpleString == "0999-Dec-04");
3237         assert(Date(9999, 7, 4).toSimpleString == "9999-Jul-04");
3238         assert(Date(10000, 10, 20).toSimpleString == "+10000-Oct-20");
3239 
3240         // Test B.C.
3241         assert(Date(0, 12, 4).toSimpleString == "0000-Dec-04");
3242         assert(Date(-9, 12, 4).toSimpleString == "-0009-Dec-04");
3243         assert(Date(-99, 12, 4).toSimpleString == "-0099-Dec-04");
3244         assert(Date(-999, 12, 4).toSimpleString == "-0999-Dec-04");
3245         assert(Date(-9999, 7, 4).toSimpleString == "-9999-Jul-04");
3246         assert(Date(-10000, 10, 20).toSimpleString == "-10000-Oct-20");
3247 
3248         const cdate = Date(1999, 7, 6);
3249         immutable idate = Date(1999, 7, 6);
3250         assert(cdate.toSimpleString == "1999-Jul-06");
3251         assert(idate.toSimpleString == "1999-Jul-06");
3252     }
3253 
3254     /// ditto
3255     void toSimpleString(W)(scope ref W w) const scope
3256         if (isOutputRange!(W, char))
3257     {
3258         import mir.format: printZeroPad;
3259         if(this == Date.init)
3260         {
3261             w.put("null");
3262             return;
3263         }
3264         with(yearMonthDay)
3265         {
3266             if (year >= 10_000)
3267                 w.put('+');
3268             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
3269             w.put('-');
3270             w.put(month.monthToString);
3271             w.put('-');
3272             w.printZeroPad(day, 2);
3273         }
3274     }
3275 
3276     version (mir_test)
3277     @safe pure unittest
3278     {
3279         import std.array : appender;
3280 
3281         auto w = appender!(char[])();
3282         Date(9, 12, 4).toSimpleString(w);
3283         assert(w.data == "0009-Dec-04");
3284         w.clear();
3285         Date(-10000, 10, 20).toSimpleString(w);
3286         assert(w.data == "-10000-Oct-20");
3287     }
3288 
3289     /++
3290     Creates a $(LREF Date) from a string with the format YYYYMMDD.
3291 
3292     Params:
3293         str = A string formatted in the way that $(LREF .date.toISOString) formats dates.
3294         value = (optional) result value.
3295 
3296     Throws:
3297         $(LREF DateTimeException) if the given string is
3298         not in the correct format or if the resulting $(LREF Date) would not
3299         be valid. Two arguments overload is `nothrow`.
3300     Returns:
3301         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
3302     +/
3303     static bool fromISOString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
3304         if (isSomeChar!C)
3305     {
3306         import mir.parse: fromString;
3307 
3308         if (str.length < 8)
3309             return false;
3310 
3311         auto yearStr = str[0 .. $ - 4];
3312 
3313         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
3314             return false;
3315 
3316         uint day, month;
3317         int year;
3318 
3319         return 
3320             fromString(str[$ - 2 .. $], day)
3321          && fromString(str[$ - 4 .. $ - 2], month)
3322          && fromString(yearStr, year)
3323          && fromYMD(year, month, day, value);
3324     }
3325 
3326     /// ditto
3327     static Date fromISOString(C)(scope const(C)[] str) @safe pure
3328         if (isSomeChar!C)
3329     {
3330         Date ret;
3331         if (fromISOString(str, ret))
3332             return ret;
3333         { import mir.exception : toMutable; throw InvalidISOString.toMutable; }
3334     }
3335 
3336     ///
3337     version (mir_test)
3338     @safe unittest
3339     {
3340         assert(Date.fromISOString("20100704") == Date(2010, 7, 4));
3341         assert(Date.fromISOString("19981225") == Date(1998, 12, 25));
3342         assert(Date.fromISOString("00000105") == Date(0, 1, 5));
3343         assert(Date.fromISOString("-00040105") == Date(-4, 1, 5));
3344     }
3345 
3346     version (mir_test)
3347     @safe unittest
3348     {
3349         assertThrown!DateTimeException(Date.fromISOString(""));
3350         assertThrown!DateTimeException(Date.fromISOString("990704"));
3351         assertThrown!DateTimeException(Date.fromISOString("0100704"));
3352         assertThrown!DateTimeException(Date.fromISOString("2010070"));
3353         assertThrown!DateTimeException(Date.fromISOString("120100704"));
3354         assertThrown!DateTimeException(Date.fromISOString("-0100704"));
3355         assertThrown!DateTimeException(Date.fromISOString("+0100704"));
3356         assertThrown!DateTimeException(Date.fromISOString("2010070a"));
3357         assertThrown!DateTimeException(Date.fromISOString("20100a04"));
3358         assertThrown!DateTimeException(Date.fromISOString("2010a704"));
3359 
3360         assertThrown!DateTimeException(Date.fromISOString("99-07-04"));
3361         assertThrown!DateTimeException(Date.fromISOString("010-07-04"));
3362         assertThrown!DateTimeException(Date.fromISOString("2010-07-0"));
3363         assertThrown!DateTimeException(Date.fromISOString("12010-07-04"));
3364         assertThrown!DateTimeException(Date.fromISOString("-010-07-04"));
3365         assertThrown!DateTimeException(Date.fromISOString("+010-07-04"));
3366         assertThrown!DateTimeException(Date.fromISOString("2010-07-0a"));
3367         assertThrown!DateTimeException(Date.fromISOString("2010-0a-04"));
3368         assertThrown!DateTimeException(Date.fromISOString("2010-a7-04"));
3369         assertThrown!DateTimeException(Date.fromISOString("2010/07/04"));
3370         assertThrown!DateTimeException(Date.fromISOString("2010/7/04"));
3371         assertThrown!DateTimeException(Date.fromISOString("2010/7/4"));
3372         assertThrown!DateTimeException(Date.fromISOString("2010/07/4"));
3373         assertThrown!DateTimeException(Date.fromISOString("2010-7-04"));
3374         assertThrown!DateTimeException(Date.fromISOString("2010-7-4"));
3375         assertThrown!DateTimeException(Date.fromISOString("2010-07-4"));
3376 
3377         assertThrown!DateTimeException(Date.fromISOString("99Jul04"));
3378         assertThrown!DateTimeException(Date.fromISOString("010Jul04"));
3379         assertThrown!DateTimeException(Date.fromISOString("2010Jul0"));
3380         assertThrown!DateTimeException(Date.fromISOString("12010Jul04"));
3381         assertThrown!DateTimeException(Date.fromISOString("-010Jul04"));
3382         assertThrown!DateTimeException(Date.fromISOString("+010Jul04"));
3383         assertThrown!DateTimeException(Date.fromISOString("2010Jul0a"));
3384         assertThrown!DateTimeException(Date.fromISOString("2010Jua04"));
3385         assertThrown!DateTimeException(Date.fromISOString("2010aul04"));
3386 
3387         assertThrown!DateTimeException(Date.fromISOString("99-Jul-04"));
3388         assertThrown!DateTimeException(Date.fromISOString("010-Jul-04"));
3389         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0"));
3390         assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04"));
3391         assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04"));
3392         assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04"));
3393         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a"));
3394         assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04"));
3395         assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04"));
3396         assertThrown!DateTimeException(Date.fromISOString("2010-aul-04"));
3397 
3398         assertThrown!DateTimeException(Date.fromISOString("2010-07-04"));
3399         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04"));
3400 
3401         assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
3402         assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6));
3403         assert(Date.fromISOString("+019990706") == Date(1999, 7, 6));
3404         assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
3405     }
3406 
3407     // bug# 17801
3408     version (mir_test)
3409     @safe unittest
3410     {
3411         import std.conv : to;
3412         import std.meta : AliasSeq;
3413         static foreach (C; AliasSeq!(char, wchar, dchar))
3414         {
3415             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
3416                 assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21));
3417         }
3418     }
3419 
3420     /++
3421     Creates a $(LREF Date) from a string with the format YYYY-MM-DD.
3422 
3423     Params:
3424         str = A string formatted in the way that $(LREF .date.toISOExtString) formats dates.
3425         value = (optional) result value.
3426 
3427     Throws:
3428         $(LREF DateTimeException) if the given string is
3429         not in the correct format or if the resulting $(LREF Date) would not
3430         be valid. Two arguments overload is `nothrow`.
3431     Returns:
3432         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
3433     +/
3434     static bool fromISOExtString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
3435         if (isSomeChar!C)
3436     {
3437         import mir.parse: fromString;
3438 
3439         if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-')
3440             return false;
3441 
3442         auto yearStr = str[0 .. $ - 6];
3443 
3444         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
3445             return false;
3446 
3447         uint day, month;
3448         int year;
3449 
3450         return
3451             fromString(str[$ - 2 .. $], day)
3452          && fromString(str[$ - 5 .. $ - 3], month)
3453          && fromString(yearStr, year)
3454          && fromYMD(year, month, day, value);
3455     }
3456 
3457     /// ditto
3458     static Date fromISOExtString(C)(scope const(C)[] str) @safe pure
3459         if (isSomeChar!C)
3460     {
3461         Date ret;
3462         if (fromISOExtString(str, ret))
3463             return ret;
3464         { import mir.exception : toMutable; throw InvalidISOExtendedString.toMutable; }
3465     }
3466 
3467     ///
3468     version (mir_test)
3469     @safe unittest
3470     {
3471         assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4));
3472         assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25));
3473         assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5));
3474         assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5));
3475     }
3476 
3477     version (mir_test)
3478     @safe unittest
3479     {
3480         assertThrown!DateTimeException(Date.fromISOExtString(""));
3481         assertThrown!DateTimeException(Date.fromISOExtString("990704"));
3482         assertThrown!DateTimeException(Date.fromISOExtString("0100704"));
3483         assertThrown!DateTimeException(Date.fromISOExtString("120100704"));
3484         assertThrown!DateTimeException(Date.fromISOExtString("-0100704"));
3485         assertThrown!DateTimeException(Date.fromISOExtString("+0100704"));
3486         assertThrown!DateTimeException(Date.fromISOExtString("2010070a"));
3487         assertThrown!DateTimeException(Date.fromISOExtString("20100a04"));
3488         assertThrown!DateTimeException(Date.fromISOExtString("2010a704"));
3489 
3490         assertThrown!DateTimeException(Date.fromISOExtString("99-07-04"));
3491         assertThrown!DateTimeException(Date.fromISOExtString("010-07-04"));
3492         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0"));
3493         assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04"));
3494         assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04"));
3495         assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04"));
3496         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a"));
3497         assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04"));
3498         assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04"));
3499         assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04"));
3500         assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04"));
3501         assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4"));
3502         assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4"));
3503         assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04"));
3504         assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4"));
3505         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4"));
3506 
3507         assertThrown!DateTimeException(Date.fromISOExtString("99Jul04"));
3508         assertThrown!DateTimeException(Date.fromISOExtString("010Jul04"));
3509         assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0"));
3510         assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04"));
3511         assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04"));
3512         assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04"));
3513         assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a"));
3514         assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04"));
3515         assertThrown!DateTimeException(Date.fromISOExtString("2010aul04"));
3516 
3517         assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04"));
3518         assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04"));
3519         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0"));
3520         assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04"));
3521         assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04"));
3522         assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04"));
3523         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a"));
3524         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04"));
3525         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04"));
3526         assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04"));
3527 
3528         assertThrown!DateTimeException(Date.fromISOExtString("20100704"));
3529         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04"));
3530 
3531         assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6));
3532         assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6));
3533         assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6));
3534     }
3535 
3536     // bug# 17801
3537     version (mir_test)
3538     @safe unittest
3539     {
3540         import std.conv : to;
3541         import std.meta : AliasSeq;
3542         static foreach (C; AliasSeq!(char, wchar, dchar))
3543         {
3544             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
3545                 assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21));
3546         }
3547     }
3548 
3549 
3550     /++
3551     Creates a $(LREF Date) from a string with the format YYYY-Mon-DD.
3552 
3553     Params:
3554         str = A string formatted in the way that $(LREF .date.toSimpleString) formats dates. The function is case sensetive.
3555         value = (optional) result value.
3556 
3557     Throws:
3558         $(LREF DateTimeException) if the given string is
3559         not in the correct format or if the resulting $(LREF Date) would not
3560         be valid. Two arguments overload is `nothrow`.
3561     Returns:
3562         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
3563     +/
3564     static bool fromSimpleString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
3565         if (isSomeChar!C)
3566     {
3567         import mir.parse: fromString;
3568 
3569         if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-')
3570             return false;
3571 
3572         auto yearStr = str[0 .. $ - 7];
3573 
3574         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
3575             return false;
3576 
3577         Month month;
3578 
3579         switch (str[$ - 6 .. $ - 3])
3580         {
3581             case "Jan": month = Month.jan; break;
3582             case "Feb": month = Month.feb; break;
3583             case "Mar": month = Month.mar; break;
3584             case "Apr": month = Month.apr; break;
3585             case "May": month = Month.may; break;
3586             case "Jun": month = Month.jun; break;
3587             case "Jul": month = Month.jul; break;
3588             case "Aug": month = Month.aug; break;
3589             case "Sep": month = Month.sep; break;
3590             case "Oct": month = Month.oct; break;
3591             case "Nov": month = Month.nov; break;
3592             case "Dec": month = Month.dec; break;
3593             default: return false;
3594         }
3595 
3596         uint day;
3597         int year;
3598 
3599         return
3600             fromString(str[$ - 2 .. $], day)
3601          && fromString(yearStr, year)
3602          && fromYMD(year, month, day, value);
3603     }
3604 
3605     /// ditto
3606     static Date fromSimpleString(C)(scope const(C)[] str) @safe pure
3607         if (isSomeChar!C)
3608     {
3609         Date ret;
3610         if (fromSimpleString(str, ret))
3611             return ret;
3612         throw new DateTimeException("Invalid Simple String");
3613     }
3614 
3615     ///
3616     version (mir_test)
3617     @safe unittest
3618     {
3619         assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4));
3620         assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25));
3621         assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5));
3622         assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5));
3623     }
3624 
3625     version (mir_test)
3626     @safe unittest
3627     {
3628         assertThrown!DateTimeException(Date.fromSimpleString(""));
3629         assertThrown!DateTimeException(Date.fromSimpleString("990704"));
3630         assertThrown!DateTimeException(Date.fromSimpleString("0100704"));
3631         assertThrown!DateTimeException(Date.fromSimpleString("2010070"));
3632         assertThrown!DateTimeException(Date.fromSimpleString("120100704"));
3633         assertThrown!DateTimeException(Date.fromSimpleString("-0100704"));
3634         assertThrown!DateTimeException(Date.fromSimpleString("+0100704"));
3635         assertThrown!DateTimeException(Date.fromSimpleString("2010070a"));
3636         assertThrown!DateTimeException(Date.fromSimpleString("20100a04"));
3637         assertThrown!DateTimeException(Date.fromSimpleString("2010a704"));
3638 
3639         assertThrown!DateTimeException(Date.fromSimpleString("99-07-04"));
3640         assertThrown!DateTimeException(Date.fromSimpleString("010-07-04"));
3641         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0"));
3642         assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04"));
3643         assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04"));
3644         assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04"));
3645         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a"));
3646         assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04"));
3647         assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04"));
3648         assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04"));
3649         assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04"));
3650         assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4"));
3651         assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4"));
3652         assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04"));
3653         assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4"));
3654         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4"));
3655 
3656         assertThrown!DateTimeException(Date.fromSimpleString("99Jul04"));
3657         assertThrown!DateTimeException(Date.fromSimpleString("010Jul04"));
3658         assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0"));
3659         assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04"));
3660         assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04"));
3661         assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04"));
3662         assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a"));
3663         assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04"));
3664         assertThrown!DateTimeException(Date.fromSimpleString("2010aul04"));
3665 
3666         assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04"));
3667         assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04"));
3668         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0"));
3669         assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04"));
3670         assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04"));
3671         assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04"));
3672         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a"));
3673         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04"));
3674         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04"));
3675         assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04"));
3676 
3677         assertThrown!DateTimeException(Date.fromSimpleString("20100704"));
3678         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04"));
3679 
3680         assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6));
3681         assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6));
3682         assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6));
3683     }
3684 
3685     // bug# 17801
3686     version (mir_test)
3687     @safe unittest
3688     {
3689         import std.conv : to;
3690         import std.meta : AliasSeq;
3691         static foreach (C; AliasSeq!(char, wchar, dchar))
3692         {
3693             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
3694                 assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21));
3695         }
3696     }
3697 
3698     /++
3699     Creates a $(LREF Date) from a string with the format YYYY-MM-DD, YYYYMMDD, or YYYY-Mon-DD.
3700 
3701     Params:
3702         str = A string formatted in the way that $(LREF .date.toISOExtString), $(LREF .date.toISOString), and $(LREF .date.toSimpleString) format dates. The function is case sensetive.
3703         value = (optional) result value.
3704 
3705     Throws:
3706         $(LREF DateTimeException) if the given string is
3707         not in the correct format or if the resulting $(LREF Date) would not
3708         be valid. Two arguments overload is `nothrow`.
3709     Returns:
3710         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
3711     +/
3712     static bool fromString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
3713     {
3714         return fromISOExtString(str, value)
3715             || fromISOString(str, value)
3716             || fromSimpleString(str, value);
3717     }
3718 
3719     ///
3720     version (mir_test)
3721     @safe pure @nogc unittest
3722     {
3723         assert(Date.fromString("2010-07-04") == Date(2010, 7, 4));
3724         assert(Date.fromString("20100704") == Date(2010, 7, 4));
3725         assert(Date.fromString("2010-Jul-04") == Date(2010, 7, 4));
3726     }
3727 
3728     /// ditto
3729     static Date fromString(C)(scope const(C)[] str) @safe pure
3730         if (isSomeChar!C)
3731     {
3732         Date ret;
3733         if (fromString(str, ret))
3734             return ret;
3735         { import mir.exception : toMutable; throw InvalidString.toMutable; }
3736     }
3737 
3738     /++
3739         Returns the $(LREF Date) farthest in the past which is representable by
3740         $(LREF Date).
3741       +/
3742     @property static Date min() @safe pure nothrow @nogc
3743     {
3744         return Date.fromDayNumber(int.max);
3745     }
3746 
3747     /++
3748         Returns the $(LREF Date) farthest in the future which is representable
3749         by $(LREF Date).
3750       +/
3751     @property static Date max() @safe pure nothrow @nogc
3752     {
3753         return Date.fromDayNumber(int.min);
3754     }
3755 
3756 private:
3757 
3758     /+
3759         Whether the given values form a valid date.
3760 
3761         Params:
3762             year  = The year to test.
3763             month = The month of the Gregorian Calendar to test.
3764             day   = The day of the month to test.
3765      +/
3766     static bool _valid(int year, int month, int day) @safe pure nothrow @nogc
3767     {
3768         if (!valid!"months"(month))
3769             return false;
3770         return valid!"days"(year, month, day);
3771     }
3772 
3773 
3774 package:
3775 
3776     /+
3777         Adds the given number of days to this $(LREF Date). A negative number
3778         will subtract.
3779 
3780         The month will be adjusted along with the day if the number of days
3781         added (or subtracted) would overflow (or underflow) the current month.
3782         The year will be adjusted along with the month if the increase (or
3783         decrease) to the month would cause it to overflow (or underflow) the
3784         current year.
3785 
3786         $(D _addDays(numDays)) is effectively equivalent to
3787         $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days).
3788 
3789         Params:
3790             days = The number of days to add to this Date.
3791       +/
3792     ref Date _addDays(long days) return @safe pure nothrow @nogc
3793     {
3794         _dayNumber = cast(int)(_dayNumber + days);
3795         return this;
3796     }
3797 
3798     version (mir_test)
3799     @safe unittest
3800     {
3801         // Test A.D.
3802         {
3803             auto date = Date(1999, 2, 28);
3804             date._addDays(1);
3805             assert(date == Date(1999, 3, 1));
3806             date._addDays(-1);
3807             assert(date == Date(1999, 2, 28));
3808         }
3809 
3810         {
3811             auto date = Date(2000, 2, 28);
3812             date._addDays(1);
3813             assert(date == Date(2000, 2, 29));
3814             date._addDays(1);
3815             assert(date == Date(2000, 3, 1));
3816             date._addDays(-1);
3817             assert(date == Date(2000, 2, 29));
3818         }
3819 
3820         {
3821             auto date = Date(1999, 6, 30);
3822             date._addDays(1);
3823             assert(date == Date(1999, 7, 1));
3824             date._addDays(-1);
3825             assert(date == Date(1999, 6, 30));
3826         }
3827 
3828         {
3829             auto date = Date(1999, 7, 31);
3830             date._addDays(1);
3831             assert(date == Date(1999, 8, 1));
3832             date._addDays(-1);
3833             assert(date == Date(1999, 7, 31));
3834         }
3835 
3836         {
3837             auto date = Date(1999, 1, 1);
3838             date._addDays(-1);
3839             assert(date == Date(1998, 12, 31));
3840             date._addDays(1);
3841             assert(date == Date(1999, 1, 1));
3842         }
3843 
3844         {
3845             auto date = Date(1999, 7, 6);
3846             date._addDays(9);
3847             assert(date == Date(1999, 7, 15));
3848             date._addDays(-11);
3849             assert(date == Date(1999, 7, 4));
3850             date._addDays(30);
3851             assert(date == Date(1999, 8, 3));
3852             date._addDays(-3);
3853             assert(date == Date(1999, 7, 31));
3854         }
3855 
3856         {
3857             auto date = Date(1999, 7, 6);
3858             date._addDays(365);
3859             assert(date == Date(2000, 7, 5));
3860             date._addDays(-365);
3861             assert(date == Date(1999, 7, 6));
3862             date._addDays(366);
3863             assert(date == Date(2000, 7, 6));
3864             date._addDays(730);
3865             assert(date == Date(2002, 7, 6));
3866             date._addDays(-1096);
3867             assert(date == Date(1999, 7, 6));
3868         }
3869 
3870         // Test B.C.
3871         {
3872             auto date = Date(-1999, 2, 28);
3873             date._addDays(1);
3874             assert(date == Date(-1999, 3, 1));
3875             date._addDays(-1);
3876             assert(date == Date(-1999, 2, 28));
3877         }
3878 
3879         {
3880             auto date = Date(-2000, 2, 28);
3881             date._addDays(1);
3882             assert(date == Date(-2000, 2, 29));
3883             date._addDays(1);
3884             assert(date == Date(-2000, 3, 1));
3885             date._addDays(-1);
3886             assert(date == Date(-2000, 2, 29));
3887         }
3888 
3889         {
3890             auto date = Date(-1999, 6, 30);
3891             date._addDays(1);
3892             assert(date == Date(-1999, 7, 1));
3893             date._addDays(-1);
3894             assert(date == Date(-1999, 6, 30));
3895         }
3896 
3897         {
3898             auto date = Date(-1999, 7, 31);
3899             date._addDays(1);
3900             assert(date == Date(-1999, 8, 1));
3901             date._addDays(-1);
3902             assert(date == Date(-1999, 7, 31));
3903         }
3904 
3905         {
3906             auto date = Date(-1999, 1, 1);
3907             date._addDays(-1);
3908             assert(date == Date(-2000, 12, 31));
3909             date._addDays(1);
3910             assert(date == Date(-1999, 1, 1));
3911         }
3912 
3913         {
3914             auto date = Date(-1999, 7, 6);
3915             date._addDays(9);
3916             assert(date == Date(-1999, 7, 15));
3917             date._addDays(-11);
3918             assert(date == Date(-1999, 7, 4));
3919             date._addDays(30);
3920             assert(date == Date(-1999, 8, 3));
3921             date._addDays(-3);
3922         }
3923 
3924         {
3925             auto date = Date(-1999, 7, 6);
3926             date._addDays(365);
3927             assert(date == Date(-1998, 7, 6));
3928             date._addDays(-365);
3929             assert(date == Date(-1999, 7, 6));
3930             date._addDays(366);
3931             assert(date == Date(-1998, 7, 7));
3932             date._addDays(730);
3933             assert(date == Date(-1996, 7, 6));
3934             date._addDays(-1096);
3935             assert(date == Date(-1999, 7, 6));
3936         }
3937 
3938         // Test Both
3939         {
3940             auto date = Date(1, 7, 6);
3941             date._addDays(-365);
3942             assert(date == Date(0, 7, 6));
3943             date._addDays(365);
3944             assert(date == Date(1, 7, 6));
3945             date._addDays(-731);
3946             assert(date == Date(-1, 7, 6));
3947             date._addDays(730);
3948             assert(date == Date(1, 7, 5));
3949         }
3950 
3951         const cdate = Date(1999, 7, 6);
3952         immutable idate = Date(1999, 7, 6);
3953         static assert(!__traits(compiles, cdate._addDays(12)));
3954         static assert(!__traits(compiles, idate._addDays(12)));
3955     }
3956 
3957     int _dayNumber;
3958 }
3959 
3960 /// ditto
3961 deprecated("use `Date` instead")
3962 alias date = Date;
3963 
3964 /++
3965     Returns the number of days from the current day of the week to the given
3966     day of the week. If they are the same, then the result is 0.
3967     Params:
3968         currDoW = The current day of the week.
3969         dow     = The day of the week to get the number of days to.
3970   +/
3971 int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc
3972 {
3973     if (currDoW == dow)
3974         return 0;
3975     if (currDoW < dow)
3976         return dow - currDoW;
3977     return DayOfWeek.sun - currDoW + dow + 1;
3978 }
3979 
3980 ///
3981 version (mir_test)
3982 @safe pure nothrow @nogc unittest
3983 {
3984     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
3985     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
3986     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
3987 }
3988 
3989 version (mir_test)
3990 @safe unittest
3991 {
3992     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0);
3993     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1);
3994     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2);
3995     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3);
3996     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4);
3997     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5);
3998     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6);
3999 
4000     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
4001     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
4002     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1);
4003     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
4004     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3);
4005     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4);
4006     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5);
4007 
4008     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5);
4009     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6);
4010     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0);
4011     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1);
4012     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2);
4013     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3);
4014     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4);
4015 
4016     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4);
4017     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5);
4018     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6);
4019     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0);
4020     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1);
4021     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2);
4022     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3);
4023 
4024     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3);
4025     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4);
4026     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5);
4027     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6);
4028     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0);
4029     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1);
4030     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2);
4031 
4032     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2);
4033     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3);
4034     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4);
4035     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5);
4036     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6);
4037     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0);
4038     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1);
4039 
4040     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1);
4041     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2);
4042     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3);
4043     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4);
4044     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5);
4045     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6);
4046     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0);
4047 }
4048 
4049 package:
4050 
4051 
4052 /+
4053     Array of the short (three letter) names of each month.
4054   +/
4055 immutable string[12] _monthNames = ["Jan",
4056                                     "Feb",
4057                                     "Mar",
4058                                     "Apr",
4059                                     "May",
4060                                     "Jun",
4061                                     "Jul",
4062                                     "Aug",
4063                                     "Sep",
4064                                     "Oct",
4065                                     "Nov",
4066                                     "Dec"];
4067 
4068 /++
4069     The maximum valid Day in the given month in the given year.
4070 
4071     Params:
4072         year  = The year to get the day for.
4073         month = The month of the Gregorian Calendar to get the day for.
4074  +/
4075 public ubyte maxDay(int year, int month) @safe pure nothrow @nogc
4076 in
4077 {
4078     assert(valid!"months"(month));
4079 }
4080 do
4081 {
4082     switch (month)
4083     {
4084         case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec:
4085             return 31;
4086         case Month.feb:
4087             return yearIsLeapYear(year) ? 29 : 28;
4088         case Month.apr, Month.jun, Month.sep, Month.nov:
4089             return 30;
4090         default:
4091             assert(0, "Invalid month.");
4092     }
4093 }
4094 
4095 version (mir_test)
4096 @safe unittest
4097 {
4098     // Test A.D.
4099     assert(maxDay(1999, 1) == 31);
4100     assert(maxDay(1999, 2) == 28);
4101     assert(maxDay(1999, 3) == 31);
4102     assert(maxDay(1999, 4) == 30);
4103     assert(maxDay(1999, 5) == 31);
4104     assert(maxDay(1999, 6) == 30);
4105     assert(maxDay(1999, 7) == 31);
4106     assert(maxDay(1999, 8) == 31);
4107     assert(maxDay(1999, 9) == 30);
4108     assert(maxDay(1999, 10) == 31);
4109     assert(maxDay(1999, 11) == 30);
4110     assert(maxDay(1999, 12) == 31);
4111 
4112     assert(maxDay(2000, 1) == 31);
4113     assert(maxDay(2000, 2) == 29);
4114     assert(maxDay(2000, 3) == 31);
4115     assert(maxDay(2000, 4) == 30);
4116     assert(maxDay(2000, 5) == 31);
4117     assert(maxDay(2000, 6) == 30);
4118     assert(maxDay(2000, 7) == 31);
4119     assert(maxDay(2000, 8) == 31);
4120     assert(maxDay(2000, 9) == 30);
4121     assert(maxDay(2000, 10) == 31);
4122     assert(maxDay(2000, 11) == 30);
4123     assert(maxDay(2000, 12) == 31);
4124 
4125     // Test B.C.
4126     assert(maxDay(-1999, 1) == 31);
4127     assert(maxDay(-1999, 2) == 28);
4128     assert(maxDay(-1999, 3) == 31);
4129     assert(maxDay(-1999, 4) == 30);
4130     assert(maxDay(-1999, 5) == 31);
4131     assert(maxDay(-1999, 6) == 30);
4132     assert(maxDay(-1999, 7) == 31);
4133     assert(maxDay(-1999, 8) == 31);
4134     assert(maxDay(-1999, 9) == 30);
4135     assert(maxDay(-1999, 10) == 31);
4136     assert(maxDay(-1999, 11) == 30);
4137     assert(maxDay(-1999, 12) == 31);
4138 
4139     assert(maxDay(-2000, 1) == 31);
4140     assert(maxDay(-2000, 2) == 29);
4141     assert(maxDay(-2000, 3) == 31);
4142     assert(maxDay(-2000, 4) == 30);
4143     assert(maxDay(-2000, 5) == 31);
4144     assert(maxDay(-2000, 6) == 30);
4145     assert(maxDay(-2000, 7) == 31);
4146     assert(maxDay(-2000, 8) == 31);
4147     assert(maxDay(-2000, 9) == 30);
4148     assert(maxDay(-2000, 10) == 31);
4149     assert(maxDay(-2000, 11) == 30);
4150     assert(maxDay(-2000, 12) == 31);
4151 }
4152 
4153 /+
4154     Returns the day of the week for the given day of the Gregorian/Julian Calendar.
4155 
4156     Params:
4157         day = The day of the Gregorian/Julian Calendar for which to get the day of
4158               the week.
4159   +/
4160 DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc
4161 {
4162     // January 1st, 1 A.D. was a Monday
4163     if (day >= 0)
4164         return cast(DayOfWeek)(day % 7);
4165     else
4166     {
4167         immutable dow = cast(DayOfWeek)((day % 7) + 7);
4168 
4169         if (dow == 7)
4170             return DayOfWeek.mon;
4171         else
4172             return dow;
4173     }
4174 }
4175 
4176 private:
4177 
4178 enum daysInYear     = 365;  // The number of days in a non-leap year.
4179 enum daysInLeapYear = 366;  // The numbef or days in a leap year.
4180 enum daysIn4Years   = daysInYear * 3 + daysInLeapYear;  // Number of days in 4 years.
4181 enum daysIn100Years = daysIn4Years * 25 - 1;  // The number of days in 100 years.
4182 enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years.
4183 
4184 /+
4185     Array of integers representing the last days of each month in a year.
4186   +/
4187 immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
4188 
4189 /+
4190     Array of integers representing the last days of each month in a leap year.
4191   +/
4192 immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
4193 
4194 /+
4195     Array of integers representing the last days of each quarter in a year.
4196   +/
4197 immutable int[5] lastDayQuarterNonLeap = [0, 90, 181, 273, 365];
4198 
4199 /+
4200     Array of integers representing the last days of each quarter in a leap year.
4201   +/
4202 immutable int[5] lastDayQuarterLeap = [0, 91, 182, 274, 366];
4203 
4204 /+
4205     Returns the string representation of the given month.
4206   +/
4207 string monthToString(Month month) @safe pure @nogc nothrow
4208 {
4209     assert(month >= Month.jan && month <= Month.dec, "Invalid month");
4210     return _monthNames[month - Month.jan];
4211 }
4212 
4213 version (mir_test)
4214 @safe unittest
4215 {
4216     assert(monthToString(Month.jan) == "Jan");
4217     assert(monthToString(Month.feb) == "Feb");
4218     assert(monthToString(Month.mar) == "Mar");
4219     assert(monthToString(Month.apr) == "Apr");
4220     assert(monthToString(Month.may) == "May");
4221     assert(monthToString(Month.jun) == "Jun");
4222     assert(monthToString(Month.jul) == "Jul");
4223     assert(monthToString(Month.aug) == "Aug");
4224     assert(monthToString(Month.sep) == "Sep");
4225     assert(monthToString(Month.oct) == "Oct");
4226     assert(monthToString(Month.nov) == "Nov");
4227     assert(monthToString(Month.dec) == "Dec");
4228 }
4229 
4230 version (mir_test)
4231 version(unittest)
4232 {
4233     // All of these helper arrays are sorted in ascending order.
4234     auto testYearsBC = [-1999, -1200, -600, -4, -1, 0];
4235     auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012];
4236 
4237     // I'd use a Tuple, but I get forward reference errors if I try.
4238     static struct MonthDay
4239     {
4240         Month month;
4241         short day;
4242 
4243         this(int m, short d) @safe
4244         {
4245             month = cast(Month) m;
4246             day = d;
4247         }
4248     }
4249 
4250     MonthDay[] testMonthDays = [MonthDay(1, 1),
4251                                 MonthDay(1, 2),
4252                                 MonthDay(3, 17),
4253                                 MonthDay(7, 4),
4254                                 MonthDay(10, 27),
4255                                 MonthDay(12, 30),
4256                                 MonthDay(12, 31)];
4257 
4258     auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31];
4259 
4260     Date[] testDatesBC;
4261     Date[] testDatesAD;
4262 
4263     // I'd use a Tuple, but I get forward reference errors if I try.
4264     struct GregDay { int day; Date date; }
4265     auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar
4266                            GregDay(-735_233, Date(-2012, 1, 1)),
4267                            GregDay(-735_202, Date(-2012, 2, 1)),
4268                            GregDay(-735_175, Date(-2012, 2, 28)),
4269                            GregDay(-735_174, Date(-2012, 2, 29)),
4270                            GregDay(-735_173, Date(-2012, 3, 1)),
4271                            GregDay(-734_502, Date(-2010, 1, 1)),
4272                            GregDay(-734_472, Date(-2010, 1, 31)),
4273                            GregDay(-734_471, Date(-2010, 2, 1)),
4274                            GregDay(-734_444, Date(-2010, 2, 28)),
4275                            GregDay(-734_443, Date(-2010, 3, 1)),
4276                            GregDay(-734_413, Date(-2010, 3, 31)),
4277                            GregDay(-734_412, Date(-2010, 4, 1)),
4278                            GregDay(-734_383, Date(-2010, 4, 30)),
4279                            GregDay(-734_382, Date(-2010, 5, 1)),
4280                            GregDay(-734_352, Date(-2010, 5, 31)),
4281                            GregDay(-734_351, Date(-2010, 6, 1)),
4282                            GregDay(-734_322, Date(-2010, 6, 30)),
4283                            GregDay(-734_321, Date(-2010, 7, 1)),
4284                            GregDay(-734_291, Date(-2010, 7, 31)),
4285                            GregDay(-734_290, Date(-2010, 8, 1)),
4286                            GregDay(-734_260, Date(-2010, 8, 31)),
4287                            GregDay(-734_259, Date(-2010, 9, 1)),
4288                            GregDay(-734_230, Date(-2010, 9, 30)),
4289                            GregDay(-734_229, Date(-2010, 10, 1)),
4290                            GregDay(-734_199, Date(-2010, 10, 31)),
4291                            GregDay(-734_198, Date(-2010, 11, 1)),
4292                            GregDay(-734_169, Date(-2010, 11, 30)),
4293                            GregDay(-734_168, Date(-2010, 12, 1)),
4294                            GregDay(-734_139, Date(-2010, 12, 30)),
4295                            GregDay(-734_138, Date(-2010, 12, 31)),
4296                            GregDay(-731_215, Date(-2001, 1, 1)),
4297                            GregDay(-730_850, Date(-2000, 1, 1)),
4298                            GregDay(-730_849, Date(-2000, 1, 2)),
4299                            GregDay(-730_486, Date(-2000, 12, 30)),
4300                            GregDay(-730_485, Date(-2000, 12, 31)),
4301                            GregDay(-730_484, Date(-1999, 1, 1)),
4302                            GregDay(-694_690, Date(-1901, 1, 1)),
4303                            GregDay(-694_325, Date(-1900, 1, 1)),
4304                            GregDay(-585_118, Date(-1601, 1, 1)),
4305                            GregDay(-584_753, Date(-1600, 1, 1)),
4306                            GregDay(-584_388, Date(-1600, 12, 31)),
4307                            GregDay(-584_387, Date(-1599, 1, 1)),
4308                            GregDay(-365_972, Date(-1001, 1, 1)),
4309                            GregDay(-365_607, Date(-1000, 1, 1)),
4310                            GregDay(-183_351, Date(-501, 1, 1)),
4311                            GregDay(-182_986, Date(-500, 1, 1)),
4312                            GregDay(-182_621, Date(-499, 1, 1)),
4313                            GregDay(-146_827, Date(-401, 1, 1)),
4314                            GregDay(-146_462, Date(-400, 1, 1)),
4315                            GregDay(-146_097, Date(-400, 12, 31)),
4316                            GregDay(-110_302, Date(-301, 1, 1)),
4317                            GregDay(-109_937, Date(-300, 1, 1)),
4318                            GregDay(-73_778, Date(-201, 1, 1)),
4319                            GregDay(-73_413, Date(-200, 1, 1)),
4320                            GregDay(-38_715, Date(-105, 1, 1)),
4321                            GregDay(-37_254, Date(-101, 1, 1)),
4322                            GregDay(-36_889, Date(-100, 1, 1)),
4323                            GregDay(-36_524, Date(-99, 1, 1)),
4324                            GregDay(-36_160, Date(-99, 12, 31)),
4325                            GregDay(-35_794, Date(-97, 1, 1)),
4326                            GregDay(-18_627, Date(-50, 1, 1)),
4327                            GregDay(-18_262, Date(-49, 1, 1)),
4328                            GregDay(-3652, Date(-9, 1, 1)),
4329                            GregDay(-2191, Date(-5, 1, 1)),
4330                            GregDay(-1827, Date(-5, 12, 31)),
4331                            GregDay(-1826, Date(-4, 1, 1)),
4332                            GregDay(-1825, Date(-4, 1, 2)),
4333                            GregDay(-1462, Date(-4, 12, 30)),
4334                            GregDay(-1461, Date(-4, 12, 31)),
4335                            GregDay(-1460, Date(-3, 1, 1)),
4336                            GregDay(-1096, Date(-3, 12, 31)),
4337                            GregDay(-1095, Date(-2, 1, 1)),
4338                            GregDay(-731, Date(-2, 12, 31)),
4339                            GregDay(-730, Date(-1, 1, 1)),
4340                            GregDay(-367, Date(-1, 12, 30)),
4341                            GregDay(-366, Date(-1, 12, 31)),
4342                            GregDay(-365, Date(0, 1, 1)),
4343                            GregDay(-31, Date(0, 11, 30)),
4344                            GregDay(-30, Date(0, 12, 1)),
4345                            GregDay(-1, Date(0, 12, 30)),
4346                            GregDay(0, Date(0, 12, 31))];
4347 
4348     auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)),
4349                            GregDay(2, Date(1, 1, 2)),
4350                            GregDay(32, Date(1, 2, 1)),
4351                            GregDay(365, Date(1, 12, 31)),
4352                            GregDay(366, Date(2, 1, 1)),
4353                            GregDay(731, Date(3, 1, 1)),
4354                            GregDay(1096, Date(4, 1, 1)),
4355                            GregDay(1097, Date(4, 1, 2)),
4356                            GregDay(1460, Date(4, 12, 30)),
4357                            GregDay(1461, Date(4, 12, 31)),
4358                            GregDay(1462, Date(5, 1, 1)),
4359                            GregDay(17_898, Date(50, 1, 1)),
4360                            GregDay(35_065, Date(97, 1, 1)),
4361                            GregDay(36_160, Date(100, 1, 1)),
4362                            GregDay(36_525, Date(101, 1, 1)),
4363                            GregDay(37_986, Date(105, 1, 1)),
4364                            GregDay(72_684, Date(200, 1, 1)),
4365                            GregDay(73_049, Date(201, 1, 1)),
4366                            GregDay(109_208, Date(300, 1, 1)),
4367                            GregDay(109_573, Date(301, 1, 1)),
4368                            GregDay(145_732, Date(400, 1, 1)),
4369                            GregDay(146_098, Date(401, 1, 1)),
4370                            GregDay(182_257, Date(500, 1, 1)),
4371                            GregDay(182_622, Date(501, 1, 1)),
4372                            GregDay(364_878, Date(1000, 1, 1)),
4373                            GregDay(365_243, Date(1001, 1, 1)),
4374                            GregDay(584_023, Date(1600, 1, 1)),
4375                            GregDay(584_389, Date(1601, 1, 1)),
4376                            GregDay(693_596, Date(1900, 1, 1)),
4377                            GregDay(693_961, Date(1901, 1, 1)),
4378                            GregDay(729_755, Date(1999, 1, 1)),
4379                            GregDay(730_120, Date(2000, 1, 1)),
4380                            GregDay(730_121, Date(2000, 1, 2)),
4381                            GregDay(730_484, Date(2000, 12, 30)),
4382                            GregDay(730_485, Date(2000, 12, 31)),
4383                            GregDay(730_486, Date(2001, 1, 1)),
4384                            GregDay(733_773, Date(2010, 1, 1)),
4385                            GregDay(733_774, Date(2010, 1, 2)),
4386                            GregDay(733_803, Date(2010, 1, 31)),
4387                            GregDay(733_804, Date(2010, 2, 1)),
4388                            GregDay(733_831, Date(2010, 2, 28)),
4389                            GregDay(733_832, Date(2010, 3, 1)),
4390                            GregDay(733_862, Date(2010, 3, 31)),
4391                            GregDay(733_863, Date(2010, 4, 1)),
4392                            GregDay(733_892, Date(2010, 4, 30)),
4393                            GregDay(733_893, Date(2010, 5, 1)),
4394                            GregDay(733_923, Date(2010, 5, 31)),
4395                            GregDay(733_924, Date(2010, 6, 1)),
4396                            GregDay(733_953, Date(2010, 6, 30)),
4397                            GregDay(733_954, Date(2010, 7, 1)),
4398                            GregDay(733_984, Date(2010, 7, 31)),
4399                            GregDay(733_985, Date(2010, 8, 1)),
4400                            GregDay(734_015, Date(2010, 8, 31)),
4401                            GregDay(734_016, Date(2010, 9, 1)),
4402                            GregDay(734_045, Date(2010, 9, 30)),
4403                            GregDay(734_046, Date(2010, 10, 1)),
4404                            GregDay(734_076, Date(2010, 10, 31)),
4405                            GregDay(734_077, Date(2010, 11, 1)),
4406                            GregDay(734_106, Date(2010, 11, 30)),
4407                            GregDay(734_107, Date(2010, 12, 1)),
4408                            GregDay(734_136, Date(2010, 12, 30)),
4409                            GregDay(734_137, Date(2010, 12, 31)),
4410                            GregDay(734_503, Date(2012, 1, 1)),
4411                            GregDay(734_534, Date(2012, 2, 1)),
4412                            GregDay(734_561, Date(2012, 2, 28)),
4413                            GregDay(734_562, Date(2012, 2, 29)),
4414                            GregDay(734_563, Date(2012, 3, 1)),
4415                            GregDay(734_858, Date(2012, 12, 21))];
4416 
4417     // I'd use a Tuple, but I get forward reference errors if I try.
4418     struct DayOfYear { int day; MonthDay md; }
4419     auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)),
4420                            DayOfYear(2, MonthDay(1, 2)),
4421                            DayOfYear(3, MonthDay(1, 3)),
4422                            DayOfYear(31, MonthDay(1, 31)),
4423                            DayOfYear(32, MonthDay(2, 1)),
4424                            DayOfYear(59, MonthDay(2, 28)),
4425                            DayOfYear(60, MonthDay(3, 1)),
4426                            DayOfYear(90, MonthDay(3, 31)),
4427                            DayOfYear(91, MonthDay(4, 1)),
4428                            DayOfYear(120, MonthDay(4, 30)),
4429                            DayOfYear(121, MonthDay(5, 1)),
4430                            DayOfYear(151, MonthDay(5, 31)),
4431                            DayOfYear(152, MonthDay(6, 1)),
4432                            DayOfYear(181, MonthDay(6, 30)),
4433                            DayOfYear(182, MonthDay(7, 1)),
4434                            DayOfYear(212, MonthDay(7, 31)),
4435                            DayOfYear(213, MonthDay(8, 1)),
4436                            DayOfYear(243, MonthDay(8, 31)),
4437                            DayOfYear(244, MonthDay(9, 1)),
4438                            DayOfYear(273, MonthDay(9, 30)),
4439                            DayOfYear(274, MonthDay(10, 1)),
4440                            DayOfYear(304, MonthDay(10, 31)),
4441                            DayOfYear(305, MonthDay(11, 1)),
4442                            DayOfYear(334, MonthDay(11, 30)),
4443                            DayOfYear(335, MonthDay(12, 1)),
4444                            DayOfYear(363, MonthDay(12, 29)),
4445                            DayOfYear(364, MonthDay(12, 30)),
4446                            DayOfYear(365, MonthDay(12, 31))];
4447 
4448     auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)),
4449                                DayOfYear(2, MonthDay(1, 2)),
4450                                DayOfYear(3, MonthDay(1, 3)),
4451                                DayOfYear(31, MonthDay(1, 31)),
4452                                DayOfYear(32, MonthDay(2, 1)),
4453                                DayOfYear(59, MonthDay(2, 28)),
4454                                DayOfYear(60, MonthDay(2, 29)),
4455                                DayOfYear(61, MonthDay(3, 1)),
4456                                DayOfYear(91, MonthDay(3, 31)),
4457                                DayOfYear(92, MonthDay(4, 1)),
4458                                DayOfYear(121, MonthDay(4, 30)),
4459                                DayOfYear(122, MonthDay(5, 1)),
4460                                DayOfYear(152, MonthDay(5, 31)),
4461                                DayOfYear(153, MonthDay(6, 1)),
4462                                DayOfYear(182, MonthDay(6, 30)),
4463                                DayOfYear(183, MonthDay(7, 1)),
4464                                DayOfYear(213, MonthDay(7, 31)),
4465                                DayOfYear(214, MonthDay(8, 1)),
4466                                DayOfYear(244, MonthDay(8, 31)),
4467                                DayOfYear(245, MonthDay(9, 1)),
4468                                DayOfYear(274, MonthDay(9, 30)),
4469                                DayOfYear(275, MonthDay(10, 1)),
4470                                DayOfYear(305, MonthDay(10, 31)),
4471                                DayOfYear(306, MonthDay(11, 1)),
4472                                DayOfYear(335, MonthDay(11, 30)),
4473                                DayOfYear(336, MonthDay(12, 1)),
4474                                DayOfYear(364, MonthDay(12, 29)),
4475                                DayOfYear(365, MonthDay(12, 30)),
4476                                DayOfYear(366, MonthDay(12, 31))];
4477 
4478     void initializeTests() @safe
4479     {
4480         foreach (year; testYearsBC)
4481         {
4482             foreach (md; testMonthDays)
4483                 testDatesBC ~= Date(year, md.month, md.day);
4484         }
4485 
4486         foreach (year; testYearsAD)
4487         {
4488             foreach (md; testMonthDays)
4489                 testDatesAD ~= Date(year, md.month, md.day);
4490         }
4491     }
4492 }