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