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 ))
11 $(TR $(TD Other date types) $(TD
12     $(LREF Month)
13     $(LREF DayOfWeek)
14 ))
15 $(TR $(TD Date checking) $(TD
16     $(LREF valid)
17     $(LREF yearIsLeapYear)
18 ))
19 $(TR $(TD Date conversion) $(TD
20     $(LREF daysToDayOfWeek)
21 ))
22 $(TR $(TD Other) $(TD
23     $(LREF AllowDayOverflow)
24     $(LREF DateTimeException)
25 ))
26 ))
27 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
28 Authors: $(HTTP jmdavisprog.com, Jonathan M Davis), Ilya Yaroshenko (boost-like and BetterC rework)
29 +/
30 module mir.date;
31 
32 import mir.primitives: isOutputRange;
33 import mir.serde: serdeProxy;
34 import mir.timestamp: Timestamp;
35 import std.traits: isSomeChar, Unqual;
36 
37 version(mir_test)
38 version(D_Exceptions)
39 version(unittest) import std.exception : assertThrown;
40 
41 version(test_with_asdf)
42 unittest
43 {
44     import asdf.serialization;
45 
46     assert(Date(2020, 3, 19).serializeToJson == `"2020-03-19"`);
47     assert(`"2020-03-19"`.deserialize!Date == Date(2020, 3, 19));
48     assert(`"20200319"`.deserialize!Date == Date(2020, 3, 19));
49     assert(`"2020-Mar-19"`.deserialize!Date == Date(2020, 3, 19));
50 }
51 
52 /++
53 Returns whether the given value is valid for the given unit type when in a
54 time point. Naturally, a duration is not held to a particular range, but
55 the values in a time point are (e.g. a month must be in the range of
56 1 - 12 inclusive).
57 Params:
58     units = The units of time to validate.
59     value = The number to validate.
60 +/
61 bool valid(string units)(int value) @safe pure nothrow @nogc
62 if (units == "months" ||
63     units == "hours" ||
64     units == "minutes" ||
65     units == "seconds")
66 {
67     static if (units == "months")
68         return value >= Month.jan && value <= Month.dec;
69     else static if (units == "hours")
70         return value >= 0 && value <= 23;
71     else static if (units == "minutes")
72         return value >= 0 && value <= 59;
73     else static if (units == "seconds")
74         return value >= 0 && value <= 59;
75 }
76 
77 ///
78 version (mir_test)
79 @safe unittest
80 {
81     assert(valid!"hours"(12));
82     assert(!valid!"hours"(32));
83     assert(valid!"months"(12));
84     assert(!valid!"months"(13));
85 }
86 
87 /++
88 Returns whether the given day is valid for the given year and month.
89 Params:
90     units = The units of time to validate.
91     year  = The year of the day to validate.
92     month = The month of the day to validate (January is 1).
93     day   = The day to validate.
94 +/
95 bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc
96     if (units == "days")
97 {
98     return day > 0 && day <= maxDay(year, month);
99 }
100 
101 ///
102 version (mir_test)
103 @safe pure nothrow @nogc unittest
104 {
105     assert(valid!"days"(2016, 2, 29));
106     assert(!valid!"days"(2016, 2, 30));
107     assert(valid!"days"(2017, 2, 20));
108     assert(!valid!"days"(2017, 2, 29));
109 }
110 
111 ///
112 enum AllowDayOverflow : bool
113 {
114     ///
115     no,
116     ///
117     yes
118 }
119 
120 /++
121 Whether the given Gregorian Year is a leap year.
122 Params:
123     year = The year to to be tested.
124  +/
125 bool yearIsLeapYear(int year) @safe pure nothrow @nogc
126 {
127     if (year % 400 == 0)
128         return true;
129     if (year % 100 == 0)
130         return false;
131     return year % 4 == 0;
132 }
133 
134 ///
135 version (mir_test)
136 @safe unittest
137 {
138     foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010])
139     {
140         assert(!yearIsLeapYear(year));
141         assert(!yearIsLeapYear(-year));
142     }
143 
144     foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
145     {
146         assert(yearIsLeapYear(year));
147         assert(yearIsLeapYear(-year));
148     }
149 }
150 
151 version (mir_test)
152 @safe unittest
153 {
154     import std.format : format;
155     foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999,
156                     2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011])
157     {
158         assert(!yearIsLeapYear(year), format("year: %s.", year));
159         assert(!yearIsLeapYear(-year), format("year: %s.", year));
160     }
161 
162     foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
163     {
164         assert(yearIsLeapYear(year), format("year: %s.", year));
165         assert(yearIsLeapYear(-year), format("year: %s.", year));
166     }
167 }
168 
169 ///
170 enum Month : short
171 {
172     ///
173     jan = 1,
174     ///
175     feb,
176     ///
177     mar,
178     ///
179     apr,
180     ///
181     may,
182     ///
183     jun,
184     ///
185     jul,
186     ///
187     aug,
188     ///
189     sep,
190     ///
191     oct,
192     ///
193     nov,
194     ///
195     dec,
196 }
197 
198 version(D_Exceptions)
199 ///
200 class DateTimeException : Exception
201 {
202     ///
203     @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
204     {
205         super(msg, file, line, nextInChain);
206     }
207 
208     /// ditto
209     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
210     {
211         super(msg, file, line, nextInChain);
212     }
213 }
214 
215 version(D_Exceptions)
216 {
217     private static immutable InvalidMonth = new DateTimeException("Invalid Month");
218     private static immutable InvalidDay = new DateTimeException("Invalid Day");
219     private static immutable InvalidISOString = new DateTimeException("Invalid ISO String");
220     private static immutable InvalidISOExtendedString = new DateTimeException("Invalid ISO Extended String");
221     private static immutable InvalidSimpleString = new DateTimeException("Invalid Simple String");
222     private static immutable InvalidString = new DateTimeException("Invalid String");
223 }
224 
225 version (mir_test)
226 @safe unittest
227 {
228     initializeTests();
229 }
230 
231 /++
232     Represents the 7 days of the Gregorian week (Monday is 0).
233   +/
234 extern(C++, "mir")
235 enum DayOfWeek
236 {
237     mon = 0, ///
238     tue,     ///
239     wed,     ///
240     thu,     ///
241     fri,     ///
242     sat,     ///
243     sun,     ///
244 }
245 
246 ///
247 @serdeProxy!Timestamp
248 struct YearMonthDay
249 {
250     short year  = 1;
251     Month month = Month.jan;
252     ubyte day   = 1;
253 
254     ///
255     Timestamp timestamp() @safe pure nothrow @nogc @property
256     {
257         return Timestamp(year, cast(ubyte)month, day);
258     }
259 
260     ///
261     alias opCast(T : Timestamp) = timestamp;
262 
263     ///
264     version(mir_test)
265     unittest
266     {
267         import mir.timestamp;
268         auto timestamp = cast(Timestamp) YearMonthDay(2020, Month.may, 12);
269     }
270 
271     ///
272     this(short year, Month month, ubyte day) @safe pure nothrow @nogc
273     {
274         this.year = year;
275         this.month = month;
276         this.day = day;
277     }
278 
279     ///
280     this(Date date) @safe pure nothrow @nogc
281     {
282         this = date.yearMonthDay;
283     }
284 
285     version(D_Exceptions)
286     ///
287     this(Timestamp timestamp) @safe pure nothrow @nogc
288     {
289         if (timestamp.precision != Timestamp.Precision.day)
290         {
291             static immutable exc = new Exception("YearMonthDay: invalid timestamp precision");
292         }
293         with(timestamp) this(year, cast(Month)month, day);
294     }
295 
296     // Shares documentation with "years" version.
297     @safe pure nothrow @nogc
298     ref YearMonthDay add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
299         if (units == "months")
300     {
301         auto years = months / 12;
302         months %= 12;
303         auto newMonth = month + months;
304 
305         if (months < 0)
306         {
307             if (newMonth < 1)
308             {
309                 newMonth += 12;
310                 --years;
311             }
312         }
313         else if (newMonth > 12)
314         {
315             newMonth -= 12;
316             ++years;
317         }
318 
319         year += years;
320         month = cast(Month) newMonth;
321 
322         immutable currMaxDay = maxDay(year, month);
323         immutable overflow = day - currMaxDay;
324 
325         if (overflow > 0)
326         {
327             if (allowOverflow == AllowDayOverflow.yes)
328             {
329                 ++month;
330                 day = cast(ubyte) overflow;
331             }
332             else
333                 day = cast(ubyte) currMaxDay;
334         }
335 
336         return this;
337     }
338 
339     // Shares documentation with "years" version.
340     @safe pure nothrow @nogc
341     ref YearMonthDay add(string units)(long years, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
342         if (units == "years")
343     {
344         year += years;
345 
346         immutable currMaxDay = maxDay(year, month);
347         immutable overflow = day - currMaxDay;
348 
349         if (overflow > 0)
350         {
351             if (allowOverflow == AllowDayOverflow.yes)
352             {
353                 ++month;
354                 day = cast(ubyte) overflow;
355             }
356             else
357                 day = cast(ubyte) currMaxDay;
358         }
359         return this;
360     }
361 
362     /++
363         Day of the year this $(LREF Date) is on.
364       +/
365     @property int dayOfYear() const @safe pure nothrow @nogc
366     {
367         if (month >= Month.jan && month <= Month.dec)
368         {
369             immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
370             auto monthIndex = month - Month.jan;
371 
372             return lastDay[monthIndex] + day;
373         }
374         assert(0, "Invalid month.");
375     }
376 
377     ///
378     version (mir_test)
379     @safe unittest
380     {
381         assert(YearMonthDay(1999, cast(Month) 1, 1).dayOfYear == 1);
382         assert(YearMonthDay(1999, cast(Month) 12, 31).dayOfYear == 365);
383         assert(YearMonthDay(2000, cast(Month) 12, 31).dayOfYear == 366);
384     }
385 
386     /++
387         Whether this $(LREF Date) is in a leap year.
388      +/
389     @property bool isLeapYear() const @safe pure nothrow @nogc
390     {
391         return yearIsLeapYear(year);
392     }
393 
394     private void setDayOfYear(bool useExceptions = false)(int days)
395     {
396         immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
397 
398         bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear);
399 
400         static if (useExceptions)
401         {
402             if (dayOutOfRange) throw InvalidDay;
403         }
404         else
405         {
406             assert(!dayOutOfRange, "Invalid Day");
407         }
408 
409         foreach (i; 1 .. lastDay.length)
410         {
411             if (days <= lastDay[i])
412             {
413                 month = cast(Month)(cast(int) Month.jan + i - 1);
414                 day = cast(ubyte)(days - lastDay[i - 1]);
415                 return;
416             }
417         }
418         assert(0, "Invalid day of the year.");
419     }
420 
421     /++
422         The last day in the month that this $(LREF Date) is in.
423       +/
424     @property ubyte daysInMonth() const @safe pure nothrow @nogc
425     {
426         return maxDay(year, month);
427     }
428 
429     /++
430         Whether the current year is a date in A.D.
431       +/
432     @property bool isAD() const @safe pure nothrow @nogc
433     {
434         return year > 0;
435     }
436 }
437 
438 /++
439     Represents a date in the
440     $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic
441     Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years
442     are A.D. Non-positive years are B.C.
443 
444     Year, month, and day are kept separately internally so that $(D Date) is
445     optimized for calendar-based operations.
446 
447     $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian
448     leap year calculations for its entire length. As per
449     $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as
450     year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C.
451     as a positive integer with 1 B.C. being the year prior to 1 A.D.
452 
453     Year 0 is a leap year.
454  +/
455 extern(C++, "boost", "gregorian")
456 extern(C++, class)
457 @serdeProxy!YearMonthDay
458 struct date
459 {
460 extern(D):
461 public:
462 
463     private enum _julianShift = 1_721_425;
464 
465     ///
466     uint toHash() @safe pure nothrow @nogc const scope
467     {
468         return _julianDay;
469     }
470 
471     /++
472         Throws:
473             $(LREF DateTimeException) if the resulting
474             $(LREF Date) would not be valid.
475 
476         Params:
477             _year  = Year of the Gregorian Calendar. Positive values are A.D.
478                     Non-positive values are B.C. with year 0 being the year
479                     prior to 1 A.D.
480             _month = Month of the year (January is 1).
481             _day   = Day of the month.
482      +/
483     pragma(inline, false)
484     static Date trustedCreate(int _year, int _month, int _day) @safe pure @nogc nothrow
485     {
486         Date ret;
487         immutable int[] lastDay = yearIsLeapYear(_year) ? lastDayLeap : lastDayNonLeap;
488         auto monthIndex = _month - Month.jan;
489 
490         const dayOfYear = lastDay[monthIndex] + _day;
491 
492         if (_month >= Month.jan && _month <= Month.dec) {} else
493             assert(0, "Invalid month.");
494         if (_year > 0)
495         {
496             if (_year == 1)
497             {
498                 ret._julianDay = dayOfYear;
499                 goto R;
500             }
501 
502             int years = _year - 1;
503             auto days = (years / 400) * daysIn400Years;
504             years %= 400;
505 
506             days += (years / 100) * daysIn100Years;
507             years %= 100;
508 
509             days += (years / 4) * daysIn4Years;
510             years %= 4;
511 
512             days += years * daysInYear;
513 
514             days += dayOfYear;
515 
516             ret._julianDay = days;
517         }
518         else if (_year == 0)
519         {
520             ret._julianDay = dayOfYear - daysInLeapYear;
521         }
522         else
523         {
524             int years = _year;
525             auto days = (years / 400) * daysIn400Years;
526             years %= 400;
527 
528             days += (years / 100) * daysIn100Years;
529             years %= 100;
530 
531             days += (years / 4) * daysIn4Years;
532             years %= 4;
533 
534             if (years < 0)
535             {
536                 days -= daysInLeapYear;
537                 ++years;
538 
539                 days += years * daysInYear;
540 
541                 days -= daysInYear - dayOfYear;
542             }
543             else
544                 days -= daysInLeapYear - dayOfYear;
545 
546             ret._julianDay = days;
547         }
548     R:
549         ret._julianDay += _julianShift;
550         return ret;
551     }
552 
553     ///
554     Timestamp timestamp() @safe pure nothrow @nogc const @property
555     {
556         return yearMonthDay.timestamp;
557     }
558 
559     version(D_Exceptions)
560     ///
561     this(Timestamp timestamp) @safe pure @nogc
562     {
563         if (timestamp.precision != Timestamp.Precision.day)
564         {
565             static immutable exc = new Exception("Date: invalid timestamp precision");
566         }
567     }
568 
569     version(D_Exceptions)
570     ///
571     this(scope const(char)[] str) @safe pure @nogc
572     {
573         this = fromString(str);
574     }
575 
576     version(D_Exceptions)
577     ///
578     this(YearMonthDay ymd) @safe pure @nogc
579     {
580         with(ymd) this(year, month, day);
581     }
582 
583     version(D_Exceptions)
584     ///
585     this(int _year, int _month, int _day) @safe pure @nogc
586     {
587         if (!valid!"months"(_month))
588             throw InvalidMonth;
589         if (!valid!"days"(_year, cast(Month) _month, _day))
590             throw InvalidDay;
591         this = trustedCreate(_year, _month, _day);
592     }
593 
594     ///
595     static bool fromYMD(int _year, int _month, int _day, out Date value) @safe pure nothrow @nogc
596     {
597         if (valid!"months"(_month) && valid!"days"(_year, cast(Month) _month, _day))
598         {
599             value = trustedCreate(_year, _month, _day);
600             return true;
601         }
602         return false;
603     }
604 
605     version (mir_test)
606     @safe unittest
607     {
608         import std.exception : assertNotThrown;
609         // assert(Date(0, 12, 31) == Date.init);
610 
611         // Test A.D.
612         assertThrown!DateTimeException(Date(1, 0, 1));
613         assertThrown!DateTimeException(Date(1, 1, 0));
614         assertThrown!DateTimeException(Date(1999, 13, 1));
615         assertThrown!DateTimeException(Date(1999, 1, 32));
616         assertThrown!DateTimeException(Date(1999, 2, 29));
617         assertThrown!DateTimeException(Date(2000, 2, 30));
618         assertThrown!DateTimeException(Date(1999, 3, 32));
619         assertThrown!DateTimeException(Date(1999, 4, 31));
620         assertThrown!DateTimeException(Date(1999, 5, 32));
621         assertThrown!DateTimeException(Date(1999, 6, 31));
622         assertThrown!DateTimeException(Date(1999, 7, 32));
623         assertThrown!DateTimeException(Date(1999, 8, 32));
624         assertThrown!DateTimeException(Date(1999, 9, 31));
625         assertThrown!DateTimeException(Date(1999, 10, 32));
626         assertThrown!DateTimeException(Date(1999, 11, 31));
627         assertThrown!DateTimeException(Date(1999, 12, 32));
628 
629         assertNotThrown!DateTimeException(Date(1999, 1, 31));
630         assertNotThrown!DateTimeException(Date(1999, 2, 28));
631         assertNotThrown!DateTimeException(Date(2000, 2, 29));
632         assertNotThrown!DateTimeException(Date(1999, 3, 31));
633         assertNotThrown!DateTimeException(Date(1999, 4, 30));
634         assertNotThrown!DateTimeException(Date(1999, 5, 31));
635         assertNotThrown!DateTimeException(Date(1999, 6, 30));
636         assertNotThrown!DateTimeException(Date(1999, 7, 31));
637         assertNotThrown!DateTimeException(Date(1999, 8, 31));
638         assertNotThrown!DateTimeException(Date(1999, 9, 30));
639         assertNotThrown!DateTimeException(Date(1999, 10, 31));
640         assertNotThrown!DateTimeException(Date(1999, 11, 30));
641         assertNotThrown!DateTimeException(Date(1999, 12, 31));
642 
643         // Test B.C.
644         assertNotThrown!DateTimeException(Date(0, 1, 1));
645         assertNotThrown!DateTimeException(Date(-1, 1, 1));
646         assertNotThrown!DateTimeException(Date(-1, 12, 31));
647         assertNotThrown!DateTimeException(Date(-1, 2, 28));
648         assertNotThrown!DateTimeException(Date(-4, 2, 29));
649 
650         assertThrown!DateTimeException(Date(-1, 2, 29));
651         assertThrown!DateTimeException(Date(-2, 2, 29));
652         assertThrown!DateTimeException(Date(-3, 2, 29));
653     }
654 
655 
656     /++
657         Params:
658             day = Julian day.
659      +/
660     this(int day) @safe pure nothrow @nogc
661     {
662         _julianDay = day;
663     }
664 
665     version (mir_test)
666     @safe unittest
667     {
668         import std.range : chain;
669 
670         // Test A.D.
671         // foreach (gd; chain(testGregDaysBC, testGregDaysAD))
672         //     assert(Date(gd.day) == gd.date);
673     }
674 
675 
676     /++
677         Compares this $(LREF Date) with the given $(LREF Date).
678 
679         Returns:
680             $(BOOKTABLE,
681             $(TR $(TD this &lt; rhs) $(TD &lt; 0))
682             $(TR $(TD this == rhs) $(TD 0))
683             $(TR $(TD this &gt; rhs) $(TD &gt; 0))
684             )
685      +/
686     int opCmp(Date rhs) const @safe pure nothrow @nogc
687     {
688         return this._julianDay - rhs._julianDay;
689     }
690 
691     version (mir_test)
692     @safe unittest
693     {
694         // Test A.D.
695         // assert(Date(0, 12, 31).opCmp(Date.init) == 0);
696 
697         assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0);
698         assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0);
699         assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0);
700 
701         assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0);
702         assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0);
703 
704         assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0);
705 
706         assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0);
707         assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0);
708         assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0);
709         assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0);
710         assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0);
711         assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0);
712 
713         assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0);
714         assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
715         assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0);
716         assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0);
717         assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0);
718         assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
719 
720         // Test B.C.
721         assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0);
722         assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0);
723         assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0);
724         assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0);
725 
726         assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0);
727         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0);
728 
729         assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0);
730 
731         assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0);
732         assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0);
733         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0);
734         assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0);
735         assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
736         assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0);
737 
738         assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0);
739         assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0);
740         assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
741         assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0);
742         assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0);
743         assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0);
744 
745         // Test Both
746         assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0);
747         assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0);
748 
749         assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0);
750         assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0);
751 
752         assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0);
753         assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0);
754 
755         assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0);
756         assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0);
757 
758         assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0);
759         assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0);
760 
761         auto date = Date(1999, 7, 6);
762         const cdate = Date(1999, 7, 6);
763         immutable idate = Date(1999, 7, 6);
764         assert(date.opCmp(date) == 0);
765         assert(date.opCmp(cdate) == 0);
766         assert(date.opCmp(idate) == 0);
767         assert(cdate.opCmp(date) == 0);
768         assert(cdate.opCmp(cdate) == 0);
769         assert(cdate.opCmp(idate) == 0);
770         assert(idate.opCmp(date) == 0);
771         assert(idate.opCmp(cdate) == 0);
772         assert(idate.opCmp(idate) == 0);
773     }
774 
775     /++
776         Day of the week this $(LREF Date) is on.
777       +/
778     @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc
779     {
780         return getDayOfWeek(_julianDay);
781     }
782 
783     version (mir_test)
784     @safe unittest
785     {
786         const cdate = Date(1999, 7, 6);
787         immutable idate = Date(1999, 7, 6);
788         assert(cdate.dayOfWeek == DayOfWeek.tue);
789         static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun));
790         assert(idate.dayOfWeek == DayOfWeek.tue);
791         static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun));
792     }
793 
794     /++
795         The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
796      +/
797     @property int dayOfGregorianCal() const @safe pure nothrow @nogc
798     {
799         return _julianDay - _julianShift;
800     }
801 
802     ///
803     version (mir_test)
804     @safe unittest
805     {
806         assert(Date(1, 1, 1).dayOfGregorianCal == 1);
807         assert(Date(1, 12, 31).dayOfGregorianCal == 365);
808         assert(Date(2, 1, 1).dayOfGregorianCal == 366);
809 
810         assert(Date(0, 12, 31).dayOfGregorianCal == 0);
811         assert(Date(0, 1, 1).dayOfGregorianCal == -365);
812         assert(Date(-1, 12, 31).dayOfGregorianCal == -366);
813 
814         assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120);
815         assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137);
816     }
817 
818     version (mir_test)
819     @safe unittest
820     {
821         import std.range : chain;
822 
823         foreach (gd; chain(testGregDaysBC, testGregDaysAD))
824             assert(gd.date.dayOfGregorianCal == gd.day);
825 
826         auto date = Date(1999, 7, 6);
827         const cdate = Date(1999, 7, 6);
828         immutable idate = Date(1999, 7, 6);
829         assert(date.dayOfGregorianCal == 729_941);
830         assert(cdate.dayOfGregorianCal == 729_941);
831         assert(idate.dayOfGregorianCal == 729_941);
832     }
833 
834     /++
835         The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
836 
837         Params:
838             day = The day of the Gregorian Calendar to set this $(LREF Date) to.
839      +/
840     @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc
841     {
842         this = Date(day + _julianShift);
843     }
844 
845     ///
846     version (mir_test)
847     @safe unittest
848     {
849         auto date = Date.init;
850         date.dayOfGregorianCal = 1;
851         assert(date == Date(1, 1, 1));
852 
853         date.dayOfGregorianCal = 365;
854         assert(date == Date(1, 12, 31));
855 
856         date.dayOfGregorianCal = 366;
857         assert(date == Date(2, 1, 1));
858 
859         date.dayOfGregorianCal = 0;
860         assert(date == Date(0, 12, 31));
861 
862         date.dayOfGregorianCal = -365;
863         assert(date == Date(-0, 1, 1));
864 
865         date.dayOfGregorianCal = -366;
866         assert(date == Date(-1, 12, 31));
867 
868         date.dayOfGregorianCal = 730_120;
869         assert(date == Date(2000, 1, 1));
870 
871         date.dayOfGregorianCal = 734_137;
872         assert(date == Date(2010, 12, 31));
873     }
874 
875     version (mir_test)
876     @safe unittest
877     {
878         auto date = Date(1999, 7, 6);
879         const cdate = Date(1999, 7, 6);
880         immutable idate = Date(1999, 7, 6);
881         date.dayOfGregorianCal = 187;
882         assert(date.dayOfGregorianCal == 187);
883         static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187));
884         static assert(!__traits(compiles, idate.dayOfGregorianCal = 187));
885     }
886 
887     private enum uint _startDict = Date(1900, 1, 1)._julianDay; // [
888     private enum uint _endDict = Date(2040, 1, 1)._julianDay; // )
889     static immutable _dict = ()
890     {
891         YearMonthDay[Date._endDict - Date._startDict] dict;
892         foreach (uint i; 0 .. dict.length)
893             dict[i] = Date(i + Date._startDict).yearMonthDayImpl;
894         return dict;
895     }();
896 
897     ///
898     YearMonthDay yearMonthDay() const @safe pure nothrow @nogc @property
899     {
900         uint day = _julianDay;
901         if (day < _endDict)
902         {
903             import mir.checkedint: subu;
904             bool overflow;
905             auto index = subu(day, _startDict, overflow);
906             if (!overflow)
907                 return _dict[index];
908         }
909         return yearMonthDayImpl;
910     }
911 
912     ///
913     short year() const @safe pure nothrow @nogc @property
914     {
915         return yearMonthDay.year;
916     }
917 
918     ///
919     Month month() const @safe pure nothrow @nogc @property
920     {
921         return yearMonthDay.month;
922     }
923 
924     ///
925     ubyte day() const @safe pure nothrow @nogc @property
926     {
927         return yearMonthDay.day;
928     }
929 
930     pragma(inline, false)
931     YearMonthDay yearMonthDayImpl() const @safe pure nothrow @nogc @property
932     {
933         YearMonthDay ymd;
934         int days = dayOfGregorianCal;
935         with(ymd)
936         if (days > 0)
937         {
938             int years = (days / daysIn400Years) * 400 + 1;
939             days %= daysIn400Years;
940 
941             {
942                 immutable tempYears = days / daysIn100Years;
943 
944                 if (tempYears == 4)
945                 {
946                     years += 300;
947                     days -= daysIn100Years * 3;
948                 }
949                 else
950                 {
951                     years += tempYears * 100;
952                     days %= daysIn100Years;
953                 }
954             }
955 
956             years += (days / daysIn4Years) * 4;
957             days %= daysIn4Years;
958 
959             {
960                 immutable tempYears = days / daysInYear;
961 
962                 if (tempYears == 4)
963                 {
964                     years += 3;
965                     days -= daysInYear * 3;
966                 }
967                 else
968                 {
969                     years += tempYears;
970                     days %= daysInYear;
971                 }
972             }
973 
974             if (days == 0)
975             {
976                 year = cast(short)(years - 1);
977                 month = Month.dec;
978                 day = 31;
979             }
980             else
981             {
982                 year = cast(short) years;
983 
984                 setDayOfYear(days);
985             }
986         }
987         else if (days <= 0 && -days < daysInLeapYear)
988         {
989             year = 0;
990 
991             setDayOfYear(daysInLeapYear + days);
992         }
993         else
994         {
995             days += daysInLeapYear - 1;
996             int years = (days / daysIn400Years) * 400 - 1;
997             days %= daysIn400Years;
998 
999             {
1000                 immutable tempYears = days / daysIn100Years;
1001 
1002                 if (tempYears == -4)
1003                 {
1004                     years -= 300;
1005                     days += daysIn100Years * 3;
1006                 }
1007                 else
1008                 {
1009                     years += tempYears * 100;
1010                     days %= daysIn100Years;
1011                 }
1012             }
1013 
1014             years += (days / daysIn4Years) * 4;
1015             days %= daysIn4Years;
1016 
1017             {
1018                 immutable tempYears = days / daysInYear;
1019 
1020                 if (tempYears == -4)
1021                 {
1022                     years -= 3;
1023                     days += daysInYear * 3;
1024                 }
1025                 else
1026                 {
1027                     years += tempYears;
1028                     days %= daysInYear;
1029                 }
1030             }
1031 
1032             if (days == 0)
1033             {
1034                 year = cast(short)(years + 1);
1035                 month = Month.jan;
1036                 day = 1;
1037             }
1038             else
1039             {
1040                 year = cast(short) years;
1041                 immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1;
1042 
1043                 setDayOfYear(newDoY);
1044             }
1045         }
1046         return ymd;
1047     }
1048 
1049     /++
1050      $(LREF Date) for the last day in the quarter that this $(LREF Date) is in.
1051     +/
1052     @property Date endOfQuarter() const @safe pure nothrow @nogc
1053     {
1054         with(yearMonthDay)
1055         {
1056             int d = _julianDay - day;
1057             final switch (month) with(Month)
1058             {
1059                 case jan: d += maxDay(year, jan); goto case;
1060                 case feb: d += maxDay(year, feb); goto case;
1061                 case mar: d += maxDay(year, mar); break;
1062 
1063                 case apr: d += maxDay(year, apr); goto case;
1064                 case may: d += maxDay(year, may); goto case;
1065                 case jun: d += maxDay(year, jun); break;
1066 
1067                 case jul: d += maxDay(year, jul); goto case;
1068                 case aug: d += maxDay(year, aug); goto case;
1069                 case sep: d += maxDay(year, sep); break;
1070 
1071                 case oct: d += maxDay(year, oct); goto case;
1072                 case nov: d += maxDay(year, nov); goto case;
1073                 case dec: d += maxDay(year, dec); break;
1074             }
1075             return Date(d);
1076         }
1077     }
1078 
1079     ///
1080     version (mir_test)
1081     @safe unittest
1082     {
1083         assert(Date(1999, 1, 6).endOfQuarter == Date(1999, 3, 31));
1084         assert(Date(1999, 2, 7).endOfQuarter == Date(1999, 3, 31));
1085         assert(Date(2000, 2, 7).endOfQuarter == Date(2000, 3, 31));
1086         assert(Date(2000, 6, 4).endOfQuarter == Date(2000, 6, 30));
1087     }
1088 
1089     /++
1090      $(LREF Date) for the last day in the month that this $(LREF Date) is in.
1091     +/
1092     @property Date endOfMonth() const @safe pure nothrow @nogc
1093     {
1094         with(yearMonthDay)
1095             return Date(_julianDay + maxDay(year, month) - day);
1096     }
1097 
1098     ///
1099     version (mir_test)
1100     @safe unittest
1101     {
1102         assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31));
1103         assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28));
1104         assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29));
1105         assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30));
1106     }
1107 
1108     version (mir_test)
1109     @safe unittest
1110     {
1111         // Test A.D.
1112         assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31));
1113         assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28));
1114         assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29));
1115         assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31));
1116         assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30));
1117         assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31));
1118         assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30));
1119         assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31));
1120         assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31));
1121         assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30));
1122         assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31));
1123         assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30));
1124         assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31));
1125 
1126         // Test B.C.
1127         assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31));
1128         assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28));
1129         assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29));
1130         assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31));
1131         assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30));
1132         assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31));
1133         assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30));
1134         assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31));
1135         assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31));
1136         assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30));
1137         assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31));
1138         assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30));
1139         assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31));
1140 
1141         const cdate = Date(1999, 7, 6);
1142         immutable idate = Date(1999, 7, 6);
1143         static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30)));
1144         static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30)));
1145     }
1146 
1147     ///
1148     int opBinary(string op : "-")(Date lhs) const
1149     {
1150         return _julianDay - lhs._julianDay;
1151     }
1152 
1153     ///
1154     Date opBinary(string op : "+")(int lhs) const
1155     {
1156         return Date(_julianDay + lhs);
1157     }
1158 
1159     ///
1160     Date opBinaryRight(string op : "+")(int lhs) const
1161     {
1162         return Date(_julianDay + lhs);
1163     }
1164 
1165     ///
1166     Date opBinary(string op : "-")(int lhs) const
1167     {
1168         return Date(_julianDay - lhs);
1169     }
1170 
1171     const nothrow @nogc pure @safe
1172     Date add(string units)(long amount, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
1173     {
1174         with(yearMonthDay.add!units(amount)) return trustedCreate(year, month, day);
1175     }
1176 
1177     /++
1178         The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this
1179         $(LREF Date) at noon (since the Julian day changes at noon).
1180       +/
1181     @property int julianDay() const @safe pure nothrow @nogc
1182     {
1183         return _julianDay;
1184     }
1185 
1186     version (mir_test)
1187     @safe unittest
1188     {
1189         assert(Date(-4713, 11, 24).julianDay == 0);
1190         assert(Date(0, 12, 31).julianDay == _julianShift);
1191         assert(Date(1, 1, 1).julianDay == 1_721_426);
1192         assert(Date(1582, 10, 15).julianDay == 2_299_161);
1193         assert(Date(1858, 11, 17).julianDay == 2_400_001);
1194         assert(Date(1982, 1, 4).julianDay == 2_444_974);
1195         assert(Date(1996, 3, 31).julianDay == 2_450_174);
1196         assert(Date(2010, 8, 24).julianDay == 2_455_433);
1197 
1198         const cdate = Date(1999, 7, 6);
1199         immutable idate = Date(1999, 7, 6);
1200         assert(cdate.julianDay == 2_451_366);
1201         assert(idate.julianDay == 2_451_366);
1202     }
1203 
1204 
1205     /++
1206         The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for
1207         any time on this date (since, the modified Julian day changes at
1208         midnight).
1209       +/
1210     @property long modJulianDay() const @safe pure nothrow @nogc
1211     {
1212         return julianDay - 2_400_001;
1213     }
1214 
1215     version (mir_test)
1216     @safe unittest
1217     {
1218         assert(Date(1858, 11, 17).modJulianDay == 0);
1219         assert(Date(2010, 8, 24).modJulianDay == 55_432);
1220 
1221         const cdate = Date(1999, 7, 6);
1222         immutable idate = Date(1999, 7, 6);
1223         assert(cdate.modJulianDay == 51_365);
1224         assert(idate.modJulianDay == 51_365);
1225     }
1226 
1227     version(D_BetterC){} else
1228     private string toStringImpl(alias fun)() const @safe pure nothrow
1229     {
1230         import mir.appender: UnsafeArrayBuffer;
1231         char[16] buffer = void;
1232         auto w = UnsafeArrayBuffer!char(buffer);
1233         fun(w);
1234         return w.data.idup;
1235     }
1236 
1237     version(D_BetterC){} else
1238     /++
1239         Converts this $(LREF Date) to a string with the format `YYYYMMDD`.
1240         If `writer` is set, the resulting string will be written directly
1241         to it.
1242 
1243         Returns:
1244             A `string` when not using an output range; `void` otherwise.
1245       +/
1246     string toISOString() const @safe pure nothrow
1247     {
1248         return toStringImpl!toISOString;
1249     }
1250 
1251     ///
1252     version (mir_test)
1253     @safe unittest
1254     {
1255         assert(Date.init.toISOString == "null");
1256         assert(Date(2010, 7, 4).toISOString == "20100704");
1257         assert(Date(1998, 12, 25).toISOString == "19981225");
1258         assert(Date(0, 1, 5).toISOString == "00000105");
1259         assert(Date(-4, 1, 5).toISOString == "-00040105", Date(-4, 1, 5).toISOString());
1260     }
1261 
1262     version (mir_test)
1263     @safe unittest
1264     {
1265         // Test A.D.
1266         assert(Date(9, 12, 4).toISOString == "00091204");
1267         assert(Date(99, 12, 4).toISOString == "00991204");
1268         assert(Date(999, 12, 4).toISOString == "09991204");
1269         assert(Date(9999, 7, 4).toISOString == "99990704");
1270         assert(Date(10000, 10, 20).toISOString == "+100001020");
1271 
1272         // Test B.C.
1273         assert(Date(0, 12, 4).toISOString == "00001204");
1274         assert(Date(-9, 12, 4).toISOString == "-00091204");
1275         assert(Date(-99, 12, 4).toISOString == "-00991204");
1276         assert(Date(-999, 12, 4).toISOString == "-09991204");
1277         assert(Date(-9999, 7, 4).toISOString == "-99990704");
1278         assert(Date(-10000, 10, 20).toISOString == "-100001020");
1279 
1280         const cdate = Date(1999, 7, 6);
1281         immutable idate = Date(1999, 7, 6);
1282         assert(cdate.toISOString == "19990706");
1283         assert(idate.toISOString == "19990706");
1284     }
1285 
1286     /// ditto
1287     void toISOString(W)(scope ref W w) const scope
1288         if (isOutputRange!(W, char))
1289     {
1290         import mir.format: printZeroPad;
1291         if(this == Date.init)
1292         {
1293             w.put("null");
1294             return;
1295         }
1296         with(yearMonthDay)
1297         {
1298             if (year >= 10_000)
1299                 w.put('+');
1300             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
1301             w.printZeroPad(cast(uint)month, 2);
1302             w.printZeroPad(day, 2);
1303         }
1304     }
1305 
1306     version (mir_test)
1307     @safe unittest
1308     {
1309         auto date = Date(1999, 7, 6);
1310         const cdate = Date(1999, 7, 6);
1311         immutable idate = Date(1999, 7, 6);
1312         assert(date.toString);
1313         assert(cdate.toString);
1314         assert(idate.toString);
1315     }
1316 
1317     version(D_BetterC){} else
1318     /++
1319     Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`.
1320     If `writer` is set, the resulting string will be written directly
1321     to it.
1322 
1323     Returns:
1324         A `string` when not using an output range; `void` otherwise.
1325       +/
1326     string toISOExtString() const @safe pure nothrow
1327     {
1328         return toStringImpl!toISOExtString;
1329     }
1330 
1331     ///ditto
1332     alias toString = toISOExtString;
1333 
1334     ///
1335     version (mir_test)
1336     @safe unittest
1337     {
1338         assert(Date.init.toISOExtString == "null");
1339         assert(Date(2010, 7, 4).toISOExtString == "2010-07-04");
1340         assert(Date(1998, 12, 25).toISOExtString == "1998-12-25");
1341         assert(Date(0, 1, 5).toISOExtString == "0000-01-05");
1342         assert(Date(-4, 1, 5).toISOExtString == "-0004-01-05");
1343     }
1344 
1345     version (mir_test)
1346     @safe pure unittest
1347     {
1348         import std.array : appender;
1349 
1350         auto w = appender!(char[])();
1351         Date(2010, 7, 4).toISOString(w);
1352         assert(w.data == "20100704");
1353         w.clear();
1354         Date(1998, 12, 25).toISOString(w);
1355         assert(w.data == "19981225");
1356     }
1357 
1358     version (mir_test)
1359     @safe unittest
1360     {
1361         // Test A.D.
1362         assert(Date(9, 12, 4).toISOExtString == "0009-12-04");
1363         assert(Date(99, 12, 4).toISOExtString == "0099-12-04");
1364         assert(Date(999, 12, 4).toISOExtString == "0999-12-04");
1365         assert(Date(9999, 7, 4).toISOExtString == "9999-07-04");
1366         assert(Date(10000, 10, 20).toISOExtString == "+10000-10-20");
1367 
1368         // Test B.C.
1369         assert(Date(0, 12, 4).toISOExtString == "0000-12-04");
1370         assert(Date(-9, 12, 4).toISOExtString == "-0009-12-04");
1371         assert(Date(-99, 12, 4).toISOExtString == "-0099-12-04");
1372         assert(Date(-999, 12, 4).toISOExtString == "-0999-12-04");
1373         assert(Date(-9999, 7, 4).toISOExtString == "-9999-07-04");
1374         assert(Date(-10000, 10, 20).toISOExtString == "-10000-10-20");
1375 
1376         const cdate = Date(1999, 7, 6);
1377         immutable idate = Date(1999, 7, 6);
1378         assert(cdate.toISOExtString == "1999-07-06");
1379         assert(idate.toISOExtString == "1999-07-06");
1380     }
1381 
1382     /// ditto
1383     void toISOExtString(W)(scope ref W w) const scope
1384         if (isOutputRange!(W, char))
1385     {
1386         import mir.format: printZeroPad;
1387         if(this == Date.init)
1388         {
1389             w.put("null");
1390             return;
1391         }
1392         with(yearMonthDay)
1393         {
1394             if (year >= 10_000)
1395                 w.put('+');
1396             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
1397             w.put('-');
1398             w.printZeroPad(cast(uint)month, 2);
1399             w.put('-');
1400             w.printZeroPad(day, 2);
1401         }
1402     }
1403 
1404     version (mir_test)
1405     @safe pure unittest
1406     {
1407         import std.array : appender;
1408 
1409         auto w = appender!(char[])();
1410         Date(2010, 7, 4).toISOExtString(w);
1411         assert(w.data == "2010-07-04");
1412         w.clear();
1413         Date(-4, 1, 5).toISOExtString(w);
1414         assert(w.data == "-0004-01-05");
1415     }
1416 
1417     version(D_BetterC){} else
1418     /++
1419         Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`.
1420         If `writer` is set, the resulting string will be written directly
1421         to it.
1422 
1423         Returns:
1424             A `string` when not using an output range; `void` otherwise.
1425       +/
1426     string toSimpleString() const @safe pure nothrow
1427     {
1428         return toStringImpl!toSimpleString;
1429     }
1430 
1431     ///
1432     version (mir_test)
1433     @safe unittest
1434     {
1435         assert(Date.init.toSimpleString == "null");
1436         assert(Date(2010, 7, 4).toSimpleString == "2010-Jul-04");
1437         assert(Date(1998, 12, 25).toSimpleString == "1998-Dec-25");
1438         assert(Date(0, 1, 5).toSimpleString == "0000-Jan-05");
1439         assert(Date(-4, 1, 5).toSimpleString == "-0004-Jan-05");
1440     }
1441 
1442     version (mir_test)
1443     @safe unittest
1444     {
1445         // Test A.D.
1446         assert(Date(9, 12, 4).toSimpleString == "0009-Dec-04");
1447         assert(Date(99, 12, 4).toSimpleString == "0099-Dec-04");
1448         assert(Date(999, 12, 4).toSimpleString == "0999-Dec-04");
1449         assert(Date(9999, 7, 4).toSimpleString == "9999-Jul-04");
1450         assert(Date(10000, 10, 20).toSimpleString == "+10000-Oct-20");
1451 
1452         // Test B.C.
1453         assert(Date(0, 12, 4).toSimpleString == "0000-Dec-04");
1454         assert(Date(-9, 12, 4).toSimpleString == "-0009-Dec-04");
1455         assert(Date(-99, 12, 4).toSimpleString == "-0099-Dec-04");
1456         assert(Date(-999, 12, 4).toSimpleString == "-0999-Dec-04");
1457         assert(Date(-9999, 7, 4).toSimpleString == "-9999-Jul-04");
1458         assert(Date(-10000, 10, 20).toSimpleString == "-10000-Oct-20");
1459 
1460         const cdate = Date(1999, 7, 6);
1461         immutable idate = Date(1999, 7, 6);
1462         assert(cdate.toSimpleString == "1999-Jul-06");
1463         assert(idate.toSimpleString == "1999-Jul-06");
1464     }
1465 
1466     /// ditto
1467     void toSimpleString(W)(scope ref W w) const scope
1468         if (isOutputRange!(W, char))
1469     {
1470         import mir.format: printZeroPad;
1471         if(this == Date.init)
1472         {
1473             w.put("null");
1474             return;
1475         }
1476         with(yearMonthDay)
1477         {
1478             if (year >= 10_000)
1479                 w.put('+');
1480             w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6);
1481             w.put('-');
1482             w.put(month.monthToString);
1483             w.put('-');
1484             w.printZeroPad(day, 2);
1485         }
1486     }
1487 
1488     version (mir_test)
1489     @safe pure unittest
1490     {
1491         import std.array : appender;
1492 
1493         auto w = appender!(char[])();
1494         Date(9, 12, 4).toSimpleString(w);
1495         assert(w.data == "0009-Dec-04");
1496         w.clear();
1497         Date(-10000, 10, 20).toSimpleString(w);
1498         assert(w.data == "-10000-Oct-20");
1499     }
1500 
1501     /++
1502     Creates a $(LREF Date) from a string with the format YYYYMMDD.
1503 
1504     Params:
1505         str = A string formatted in the way that $(LREF .date.toISOString) formats dates.
1506         value = (optional) result value.
1507 
1508     Throws:
1509         $(LREF DateTimeException) if the given string is
1510         not in the correct format or if the resulting $(LREF Date) would not
1511         be valid. Two arguments overload is `nothrow`.
1512     Returns:
1513         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
1514     +/
1515     static bool fromISOString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
1516         if (isSomeChar!C)
1517     {
1518         import mir.parse: fromString;
1519 
1520         if (str.length < 8)
1521             return false;
1522 
1523         auto yearStr = str[0 .. $ - 4];
1524 
1525         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
1526             return false;
1527 
1528         uint day, month;
1529         int year;
1530 
1531         return 
1532             fromString(str[$ - 2 .. $], day)
1533          && fromString(str[$ - 4 .. $ - 2], month)
1534          && fromString(yearStr, year)
1535          && fromYMD(year, month, day, value);
1536     }
1537 
1538     /// ditto
1539     static Date fromISOString(C)(scope const(C)[] str) @safe pure
1540         if (isSomeChar!C)
1541     {
1542         Date ret;
1543         if (fromISOString(str, ret))
1544             return ret;
1545         throw InvalidISOString;
1546     }
1547 
1548     ///
1549     version (mir_test)
1550     @safe unittest
1551     {
1552         assert(Date.fromISOString("20100704") == Date(2010, 7, 4));
1553         assert(Date.fromISOString("19981225") == Date(1998, 12, 25));
1554         assert(Date.fromISOString("00000105") == Date(0, 1, 5));
1555         assert(Date.fromISOString("-00040105") == Date(-4, 1, 5));
1556     }
1557 
1558     version (mir_test)
1559     @safe unittest
1560     {
1561         assertThrown!DateTimeException(Date.fromISOString(""));
1562         assertThrown!DateTimeException(Date.fromISOString("990704"));
1563         assertThrown!DateTimeException(Date.fromISOString("0100704"));
1564         assertThrown!DateTimeException(Date.fromISOString("2010070"));
1565         assertThrown!DateTimeException(Date.fromISOString("120100704"));
1566         assertThrown!DateTimeException(Date.fromISOString("-0100704"));
1567         assertThrown!DateTimeException(Date.fromISOString("+0100704"));
1568         assertThrown!DateTimeException(Date.fromISOString("2010070a"));
1569         assertThrown!DateTimeException(Date.fromISOString("20100a04"));
1570         assertThrown!DateTimeException(Date.fromISOString("2010a704"));
1571 
1572         assertThrown!DateTimeException(Date.fromISOString("99-07-04"));
1573         assertThrown!DateTimeException(Date.fromISOString("010-07-04"));
1574         assertThrown!DateTimeException(Date.fromISOString("2010-07-0"));
1575         assertThrown!DateTimeException(Date.fromISOString("12010-07-04"));
1576         assertThrown!DateTimeException(Date.fromISOString("-010-07-04"));
1577         assertThrown!DateTimeException(Date.fromISOString("+010-07-04"));
1578         assertThrown!DateTimeException(Date.fromISOString("2010-07-0a"));
1579         assertThrown!DateTimeException(Date.fromISOString("2010-0a-04"));
1580         assertThrown!DateTimeException(Date.fromISOString("2010-a7-04"));
1581         assertThrown!DateTimeException(Date.fromISOString("2010/07/04"));
1582         assertThrown!DateTimeException(Date.fromISOString("2010/7/04"));
1583         assertThrown!DateTimeException(Date.fromISOString("2010/7/4"));
1584         assertThrown!DateTimeException(Date.fromISOString("2010/07/4"));
1585         assertThrown!DateTimeException(Date.fromISOString("2010-7-04"));
1586         assertThrown!DateTimeException(Date.fromISOString("2010-7-4"));
1587         assertThrown!DateTimeException(Date.fromISOString("2010-07-4"));
1588 
1589         assertThrown!DateTimeException(Date.fromISOString("99Jul04"));
1590         assertThrown!DateTimeException(Date.fromISOString("010Jul04"));
1591         assertThrown!DateTimeException(Date.fromISOString("2010Jul0"));
1592         assertThrown!DateTimeException(Date.fromISOString("12010Jul04"));
1593         assertThrown!DateTimeException(Date.fromISOString("-010Jul04"));
1594         assertThrown!DateTimeException(Date.fromISOString("+010Jul04"));
1595         assertThrown!DateTimeException(Date.fromISOString("2010Jul0a"));
1596         assertThrown!DateTimeException(Date.fromISOString("2010Jua04"));
1597         assertThrown!DateTimeException(Date.fromISOString("2010aul04"));
1598 
1599         assertThrown!DateTimeException(Date.fromISOString("99-Jul-04"));
1600         assertThrown!DateTimeException(Date.fromISOString("010-Jul-04"));
1601         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0"));
1602         assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04"));
1603         assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04"));
1604         assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04"));
1605         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a"));
1606         assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04"));
1607         assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04"));
1608         assertThrown!DateTimeException(Date.fromISOString("2010-aul-04"));
1609 
1610         assertThrown!DateTimeException(Date.fromISOString("2010-07-04"));
1611         assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04"));
1612 
1613         assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
1614         assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6));
1615         assert(Date.fromISOString("+019990706") == Date(1999, 7, 6));
1616         assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
1617     }
1618 
1619     // bug# 17801
1620     version (mir_test)
1621     @safe unittest
1622     {
1623         import std.conv : to;
1624         import std.meta : AliasSeq;
1625         static foreach (C; AliasSeq!(char, wchar, dchar))
1626         {
1627             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
1628                 assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21));
1629         }
1630     }
1631 
1632     /++
1633     Creates a $(LREF Date) from a string with the format YYYY-MM-DD.
1634 
1635     Params:
1636         str = A string formatted in the way that $(LREF .date.toISOExtString) formats dates.
1637         value = (optional) result value.
1638 
1639     Throws:
1640         $(LREF DateTimeException) if the given string is
1641         not in the correct format or if the resulting $(LREF Date) would not
1642         be valid. Two arguments overload is `nothrow`.
1643     Returns:
1644         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
1645     +/
1646     static bool fromISOExtString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
1647         if (isSomeChar!C)
1648     {
1649         import mir.parse: fromString;
1650 
1651         if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-')
1652             return false;
1653 
1654         auto yearStr = str[0 .. $ - 6];
1655 
1656         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
1657             return false;
1658 
1659         uint day, month;
1660         int year;
1661 
1662         return
1663             fromString(str[$ - 2 .. $], day)
1664          && fromString(str[$ - 5 .. $ - 3], month)
1665          && fromString(yearStr, year)
1666          && fromYMD(year, month, day, value);
1667     }
1668 
1669     /// ditto
1670     static Date fromISOExtString(C)(scope const(C)[] str) @safe pure
1671         if (isSomeChar!C)
1672     {
1673         Date ret;
1674         if (fromISOExtString(str, ret))
1675             return ret;
1676         throw InvalidISOExtendedString;
1677     }
1678 
1679     ///
1680     version (mir_test)
1681     @safe unittest
1682     {
1683         assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4));
1684         assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25));
1685         assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5));
1686         assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5));
1687     }
1688 
1689     version (mir_test)
1690     @safe unittest
1691     {
1692         assertThrown!DateTimeException(Date.fromISOExtString(""));
1693         assertThrown!DateTimeException(Date.fromISOExtString("990704"));
1694         assertThrown!DateTimeException(Date.fromISOExtString("0100704"));
1695         assertThrown!DateTimeException(Date.fromISOExtString("120100704"));
1696         assertThrown!DateTimeException(Date.fromISOExtString("-0100704"));
1697         assertThrown!DateTimeException(Date.fromISOExtString("+0100704"));
1698         assertThrown!DateTimeException(Date.fromISOExtString("2010070a"));
1699         assertThrown!DateTimeException(Date.fromISOExtString("20100a04"));
1700         assertThrown!DateTimeException(Date.fromISOExtString("2010a704"));
1701 
1702         assertThrown!DateTimeException(Date.fromISOExtString("99-07-04"));
1703         assertThrown!DateTimeException(Date.fromISOExtString("010-07-04"));
1704         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0"));
1705         assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04"));
1706         assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04"));
1707         assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04"));
1708         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a"));
1709         assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04"));
1710         assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04"));
1711         assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04"));
1712         assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04"));
1713         assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4"));
1714         assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4"));
1715         assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04"));
1716         assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4"));
1717         assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4"));
1718 
1719         assertThrown!DateTimeException(Date.fromISOExtString("99Jul04"));
1720         assertThrown!DateTimeException(Date.fromISOExtString("010Jul04"));
1721         assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0"));
1722         assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04"));
1723         assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04"));
1724         assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04"));
1725         assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a"));
1726         assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04"));
1727         assertThrown!DateTimeException(Date.fromISOExtString("2010aul04"));
1728 
1729         assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04"));
1730         assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04"));
1731         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0"));
1732         assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04"));
1733         assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04"));
1734         assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04"));
1735         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a"));
1736         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04"));
1737         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04"));
1738         assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04"));
1739 
1740         assertThrown!DateTimeException(Date.fromISOExtString("20100704"));
1741         assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04"));
1742 
1743         assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6));
1744         assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6));
1745         assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6));
1746     }
1747 
1748     // bug# 17801
1749     version (mir_test)
1750     @safe unittest
1751     {
1752         import std.conv : to;
1753         import std.meta : AliasSeq;
1754         static foreach (C; AliasSeq!(char, wchar, dchar))
1755         {
1756             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
1757                 assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21));
1758         }
1759     }
1760 
1761 
1762     /++
1763     Creates a $(LREF Date) from a string with the format YYYY-Mon-DD.
1764 
1765     Params:
1766         str = A string formatted in the way that $(LREF .date.toSimpleString) formats dates. The function is case sensetive.
1767         value = (optional) result value.
1768 
1769     Throws:
1770         $(LREF DateTimeException) if the given string is
1771         not in the correct format or if the resulting $(LREF Date) would not
1772         be valid. Two arguments overload is `nothrow`.
1773     Returns:
1774         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
1775     +/
1776     static bool fromSimpleString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
1777         if (isSomeChar!C)
1778     {
1779         import mir.parse: fromString;
1780 
1781         if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-')
1782             return false;
1783 
1784         auto yearStr = str[0 .. $ - 7];
1785 
1786         if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4))
1787             return false;
1788 
1789         Month month;
1790 
1791         switch (str[$ - 6 .. $ - 3])
1792         {
1793             case "Jan": month = Month.jan; break;
1794             case "Feb": month = Month.feb; break;
1795             case "Mar": month = Month.mar; break;
1796             case "Apr": month = Month.apr; break;
1797             case "May": month = Month.may; break;
1798             case "Jun": month = Month.jun; break;
1799             case "Jul": month = Month.jul; break;
1800             case "Aug": month = Month.aug; break;
1801             case "Sep": month = Month.sep; break;
1802             case "Oct": month = Month.oct; break;
1803             case "Nov": month = Month.nov; break;
1804             case "Dec": month = Month.dec; break;
1805             default: return false;
1806         }
1807 
1808         uint day;
1809         int year;
1810 
1811         return
1812             fromString(str[$ - 2 .. $], day)
1813          && fromString(yearStr, year)
1814          && fromYMD(year, month, day, value);
1815     }
1816 
1817     /// ditto
1818     static Date fromSimpleString(C)(scope const(C)[] str) @safe pure
1819         if (isSomeChar!C)
1820     {
1821         Date ret;
1822         if (fromSimpleString(str, ret))
1823             return ret;
1824         throw new DateTimeException("Invalid Simple String");
1825     }
1826 
1827     ///
1828     version (mir_test)
1829     @safe unittest
1830     {
1831         assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4));
1832         assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25));
1833         assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5));
1834         assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5));
1835     }
1836 
1837     version (mir_test)
1838     @safe unittest
1839     {
1840         assertThrown!DateTimeException(Date.fromSimpleString(""));
1841         assertThrown!DateTimeException(Date.fromSimpleString("990704"));
1842         assertThrown!DateTimeException(Date.fromSimpleString("0100704"));
1843         assertThrown!DateTimeException(Date.fromSimpleString("2010070"));
1844         assertThrown!DateTimeException(Date.fromSimpleString("120100704"));
1845         assertThrown!DateTimeException(Date.fromSimpleString("-0100704"));
1846         assertThrown!DateTimeException(Date.fromSimpleString("+0100704"));
1847         assertThrown!DateTimeException(Date.fromSimpleString("2010070a"));
1848         assertThrown!DateTimeException(Date.fromSimpleString("20100a04"));
1849         assertThrown!DateTimeException(Date.fromSimpleString("2010a704"));
1850 
1851         assertThrown!DateTimeException(Date.fromSimpleString("99-07-04"));
1852         assertThrown!DateTimeException(Date.fromSimpleString("010-07-04"));
1853         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0"));
1854         assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04"));
1855         assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04"));
1856         assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04"));
1857         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a"));
1858         assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04"));
1859         assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04"));
1860         assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04"));
1861         assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04"));
1862         assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4"));
1863         assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4"));
1864         assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04"));
1865         assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4"));
1866         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4"));
1867 
1868         assertThrown!DateTimeException(Date.fromSimpleString("99Jul04"));
1869         assertThrown!DateTimeException(Date.fromSimpleString("010Jul04"));
1870         assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0"));
1871         assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04"));
1872         assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04"));
1873         assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04"));
1874         assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a"));
1875         assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04"));
1876         assertThrown!DateTimeException(Date.fromSimpleString("2010aul04"));
1877 
1878         assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04"));
1879         assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04"));
1880         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0"));
1881         assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04"));
1882         assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04"));
1883         assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04"));
1884         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a"));
1885         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04"));
1886         assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04"));
1887         assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04"));
1888 
1889         assertThrown!DateTimeException(Date.fromSimpleString("20100704"));
1890         assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04"));
1891 
1892         assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6));
1893         assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6));
1894         assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6));
1895     }
1896 
1897     // bug# 17801
1898     version (mir_test)
1899     @safe unittest
1900     {
1901         import std.conv : to;
1902         import std.meta : AliasSeq;
1903         static foreach (C; AliasSeq!(char, wchar, dchar))
1904         {
1905             static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
1906                 assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21));
1907         }
1908     }
1909 
1910     /++
1911     Creates a $(LREF Date) from a string with the format YYYY-MM-DD, YYYYMMDD, or YYYY-Mon-DD.
1912 
1913     Params:
1914         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.
1915         value = (optional) result value.
1916 
1917     Throws:
1918         $(LREF DateTimeException) if the given string is
1919         not in the correct format or if the resulting $(LREF Date) would not
1920         be valid. Two arguments overload is `nothrow`.
1921     Returns:
1922         `bool` on success for two arguments overload, and the resulting date for single argument overdload.
1923     +/
1924     static bool fromString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc
1925     {
1926         return fromISOExtString(str, value)
1927             || fromISOString(str, value)
1928             || fromSimpleString(str, value);
1929     }
1930 
1931     ///
1932     version (mir_test)
1933     @safe pure @nogc unittest
1934     {
1935         assert(Date.fromString("2010-07-04") == Date(2010, 7, 4));
1936         assert(Date.fromString("20100704") == Date(2010, 7, 4));
1937         assert(Date.fromString("2010-Jul-04") == Date(2010, 7, 4));
1938     }
1939 
1940     /// ditto
1941     static Date fromString(C)(scope const(C)[] str) @safe pure
1942         if (isSomeChar!C)
1943     {
1944         Date ret;
1945         if (fromString(str, ret))
1946             return ret;
1947         throw InvalidString;
1948     }
1949 
1950     /++
1951         Returns the $(LREF Date) farthest in the past which is representable by
1952         $(LREF Date).
1953       +/
1954     @property static Date min() @safe pure nothrow @nogc
1955     {
1956         return Date(-(int.max / 2));
1957     }
1958 
1959     /++
1960         Returns the $(LREF Date) farthest in the future which is representable
1961         by $(LREF Date).
1962       +/
1963     @property static Date max() @safe pure nothrow @nogc
1964     {
1965         return Date(int.max / 2);
1966     }
1967 
1968 private:
1969 
1970     /+
1971         Whether the given values form a valid date.
1972 
1973         Params:
1974             year  = The year to test.
1975             month = The month of the Gregorian Calendar to test.
1976             day   = The day of the month to test.
1977      +/
1978     static bool _valid(int year, int month, int day) @safe pure nothrow @nogc
1979     {
1980         if (!valid!"months"(month))
1981             return false;
1982         return valid!"days"(year, month, day);
1983     }
1984 
1985 
1986 package:
1987 
1988     /+
1989         Adds the given number of days to this $(LREF Date). A negative number
1990         will subtract.
1991 
1992         The month will be adjusted along with the day if the number of days
1993         added (or subtracted) would overflow (or underflow) the current month.
1994         The year will be adjusted along with the month if the increase (or
1995         decrease) to the month would cause it to overflow (or underflow) the
1996         current year.
1997 
1998         $(D _addDays(numDays)) is effectively equivalent to
1999         $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days).
2000 
2001         Params:
2002             days = The number of days to add to this Date.
2003       +/
2004     ref Date _addDays(long days) return @safe pure nothrow @nogc
2005     {
2006         _julianDay = cast(int)(_julianDay + days);
2007         return this;
2008     }
2009 
2010     version (mir_test)
2011     @safe unittest
2012     {
2013         // Test A.D.
2014         {
2015             auto date = Date(1999, 2, 28);
2016             date._addDays(1);
2017             assert(date == Date(1999, 3, 1));
2018             date._addDays(-1);
2019             assert(date == Date(1999, 2, 28));
2020         }
2021 
2022         {
2023             auto date = Date(2000, 2, 28);
2024             date._addDays(1);
2025             assert(date == Date(2000, 2, 29));
2026             date._addDays(1);
2027             assert(date == Date(2000, 3, 1));
2028             date._addDays(-1);
2029             assert(date == Date(2000, 2, 29));
2030         }
2031 
2032         {
2033             auto date = Date(1999, 6, 30);
2034             date._addDays(1);
2035             assert(date == Date(1999, 7, 1));
2036             date._addDays(-1);
2037             assert(date == Date(1999, 6, 30));
2038         }
2039 
2040         {
2041             auto date = Date(1999, 7, 31);
2042             date._addDays(1);
2043             assert(date == Date(1999, 8, 1));
2044             date._addDays(-1);
2045             assert(date == Date(1999, 7, 31));
2046         }
2047 
2048         {
2049             auto date = Date(1999, 1, 1);
2050             date._addDays(-1);
2051             assert(date == Date(1998, 12, 31));
2052             date._addDays(1);
2053             assert(date == Date(1999, 1, 1));
2054         }
2055 
2056         {
2057             auto date = Date(1999, 7, 6);
2058             date._addDays(9);
2059             assert(date == Date(1999, 7, 15));
2060             date._addDays(-11);
2061             assert(date == Date(1999, 7, 4));
2062             date._addDays(30);
2063             assert(date == Date(1999, 8, 3));
2064             date._addDays(-3);
2065             assert(date == Date(1999, 7, 31));
2066         }
2067 
2068         {
2069             auto date = Date(1999, 7, 6);
2070             date._addDays(365);
2071             assert(date == Date(2000, 7, 5));
2072             date._addDays(-365);
2073             assert(date == Date(1999, 7, 6));
2074             date._addDays(366);
2075             assert(date == Date(2000, 7, 6));
2076             date._addDays(730);
2077             assert(date == Date(2002, 7, 6));
2078             date._addDays(-1096);
2079             assert(date == Date(1999, 7, 6));
2080         }
2081 
2082         // Test B.C.
2083         {
2084             auto date = Date(-1999, 2, 28);
2085             date._addDays(1);
2086             assert(date == Date(-1999, 3, 1));
2087             date._addDays(-1);
2088             assert(date == Date(-1999, 2, 28));
2089         }
2090 
2091         {
2092             auto date = Date(-2000, 2, 28);
2093             date._addDays(1);
2094             assert(date == Date(-2000, 2, 29));
2095             date._addDays(1);
2096             assert(date == Date(-2000, 3, 1));
2097             date._addDays(-1);
2098             assert(date == Date(-2000, 2, 29));
2099         }
2100 
2101         {
2102             auto date = Date(-1999, 6, 30);
2103             date._addDays(1);
2104             assert(date == Date(-1999, 7, 1));
2105             date._addDays(-1);
2106             assert(date == Date(-1999, 6, 30));
2107         }
2108 
2109         {
2110             auto date = Date(-1999, 7, 31);
2111             date._addDays(1);
2112             assert(date == Date(-1999, 8, 1));
2113             date._addDays(-1);
2114             assert(date == Date(-1999, 7, 31));
2115         }
2116 
2117         {
2118             auto date = Date(-1999, 1, 1);
2119             date._addDays(-1);
2120             assert(date == Date(-2000, 12, 31));
2121             date._addDays(1);
2122             assert(date == Date(-1999, 1, 1));
2123         }
2124 
2125         {
2126             auto date = Date(-1999, 7, 6);
2127             date._addDays(9);
2128             assert(date == Date(-1999, 7, 15));
2129             date._addDays(-11);
2130             assert(date == Date(-1999, 7, 4));
2131             date._addDays(30);
2132             assert(date == Date(-1999, 8, 3));
2133             date._addDays(-3);
2134         }
2135 
2136         {
2137             auto date = Date(-1999, 7, 6);
2138             date._addDays(365);
2139             assert(date == Date(-1998, 7, 6));
2140             date._addDays(-365);
2141             assert(date == Date(-1999, 7, 6));
2142             date._addDays(366);
2143             assert(date == Date(-1998, 7, 7));
2144             date._addDays(730);
2145             assert(date == Date(-1996, 7, 6));
2146             date._addDays(-1096);
2147             assert(date == Date(-1999, 7, 6));
2148         }
2149 
2150         // Test Both
2151         {
2152             auto date = Date(1, 7, 6);
2153             date._addDays(-365);
2154             assert(date == Date(0, 7, 6));
2155             date._addDays(365);
2156             assert(date == Date(1, 7, 6));
2157             date._addDays(-731);
2158             assert(date == Date(-1, 7, 6));
2159             date._addDays(730);
2160             assert(date == Date(1, 7, 5));
2161         }
2162 
2163         const cdate = Date(1999, 7, 6);
2164         immutable idate = Date(1999, 7, 6);
2165         static assert(!__traits(compiles, cdate._addDays(12)));
2166         static assert(!__traits(compiles, idate._addDays(12)));
2167     }
2168 
2169     int _julianDay;
2170 }
2171 
2172 /// ditto
2173 alias Date = date;
2174 
2175 /++
2176     Returns the number of days from the current day of the week to the given
2177     day of the week. If they are the same, then the result is 0.
2178     Params:
2179         currDoW = The current day of the week.
2180         dow     = The day of the week to get the number of days to.
2181   +/
2182 int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc
2183 {
2184     if (currDoW == dow)
2185         return 0;
2186     if (currDoW < dow)
2187         return dow - currDoW;
2188     return DayOfWeek.sun - currDoW + dow + 1;
2189 }
2190 
2191 ///
2192 version (mir_test)
2193 @safe pure nothrow @nogc unittest
2194 {
2195     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
2196     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
2197     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
2198 }
2199 
2200 version (mir_test)
2201 @safe unittest
2202 {
2203     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0);
2204     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1);
2205     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2);
2206     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3);
2207     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4);
2208     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5);
2209     assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6);
2210 
2211     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
2212     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
2213     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1);
2214     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
2215     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3);
2216     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4);
2217     assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5);
2218 
2219     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5);
2220     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6);
2221     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0);
2222     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1);
2223     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2);
2224     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3);
2225     assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4);
2226 
2227     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4);
2228     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5);
2229     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6);
2230     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0);
2231     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1);
2232     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2);
2233     assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3);
2234 
2235     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3);
2236     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4);
2237     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5);
2238     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6);
2239     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0);
2240     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1);
2241     assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2);
2242 
2243     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2);
2244     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3);
2245     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4);
2246     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5);
2247     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6);
2248     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0);
2249     assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1);
2250 
2251     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1);
2252     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2);
2253     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3);
2254     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4);
2255     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5);
2256     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6);
2257     assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0);
2258 }
2259 
2260 package:
2261 
2262 
2263 /+
2264     Array of the short (three letter) names of each month.
2265   +/
2266 immutable string[12] _monthNames = ["Jan",
2267                                     "Feb",
2268                                     "Mar",
2269                                     "Apr",
2270                                     "May",
2271                                     "Jun",
2272                                     "Jul",
2273                                     "Aug",
2274                                     "Sep",
2275                                     "Oct",
2276                                     "Nov",
2277                                     "Dec"];
2278 
2279 /++
2280     The maximum valid Day in the given month in the given year.
2281 
2282     Params:
2283         year  = The year to get the day for.
2284         month = The month of the Gregorian Calendar to get the day for.
2285  +/
2286 public ubyte maxDay(int year, int month) @safe pure nothrow @nogc
2287 in
2288 {
2289     assert(valid!"months"(month));
2290 }
2291 do
2292 {
2293     switch (month)
2294     {
2295         case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec:
2296             return 31;
2297         case Month.feb:
2298             return yearIsLeapYear(year) ? 29 : 28;
2299         case Month.apr, Month.jun, Month.sep, Month.nov:
2300             return 30;
2301         default:
2302             assert(0, "Invalid month.");
2303     }
2304 }
2305 
2306 version (mir_test)
2307 @safe unittest
2308 {
2309     // Test A.D.
2310     assert(maxDay(1999, 1) == 31);
2311     assert(maxDay(1999, 2) == 28);
2312     assert(maxDay(1999, 3) == 31);
2313     assert(maxDay(1999, 4) == 30);
2314     assert(maxDay(1999, 5) == 31);
2315     assert(maxDay(1999, 6) == 30);
2316     assert(maxDay(1999, 7) == 31);
2317     assert(maxDay(1999, 8) == 31);
2318     assert(maxDay(1999, 9) == 30);
2319     assert(maxDay(1999, 10) == 31);
2320     assert(maxDay(1999, 11) == 30);
2321     assert(maxDay(1999, 12) == 31);
2322 
2323     assert(maxDay(2000, 1) == 31);
2324     assert(maxDay(2000, 2) == 29);
2325     assert(maxDay(2000, 3) == 31);
2326     assert(maxDay(2000, 4) == 30);
2327     assert(maxDay(2000, 5) == 31);
2328     assert(maxDay(2000, 6) == 30);
2329     assert(maxDay(2000, 7) == 31);
2330     assert(maxDay(2000, 8) == 31);
2331     assert(maxDay(2000, 9) == 30);
2332     assert(maxDay(2000, 10) == 31);
2333     assert(maxDay(2000, 11) == 30);
2334     assert(maxDay(2000, 12) == 31);
2335 
2336     // Test B.C.
2337     assert(maxDay(-1999, 1) == 31);
2338     assert(maxDay(-1999, 2) == 28);
2339     assert(maxDay(-1999, 3) == 31);
2340     assert(maxDay(-1999, 4) == 30);
2341     assert(maxDay(-1999, 5) == 31);
2342     assert(maxDay(-1999, 6) == 30);
2343     assert(maxDay(-1999, 7) == 31);
2344     assert(maxDay(-1999, 8) == 31);
2345     assert(maxDay(-1999, 9) == 30);
2346     assert(maxDay(-1999, 10) == 31);
2347     assert(maxDay(-1999, 11) == 30);
2348     assert(maxDay(-1999, 12) == 31);
2349 
2350     assert(maxDay(-2000, 1) == 31);
2351     assert(maxDay(-2000, 2) == 29);
2352     assert(maxDay(-2000, 3) == 31);
2353     assert(maxDay(-2000, 4) == 30);
2354     assert(maxDay(-2000, 5) == 31);
2355     assert(maxDay(-2000, 6) == 30);
2356     assert(maxDay(-2000, 7) == 31);
2357     assert(maxDay(-2000, 8) == 31);
2358     assert(maxDay(-2000, 9) == 30);
2359     assert(maxDay(-2000, 10) == 31);
2360     assert(maxDay(-2000, 11) == 30);
2361     assert(maxDay(-2000, 12) == 31);
2362 }
2363 
2364 /+
2365     Returns the day of the week for the given day of the Gregorian/Julian Calendar.
2366 
2367     Params:
2368         day = The day of the Gregorian/Julian Calendar for which to get the day of
2369               the week.
2370   +/
2371 DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc
2372 {
2373     // January 1st, 1 A.D. was a Monday
2374     if (day >= 0)
2375         return cast(DayOfWeek)(day % 7);
2376     else
2377     {
2378         immutable dow = cast(DayOfWeek)((day % 7) + 7);
2379 
2380         if (dow == 7)
2381             return DayOfWeek.mon;
2382         else
2383             return dow;
2384     }
2385 }
2386 
2387 private:
2388 
2389 enum daysInYear     = 365;  // The number of days in a non-leap year.
2390 enum daysInLeapYear = 366;  // The numbef or days in a leap year.
2391 enum daysIn4Years   = daysInYear * 3 + daysInLeapYear;  // Number of days in 4 years.
2392 enum daysIn100Years = daysIn4Years * 25 - 1;  // The number of days in 100 years.
2393 enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years.
2394 
2395 /+
2396     Array of integers representing the last days of each month in a year.
2397   +/
2398 immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
2399 
2400 /+
2401     Array of integers representing the last days of each month in a leap year.
2402   +/
2403 immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
2404 
2405 
2406 /+
2407     Returns the string representation of the given month.
2408   +/
2409 string monthToString(Month month) @safe pure @nogc nothrow
2410 {
2411     assert(month >= Month.jan && month <= Month.dec, "Invalid month");
2412     return _monthNames[month - Month.jan];
2413 }
2414 
2415 version (mir_test)
2416 @safe unittest
2417 {
2418     assert(monthToString(Month.jan) == "Jan");
2419     assert(monthToString(Month.feb) == "Feb");
2420     assert(monthToString(Month.mar) == "Mar");
2421     assert(monthToString(Month.apr) == "Apr");
2422     assert(monthToString(Month.may) == "May");
2423     assert(monthToString(Month.jun) == "Jun");
2424     assert(monthToString(Month.jul) == "Jul");
2425     assert(monthToString(Month.aug) == "Aug");
2426     assert(monthToString(Month.sep) == "Sep");
2427     assert(monthToString(Month.oct) == "Oct");
2428     assert(monthToString(Month.nov) == "Nov");
2429     assert(monthToString(Month.dec) == "Dec");
2430 }
2431 
2432 version (mir_test)
2433 version(unittest)
2434 {
2435     // All of these helper arrays are sorted in ascending order.
2436     auto testYearsBC = [-1999, -1200, -600, -4, -1, 0];
2437     auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012];
2438 
2439     // I'd use a Tuple, but I get forward reference errors if I try.
2440     struct MonthDay
2441     {
2442         Month month;
2443         short day;
2444 
2445         this(int m, short d)
2446         {
2447             month = cast(Month) m;
2448             day = d;
2449         }
2450     }
2451 
2452     MonthDay[] testMonthDays = [MonthDay(1, 1),
2453                                 MonthDay(1, 2),
2454                                 MonthDay(3, 17),
2455                                 MonthDay(7, 4),
2456                                 MonthDay(10, 27),
2457                                 MonthDay(12, 30),
2458                                 MonthDay(12, 31)];
2459 
2460     auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31];
2461 
2462     Date[] testDatesBC;
2463     Date[] testDatesAD;
2464 
2465     // I'd use a Tuple, but I get forward reference errors if I try.
2466     struct GregDay { int day; Date date; }
2467     auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar
2468                            GregDay(-735_233, Date(-2012, 1, 1)),
2469                            GregDay(-735_202, Date(-2012, 2, 1)),
2470                            GregDay(-735_175, Date(-2012, 2, 28)),
2471                            GregDay(-735_174, Date(-2012, 2, 29)),
2472                            GregDay(-735_173, Date(-2012, 3, 1)),
2473                            GregDay(-734_502, Date(-2010, 1, 1)),
2474                            GregDay(-734_472, Date(-2010, 1, 31)),
2475                            GregDay(-734_471, Date(-2010, 2, 1)),
2476                            GregDay(-734_444, Date(-2010, 2, 28)),
2477                            GregDay(-734_443, Date(-2010, 3, 1)),
2478                            GregDay(-734_413, Date(-2010, 3, 31)),
2479                            GregDay(-734_412, Date(-2010, 4, 1)),
2480                            GregDay(-734_383, Date(-2010, 4, 30)),
2481                            GregDay(-734_382, Date(-2010, 5, 1)),
2482                            GregDay(-734_352, Date(-2010, 5, 31)),
2483                            GregDay(-734_351, Date(-2010, 6, 1)),
2484                            GregDay(-734_322, Date(-2010, 6, 30)),
2485                            GregDay(-734_321, Date(-2010, 7, 1)),
2486                            GregDay(-734_291, Date(-2010, 7, 31)),
2487                            GregDay(-734_290, Date(-2010, 8, 1)),
2488                            GregDay(-734_260, Date(-2010, 8, 31)),
2489                            GregDay(-734_259, Date(-2010, 9, 1)),
2490                            GregDay(-734_230, Date(-2010, 9, 30)),
2491                            GregDay(-734_229, Date(-2010, 10, 1)),
2492                            GregDay(-734_199, Date(-2010, 10, 31)),
2493                            GregDay(-734_198, Date(-2010, 11, 1)),
2494                            GregDay(-734_169, Date(-2010, 11, 30)),
2495                            GregDay(-734_168, Date(-2010, 12, 1)),
2496                            GregDay(-734_139, Date(-2010, 12, 30)),
2497                            GregDay(-734_138, Date(-2010, 12, 31)),
2498                            GregDay(-731_215, Date(-2001, 1, 1)),
2499                            GregDay(-730_850, Date(-2000, 1, 1)),
2500                            GregDay(-730_849, Date(-2000, 1, 2)),
2501                            GregDay(-730_486, Date(-2000, 12, 30)),
2502                            GregDay(-730_485, Date(-2000, 12, 31)),
2503                            GregDay(-730_484, Date(-1999, 1, 1)),
2504                            GregDay(-694_690, Date(-1901, 1, 1)),
2505                            GregDay(-694_325, Date(-1900, 1, 1)),
2506                            GregDay(-585_118, Date(-1601, 1, 1)),
2507                            GregDay(-584_753, Date(-1600, 1, 1)),
2508                            GregDay(-584_388, Date(-1600, 12, 31)),
2509                            GregDay(-584_387, Date(-1599, 1, 1)),
2510                            GregDay(-365_972, Date(-1001, 1, 1)),
2511                            GregDay(-365_607, Date(-1000, 1, 1)),
2512                            GregDay(-183_351, Date(-501, 1, 1)),
2513                            GregDay(-182_986, Date(-500, 1, 1)),
2514                            GregDay(-182_621, Date(-499, 1, 1)),
2515                            GregDay(-146_827, Date(-401, 1, 1)),
2516                            GregDay(-146_462, Date(-400, 1, 1)),
2517                            GregDay(-146_097, Date(-400, 12, 31)),
2518                            GregDay(-110_302, Date(-301, 1, 1)),
2519                            GregDay(-109_937, Date(-300, 1, 1)),
2520                            GregDay(-73_778, Date(-201, 1, 1)),
2521                            GregDay(-73_413, Date(-200, 1, 1)),
2522                            GregDay(-38_715, Date(-105, 1, 1)),
2523                            GregDay(-37_254, Date(-101, 1, 1)),
2524                            GregDay(-36_889, Date(-100, 1, 1)),
2525                            GregDay(-36_524, Date(-99, 1, 1)),
2526                            GregDay(-36_160, Date(-99, 12, 31)),
2527                            GregDay(-35_794, Date(-97, 1, 1)),
2528                            GregDay(-18_627, Date(-50, 1, 1)),
2529                            GregDay(-18_262, Date(-49, 1, 1)),
2530                            GregDay(-3652, Date(-9, 1, 1)),
2531                            GregDay(-2191, Date(-5, 1, 1)),
2532                            GregDay(-1827, Date(-5, 12, 31)),
2533                            GregDay(-1826, Date(-4, 1, 1)),
2534                            GregDay(-1825, Date(-4, 1, 2)),
2535                            GregDay(-1462, Date(-4, 12, 30)),
2536                            GregDay(-1461, Date(-4, 12, 31)),
2537                            GregDay(-1460, Date(-3, 1, 1)),
2538                            GregDay(-1096, Date(-3, 12, 31)),
2539                            GregDay(-1095, Date(-2, 1, 1)),
2540                            GregDay(-731, Date(-2, 12, 31)),
2541                            GregDay(-730, Date(-1, 1, 1)),
2542                            GregDay(-367, Date(-1, 12, 30)),
2543                            GregDay(-366, Date(-1, 12, 31)),
2544                            GregDay(-365, Date(0, 1, 1)),
2545                            GregDay(-31, Date(0, 11, 30)),
2546                            GregDay(-30, Date(0, 12, 1)),
2547                            GregDay(-1, Date(0, 12, 30)),
2548                            GregDay(0, Date(0, 12, 31))];
2549 
2550     auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)),
2551                            GregDay(2, Date(1, 1, 2)),
2552                            GregDay(32, Date(1, 2, 1)),
2553                            GregDay(365, Date(1, 12, 31)),
2554                            GregDay(366, Date(2, 1, 1)),
2555                            GregDay(731, Date(3, 1, 1)),
2556                            GregDay(1096, Date(4, 1, 1)),
2557                            GregDay(1097, Date(4, 1, 2)),
2558                            GregDay(1460, Date(4, 12, 30)),
2559                            GregDay(1461, Date(4, 12, 31)),
2560                            GregDay(1462, Date(5, 1, 1)),
2561                            GregDay(17_898, Date(50, 1, 1)),
2562                            GregDay(35_065, Date(97, 1, 1)),
2563                            GregDay(36_160, Date(100, 1, 1)),
2564                            GregDay(36_525, Date(101, 1, 1)),
2565                            GregDay(37_986, Date(105, 1, 1)),
2566                            GregDay(72_684, Date(200, 1, 1)),
2567                            GregDay(73_049, Date(201, 1, 1)),
2568                            GregDay(109_208, Date(300, 1, 1)),
2569                            GregDay(109_573, Date(301, 1, 1)),
2570                            GregDay(145_732, Date(400, 1, 1)),
2571                            GregDay(146_098, Date(401, 1, 1)),
2572                            GregDay(182_257, Date(500, 1, 1)),
2573                            GregDay(182_622, Date(501, 1, 1)),
2574                            GregDay(364_878, Date(1000, 1, 1)),
2575                            GregDay(365_243, Date(1001, 1, 1)),
2576                            GregDay(584_023, Date(1600, 1, 1)),
2577                            GregDay(584_389, Date(1601, 1, 1)),
2578                            GregDay(693_596, Date(1900, 1, 1)),
2579                            GregDay(693_961, Date(1901, 1, 1)),
2580                            GregDay(729_755, Date(1999, 1, 1)),
2581                            GregDay(730_120, Date(2000, 1, 1)),
2582                            GregDay(730_121, Date(2000, 1, 2)),
2583                            GregDay(730_484, Date(2000, 12, 30)),
2584                            GregDay(730_485, Date(2000, 12, 31)),
2585                            GregDay(730_486, Date(2001, 1, 1)),
2586                            GregDay(733_773, Date(2010, 1, 1)),
2587                            GregDay(733_774, Date(2010, 1, 2)),
2588                            GregDay(733_803, Date(2010, 1, 31)),
2589                            GregDay(733_804, Date(2010, 2, 1)),
2590                            GregDay(733_831, Date(2010, 2, 28)),
2591                            GregDay(733_832, Date(2010, 3, 1)),
2592                            GregDay(733_862, Date(2010, 3, 31)),
2593                            GregDay(733_863, Date(2010, 4, 1)),
2594                            GregDay(733_892, Date(2010, 4, 30)),
2595                            GregDay(733_893, Date(2010, 5, 1)),
2596                            GregDay(733_923, Date(2010, 5, 31)),
2597                            GregDay(733_924, Date(2010, 6, 1)),
2598                            GregDay(733_953, Date(2010, 6, 30)),
2599                            GregDay(733_954, Date(2010, 7, 1)),
2600                            GregDay(733_984, Date(2010, 7, 31)),
2601                            GregDay(733_985, Date(2010, 8, 1)),
2602                            GregDay(734_015, Date(2010, 8, 31)),
2603                            GregDay(734_016, Date(2010, 9, 1)),
2604                            GregDay(734_045, Date(2010, 9, 30)),
2605                            GregDay(734_046, Date(2010, 10, 1)),
2606                            GregDay(734_076, Date(2010, 10, 31)),
2607                            GregDay(734_077, Date(2010, 11, 1)),
2608                            GregDay(734_106, Date(2010, 11, 30)),
2609                            GregDay(734_107, Date(2010, 12, 1)),
2610                            GregDay(734_136, Date(2010, 12, 30)),
2611                            GregDay(734_137, Date(2010, 12, 31)),
2612                            GregDay(734_503, Date(2012, 1, 1)),
2613                            GregDay(734_534, Date(2012, 2, 1)),
2614                            GregDay(734_561, Date(2012, 2, 28)),
2615                            GregDay(734_562, Date(2012, 2, 29)),
2616                            GregDay(734_563, Date(2012, 3, 1)),
2617                            GregDay(734_858, Date(2012, 12, 21))];
2618 
2619     // I'd use a Tuple, but I get forward reference errors if I try.
2620     struct DayOfYear { int day; MonthDay md; }
2621     auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)),
2622                            DayOfYear(2, MonthDay(1, 2)),
2623                            DayOfYear(3, MonthDay(1, 3)),
2624                            DayOfYear(31, MonthDay(1, 31)),
2625                            DayOfYear(32, MonthDay(2, 1)),
2626                            DayOfYear(59, MonthDay(2, 28)),
2627                            DayOfYear(60, MonthDay(3, 1)),
2628                            DayOfYear(90, MonthDay(3, 31)),
2629                            DayOfYear(91, MonthDay(4, 1)),
2630                            DayOfYear(120, MonthDay(4, 30)),
2631                            DayOfYear(121, MonthDay(5, 1)),
2632                            DayOfYear(151, MonthDay(5, 31)),
2633                            DayOfYear(152, MonthDay(6, 1)),
2634                            DayOfYear(181, MonthDay(6, 30)),
2635                            DayOfYear(182, MonthDay(7, 1)),
2636                            DayOfYear(212, MonthDay(7, 31)),
2637                            DayOfYear(213, MonthDay(8, 1)),
2638                            DayOfYear(243, MonthDay(8, 31)),
2639                            DayOfYear(244, MonthDay(9, 1)),
2640                            DayOfYear(273, MonthDay(9, 30)),
2641                            DayOfYear(274, MonthDay(10, 1)),
2642                            DayOfYear(304, MonthDay(10, 31)),
2643                            DayOfYear(305, MonthDay(11, 1)),
2644                            DayOfYear(334, MonthDay(11, 30)),
2645                            DayOfYear(335, MonthDay(12, 1)),
2646                            DayOfYear(363, MonthDay(12, 29)),
2647                            DayOfYear(364, MonthDay(12, 30)),
2648                            DayOfYear(365, MonthDay(12, 31))];
2649 
2650     auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)),
2651                                DayOfYear(2, MonthDay(1, 2)),
2652                                DayOfYear(3, MonthDay(1, 3)),
2653                                DayOfYear(31, MonthDay(1, 31)),
2654                                DayOfYear(32, MonthDay(2, 1)),
2655                                DayOfYear(59, MonthDay(2, 28)),
2656                                DayOfYear(60, MonthDay(2, 29)),
2657                                DayOfYear(61, MonthDay(3, 1)),
2658                                DayOfYear(91, MonthDay(3, 31)),
2659                                DayOfYear(92, MonthDay(4, 1)),
2660                                DayOfYear(121, MonthDay(4, 30)),
2661                                DayOfYear(122, MonthDay(5, 1)),
2662                                DayOfYear(152, MonthDay(5, 31)),
2663                                DayOfYear(153, MonthDay(6, 1)),
2664                                DayOfYear(182, MonthDay(6, 30)),
2665                                DayOfYear(183, MonthDay(7, 1)),
2666                                DayOfYear(213, MonthDay(7, 31)),
2667                                DayOfYear(214, MonthDay(8, 1)),
2668                                DayOfYear(244, MonthDay(8, 31)),
2669                                DayOfYear(245, MonthDay(9, 1)),
2670                                DayOfYear(274, MonthDay(9, 30)),
2671                                DayOfYear(275, MonthDay(10, 1)),
2672                                DayOfYear(305, MonthDay(10, 31)),
2673                                DayOfYear(306, MonthDay(11, 1)),
2674                                DayOfYear(335, MonthDay(11, 30)),
2675                                DayOfYear(336, MonthDay(12, 1)),
2676                                DayOfYear(364, MonthDay(12, 29)),
2677                                DayOfYear(365, MonthDay(12, 30)),
2678                                DayOfYear(366, MonthDay(12, 31))];
2679 
2680     void initializeTests() @safe
2681     {
2682         foreach (year; testYearsBC)
2683         {
2684             foreach (md; testMonthDays)
2685                 testDatesBC ~= Date(year, md.month, md.day);
2686         }
2687 
2688         foreach (year; testYearsAD)
2689         {
2690             foreach (md; testMonthDays)
2691                 testDatesAD ~= Date(year, md.month, md.day);
2692         }
2693     }
2694 }