// Written in the D programming language
/++

$(SCRIPT inhibitQuickIndex = 1;)
$(BOOKTABLE,
$(TR $(TH Category) $(TH Functions))
$(TR $(TD Main date types) $(TD
    $(LREF Date)
    $(LREF DateTime)
))
$(TR $(TD Other date types) $(TD
    $(LREF Month)
    $(LREF DayOfWeek)
    $(LREF TimeOfDay)
))
$(TR $(TD Date checking) $(TD
    $(LREF valid)
    $(LREF validTimeUnits)
    $(LREF yearIsLeapYear)
    $(LREF isTimePoint)
    $(LREF enforceValid)
))
$(TR $(TD Date conversion) $(TD
    $(LREF daysToDayOfWeek)
    $(LREF monthsToMonth)
))
$(TR $(TD Time units) $(TD
    $(LREF cmpTimeUnits)
    $(LREF timeStrings)
))
$(TR $(TD Other) $(TD
    $(LREF AllowDayOverflow)
    $(LREF DateTimeException)
))
)

    License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
    Authors:   $(HTTP jmdavisprog.com, Jonathan M Davis)
    Source:    $(PHOBOSSRC std/datetime/date.d)
+/
module std.datetime.date;

// Note: reconsider using specific imports below after
// https://issues.dlang.org/show_bug.cgi?id=17630 has been fixed
import core.time;// : TimeException;
import std.traits : isSomeString, Unqual;
import std.typecons : Flag;
import std.range.primitives : isOutputRange;

version (unittest) import std.exception : assertThrown;

@safe unittest
{
    initializeTests();
}


/++
    Exception type used by std.datetime. It's an alias to
    $(REF TimeException,core,time). Either can be caught without concern about
    which module it came from.
  +/
alias DateTimeException = TimeException;


/++
    Represents the 12 months of the Gregorian year (January is 1).
  +/
enum Month : ubyte
{
    jan = 1, ///
    feb,     ///
    mar,     ///
    apr,     ///
    may,     ///
    jun,     ///
    jul,     ///
    aug,     ///
    sep,     ///
    oct,     ///
    nov,     ///
    dec      ///
}

///
@safe pure unittest
{
    assert(Date(2018, 10, 1).month == Month.oct);
    assert(DateTime(1, 1, 1).month == Month.jan);
}


/++
    Represents the 7 days of the Gregorian week (Sunday is 0).
  +/
enum DayOfWeek : ubyte
{
    sun = 0, ///
    mon,     ///
    tue,     ///
    wed,     ///
    thu,     ///
    fri,     ///
    sat      ///
}

///
@safe pure unittest
{
    assert(Date(2018, 10, 1).dayOfWeek == DayOfWeek.mon);
    assert(DateTime(5, 5, 5).dayOfWeek == DayOfWeek.thu);
}

/++
    In some date calculations, adding months or years can cause the date to fall
    on a day of the month which is not valid (e.g. February 29th 2001 or
    June 31st 2000). If overflow is allowed (as is the default), then the month
    will be incremented accordingly (so, February 29th 2001 would become
    March 1st 2001, and June 31st 2000 would become July 1st 2000). If overflow
    is not allowed, then the day will be adjusted to the last valid day in that
    month (so, February 29th 2001 would become February 28th 2001 and
    June 31st 2000 would become June 30th 2000).

    AllowDayOverflow only applies to calculations involving months or years.

    If set to `AllowDayOverflow.no`, then day overflow is not allowed.

    Otherwise, if set to `AllowDayOverflow.yes`, then day overflow is
    allowed.
  +/
alias AllowDayOverflow = Flag!"allowDayOverflow";


/++
    Array of the strings representing time units, starting with the smallest
    unit and going to the largest. It does not include `"nsecs"`.

    Includes `"hnsecs"` (hecto-nanoseconds (100 ns)),
    `"usecs"` (microseconds), `"msecs"` (milliseconds), `"seconds"`,
    `"minutes"`, `"hours"`, `"days"`, `"weeks"`, `"months"`, and
    `"years"`
  +/
immutable string[] timeStrings = ["hnsecs", "usecs", "msecs", "seconds", "minutes",
                                  "hours", "days", "weeks", "months", "years"];


/++
    Combines the $(REF Date,std,datetime,date) and
    $(REF TimeOfDay,std,datetime,date) structs to give an object which holds
    both the date and the time. It is optimized for calendar-based operations
    and has no concept of time zone. For an object which is optimized for time
    operations based on the system time, use
    $(REF SysTime,std,datetime,systime). $(REF SysTime,std,datetime,systime) has
    a concept of time zone and has much higher precision (hnsecs). `DateTime`
    is intended primarily for calendar-based uses rather than precise time
    operations.
  +/
struct DateTime
{
public:

    /++
        Params:
            date = The date portion of $(LREF DateTime).
            tod  = The time portion of $(LREF DateTime).
      +/
    this(Date date, TimeOfDay tod = TimeOfDay.init) @safe pure nothrow @nogc
    {
        _date = date;
        _tod = tod;
    }

    @safe unittest
    {
        {
            auto dt = DateTime.init;
            assert(dt._date == Date.init);
            assert(dt._tod == TimeOfDay.init);
        }

        {
            auto dt = DateTime(Date(1999, 7 ,6));
            assert(dt._date == Date(1999, 7, 6));
            assert(dt._tod == TimeOfDay.init);
        }

        {
            auto dt = DateTime(Date(1999, 7 ,6), TimeOfDay(12, 30, 33));
            assert(dt._date == Date(1999, 7, 6));
            assert(dt._tod == TimeOfDay(12, 30, 33));
        }
    }


    /++
        Params:
            year   = The year portion of the date.
            month  = The month portion of the date (January is 1).
            day    = The day portion of the date.
            hour   = The hour portion of the time;
            minute = The minute portion of the time;
            second = The second portion of the time;
      +/
    this(int year, int month, int day, int hour = 0, int minute = 0, int second = 0) @safe pure
    {
        _date = Date(year, month, day);
        _tod = TimeOfDay(hour, minute, second);
    }

    @safe unittest
    {
        {
            auto dt = DateTime(1999, 7 ,6);
            assert(dt._date == Date(1999, 7, 6));
            assert(dt._tod == TimeOfDay.init);
        }

        {
            auto dt = DateTime(1999, 7 ,6, 12, 30, 33);
            assert(dt._date == Date(1999, 7, 6));
            assert(dt._tod == TimeOfDay(12, 30, 33));
        }
    }


    /++
        Compares this $(LREF DateTime) with the given `DateTime.`.

        Returns:
            $(BOOKTABLE,
            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
            $(TR $(TD this == rhs) $(TD 0))
            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
            )
     +/
    int opCmp(DateTime rhs) const @safe pure nothrow @nogc
    {
        immutable dateResult = _date.opCmp(rhs._date);

        if (dateResult != 0)
            return dateResult;

        return _tod.opCmp(rhs._tod);
    }

    @safe unittest
    {
        // Test A.D.
        assert(DateTime(Date.init, TimeOfDay.init).opCmp(DateTime.init) == 0);

        assert(DateTime(Date(1999, 1, 1)).opCmp(DateTime(Date(1999, 1, 1))) == 0);
        assert(DateTime(Date(1, 7, 1)).opCmp(DateTime(Date(1, 7, 1))) == 0);
        assert(DateTime(Date(1, 1, 6)).opCmp(DateTime(Date(1, 1, 6))) == 0);

        assert(DateTime(Date(1999, 7, 1)).opCmp(DateTime(Date(1999, 7, 1))) == 0);
        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) == 0);

        assert(DateTime(Date(1, 7, 6)).opCmp(DateTime(Date(1, 7, 6))) == 0);

        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
        assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0);
        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 8, 6))) < 0);
        assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0);
        assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) < 0);
        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 7, 6))) > 0);

        assert(DateTime(Date(1999, 8, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
        assert(DateTime(Date(2000, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);
        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0);
        assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);
        assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 8, 6))) < 0);
        assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0);


        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0))) == 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0))) == 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0))) == 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))) == 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) == 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))) == 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp(
                   DateTime(Date(1999, 7, 7), TimeOfDay(12, 31, 33))) < 0);
        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp(
                   DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0);

        // Test B.C.
        assert(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33))) == 0);
        assert(DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33))) == 0);
        assert(DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33))) == 0);

        assert(DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33))) == 0);
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) == 0);

        assert(DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33))) == 0);

        assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(-2000, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0);
        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0);

        // Test Both
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33))) > 0);

        assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(1999, 6, 6), TimeOfDay(12, 30, 33))) < 0);
        assert(DateTime(Date(1999, 6, 8), TimeOfDay(12, 30, 33)).opCmp(
                   DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0);

        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30));
        assert(dt.opCmp(dt) == 0);
        assert(dt.opCmp(cdt) == 0);
        assert(dt.opCmp(idt) == 0);
        assert(cdt.opCmp(dt) == 0);
        assert(cdt.opCmp(cdt) == 0);
        assert(cdt.opCmp(idt) == 0);
        assert(idt.opCmp(dt) == 0);
        assert(idt.opCmp(cdt) == 0);
        assert(idt.opCmp(idt) == 0);
    }


    /++
        The date portion of $(LREF DateTime).
      +/
    @property Date date() const @safe pure nothrow @nogc
    {
        return _date;
    }

    @safe unittest
    {
        {
            auto dt = DateTime.init;
            assert(dt.date == Date.init);
        }

        {
            auto dt = DateTime(Date(1999, 7, 6));
            assert(dt.date == Date(1999, 7, 6));
        }

        const cdt = DateTime(1999, 7, 6);
        immutable idt = DateTime(1999, 7, 6);
        assert(cdt.date == Date(1999, 7, 6));
        assert(idt.date == Date(1999, 7, 6));
    }


    /++
        The date portion of $(LREF DateTime).

        Params:
            date = The Date to set this $(LREF DateTime)'s date portion to.
      +/
    @property void date(Date date) @safe pure nothrow @nogc
    {
        _date = date;
    }

    @safe unittest
    {
        auto dt = DateTime.init;
        dt.date = Date(1999, 7, 6);
        assert(dt._date == Date(1999, 7, 6));
        assert(dt._tod == TimeOfDay.init);

        const cdt = DateTime(1999, 7, 6);
        immutable idt = DateTime(1999, 7, 6);
        static assert(!__traits(compiles, cdt.date = Date(2010, 1, 1)));
        static assert(!__traits(compiles, idt.date = Date(2010, 1, 1)));
    }


    /++
        The time portion of $(LREF DateTime).
      +/
    @property TimeOfDay timeOfDay() const @safe pure nothrow @nogc
    {
        return _tod;
    }

    @safe unittest
    {
        {
            auto dt = DateTime.init;
            assert(dt.timeOfDay == TimeOfDay.init);
        }

        {
            auto dt = DateTime(Date.init, TimeOfDay(12, 30, 33));
            assert(dt.timeOfDay == TimeOfDay(12, 30, 33));
        }

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.timeOfDay == TimeOfDay(12, 30, 33));
        assert(idt.timeOfDay == TimeOfDay(12, 30, 33));
    }


    /++
        The time portion of $(LREF DateTime).

        Params:
            tod = The $(REF TimeOfDay,std,datetime,date) to set this
                  $(LREF DateTime)'s time portion to.
      +/
    @property void timeOfDay(TimeOfDay tod) @safe pure nothrow @nogc
    {
        _tod = tod;
    }

    @safe unittest
    {
        auto dt = DateTime.init;
        dt.timeOfDay = TimeOfDay(12, 30, 33);
        assert(dt._date == Date.init);
        assert(dt._tod == TimeOfDay(12, 30, 33));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.timeOfDay = TimeOfDay(12, 30, 33)));
        static assert(!__traits(compiles, idt.timeOfDay = TimeOfDay(12, 30, 33)));
    }


    /++
        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
        are B.C.
     +/
    @property short year() const @safe pure nothrow @nogc
    {
        return _date.year;
    }

    @safe unittest
    {
        assert(Date.init.year == 1);
        assert(Date(1999, 7, 6).year == 1999);
        assert(Date(-1999, 7, 6).year == -1999);

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(idt.year == 1999);
        assert(idt.year == 1999);
    }


    /++
        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
        are B.C.

        Params:
            year = The year to set this $(LREF DateTime)'s year to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the new year is not
            a leap year and if the resulting date would be on February 29th.
     +/
    @property void year(int year) @safe pure
    {
        _date.year = year;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).year == 1999);
        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).year == 2010);
        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).year == -7);
    }

    @safe unittest
    {
        static void testDT(DateTime dt, int year, DateTime expected, size_t line = __LINE__)
        {
            dt.year = year;
            assert(dt == expected);
        }

        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)),
               1999,
               DateTime(Date(1999, 1, 1), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)),
               0,
               DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)),
               -1999,
               DateTime(Date(-1999, 1, 1), TimeOfDay(12, 30, 33)));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.year = 7));
        static assert(!__traits(compiles, idt.year = 7));
    }


    /++
        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.

        Throws:
            $(REF DateTimeException,std,datetime,date) if `isAD` is true.
     +/
    @property short yearBC() const @safe pure
    {
        return _date.yearBC;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33)).yearBC == 1);
        assert(DateTime(Date(-1, 1, 1), TimeOfDay(10, 7, 2)).yearBC == 2);
        assert(DateTime(Date(-100, 1, 1), TimeOfDay(4, 59, 0)).yearBC == 101);
    }

    @safe unittest
    {
        assertThrown!DateTimeException((DateTime dt){dt.yearBC;}(DateTime(Date(1, 1, 1))));

        auto dt = DateTime(1999, 7, 6, 12, 30, 33);
        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        dt.yearBC = 12;
        assert(dt.yearBC == 12);
        static assert(!__traits(compiles, cdt.yearBC = 12));
        static assert(!__traits(compiles, idt.yearBC = 12));
    }


    /++
        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.

        Params:
            year = The year B.C. to set this $(LREF DateTime)'s year to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if a non-positive value
            is given.
     +/
    @property void yearBC(int year) @safe pure
    {
        _date.yearBC = year;
    }

    ///
    @safe unittest
    {
        auto dt = DateTime(Date(2010, 1, 1), TimeOfDay(7, 30, 0));
        dt.yearBC = 1;
        assert(dt == DateTime(Date(0, 1, 1), TimeOfDay(7, 30, 0)));

        dt.yearBC = 10;
        assert(dt == DateTime(Date(-9, 1, 1), TimeOfDay(7, 30, 0)));
    }

    @safe unittest
    {
        assertThrown!DateTimeException((DateTime dt){dt.yearBC = -1;}(DateTime(Date(1, 1, 1))));

        auto dt = DateTime(1999, 7, 6, 12, 30, 33);
        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        dt.yearBC = 12;
        assert(dt.yearBC == 12);
        static assert(!__traits(compiles, cdt.yearBC = 12));
        static assert(!__traits(compiles, idt.yearBC = 12));
    }


    /++
        Month of a Gregorian Year.
     +/
    @property Month month() const @safe pure nothrow @nogc
    {
        return _date.month;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).month == 7);
        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).month == 10);
        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).month == 4);
    }

    @safe unittest
    {
        assert(DateTime.init.month == 1);
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7);
        assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7);

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.month == 7);
        assert(idt.month == 7);
    }


    /++
        Month of a Gregorian Year.

        Params:
            month = The month to set this $(LREF DateTime)'s month to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given month is
            not a valid month.
     +/
    @property void month(Month month) @safe pure
    {
        _date.month = month;
    }

    @safe unittest
    {
        static void testDT(DateTime dt, Month month, DateTime expected = DateTime.init, size_t line = __LINE__)
        {
            dt.month = month;
            assert(expected != DateTime.init);
            assert(dt == expected);
        }

        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month) 0));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month) 13));

        testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)),
               cast(Month) 7,
               DateTime(Date(1, 7, 1), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)),
               cast(Month) 7,
               DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33)));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.month = 12));
        static assert(!__traits(compiles, idt.month = 12));
    }


    /++
        Day of a Gregorian Month.
     +/
    @property ubyte day() const @safe pure nothrow @nogc
    {
        return _date.day;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).day == 6);
        assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).day == 4);
        assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).day == 5);
    }

    @safe unittest
    {
        import std.format : format;
        import std.range : chain;

        static void test(DateTime dateTime, int expected)
        {
            assert(dateTime.day == expected, format("Value given: %s", dateTime));
        }

        foreach (year; chain(testYearsBC, testYearsAD))
        {
            foreach (md; testMonthDays)
            {
                foreach (tod; testTODs)
                    test(DateTime(Date(year, md.month, md.day), tod), md.day);
            }
        }

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.day == 6);
        assert(idt.day == 6);
    }


    /++
        Day of a Gregorian Month.

        Params:
            day = The day of the month to set this $(LREF DateTime)'s day to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given day is not
            a valid day of the current month.
     +/
    @property void day(int day) @safe pure
    {
        _date.day = day;
    }

    @safe unittest
    {
        import std.exception : assertNotThrown;

        static void testDT(DateTime dt, int day)
        {
            dt.day = day;
        }

        // Test A.D.
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 0));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 29));
        assertThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 30));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 32));

        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 28));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 29));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 31));

        {
            auto dt = DateTime(Date(1, 1, 1), TimeOfDay(7, 12, 22));
            dt.day = 6;
            assert(dt == DateTime(Date(1, 1, 6), TimeOfDay(7, 12, 22)));
        }

        // Test B.C.
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 0));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 29));
        assertThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 30));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 32));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 31));
        assertThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 32));

        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 28));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 29));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 31));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 30));
        assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 31));

        auto dt = DateTime(Date(-1, 1, 1), TimeOfDay(7, 12, 22));
        dt.day = 6;
        assert(dt == DateTime(Date(-1, 1, 6), TimeOfDay(7, 12, 22)));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.day = 27));
        static assert(!__traits(compiles, idt.day = 27));
    }


    /++
        Hours past midnight.
     +/
    @property ubyte hour() const @safe pure nothrow @nogc
    {
        return _tod.hour;
    }

    @safe unittest
    {
        assert(DateTime.init.hour == 0);
        assert(DateTime(Date.init, TimeOfDay(12, 0, 0)).hour == 12);

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.hour == 12);
        assert(idt.hour == 12);
    }


    /++
        Hours past midnight.

        Params:
            hour = The hour of the day to set this $(LREF DateTime)'s hour to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given hour would
            result in an invalid $(LREF DateTime).
     +/
    @property void hour(int hour) @safe pure
    {
        _tod.hour = hour;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).hour = 24;}());

        auto dt = DateTime.init;
        dt.hour = 12;
        assert(dt == DateTime(1, 1, 1, 12, 0, 0));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.hour = 27));
        static assert(!__traits(compiles, idt.hour = 27));
    }


    /++
        Minutes past the hour.
     +/
    @property ubyte minute() const @safe pure nothrow @nogc
    {
        return _tod.minute;
    }

    @safe unittest
    {
        assert(DateTime.init.minute == 0);
        assert(DateTime(1, 1, 1, 0, 30, 0).minute == 30);

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.minute == 30);
        assert(idt.minute == 30);
    }


    /++
        Minutes past the hour.

        Params:
            minute = The minute to set this $(LREF DateTime)'s minute to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given minute
            would result in an invalid $(LREF DateTime).
     +/
    @property void minute(int minute) @safe pure
    {
        _tod.minute = minute;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){DateTime.init.minute = 60;}());

        auto dt = DateTime.init;
        dt.minute = 30;
        assert(dt == DateTime(1, 1, 1, 0, 30, 0));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.minute = 27));
        static assert(!__traits(compiles, idt.minute = 27));
    }


    /++
        Seconds past the minute.
     +/
    @property ubyte second() const @safe pure nothrow @nogc
    {
        return _tod.second;
    }

    @safe unittest
    {
        assert(DateTime.init.second == 0);
        assert(DateTime(1, 1, 1, 0, 0, 33).second == 33);

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.second == 33);
        assert(idt.second == 33);
    }


    /++
        Seconds past the minute.

        Params:
            second = The second to set this $(LREF DateTime)'s second to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given seconds
            would result in an invalid $(LREF DateTime).
     +/
    @property void second(int second) @safe pure
    {
        _tod.second = second;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){DateTime.init.second = 60;}());

        auto dt = DateTime.init;
        dt.second = 33;
        assert(dt == DateTime(1, 1, 1, 0, 0, 33));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.second = 27));
        static assert(!__traits(compiles, idt.second = 27));
    }


    /++
        Adds the given number of years or months to this $(LREF DateTime). A
        negative number will subtract.

        Note that if day overflow is allowed, and the date with the adjusted
        year/month overflows the number of days in the new month, then the month
        will be incremented by one, and the day set to the number of days
        overflowed. (e.g. if the day were 31 and the new month were June, then
        the month would be incremented to July, and the new day would be 1). If
        day overflow is not allowed, then the day will be set to the last valid
        day in the month (e.g. June 31st would become June 30th).

        Params:
            units         = The type of units to add ("years" or "months").
            value         = The number of months or years to add to this
                            $(LREF DateTime).
            allowOverflow = Whether the days should be allowed to overflow,
                            causing the month to increment.
      +/
    ref DateTime add(string units)
                    (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc
        if (units == "years" || units == "months")
    {
        _date.add!units(value, allowOverflow);
        return this;
    }

    ///
    @safe unittest
    {
        auto dt1 = DateTime(2010, 1, 1, 12, 30, 33);
        dt1.add!"months"(11);
        assert(dt1 == DateTime(2010, 12, 1, 12, 30, 33));

        auto dt2 = DateTime(2010, 1, 1, 12, 30, 33);
        dt2.add!"months"(-11);
        assert(dt2 == DateTime(2009, 2, 1, 12, 30, 33));

        auto dt3 = DateTime(2000, 2, 29, 12, 30, 33);
        dt3.add!"years"(1);
        assert(dt3 == DateTime(2001, 3, 1, 12, 30, 33));

        auto dt4 = DateTime(2000, 2, 29, 12, 30, 33);
        dt4.add!"years"(1, AllowDayOverflow.no);
        assert(dt4 == DateTime(2001, 2, 28, 12, 30, 33));
    }

    @safe unittest
    {
        auto dt = DateTime(2000, 1, 31);
        dt.add!"years"(7).add!"months"(-4);
        assert(dt == DateTime(2006, 10, 1));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.add!"years"(4)));
        static assert(!__traits(compiles, idt.add!"years"(4)));
        static assert(!__traits(compiles, cdt.add!"months"(4)));
        static assert(!__traits(compiles, idt.add!"months"(4)));
    }


    /++
        Adds the given number of years or months to this $(LREF DateTime). A
        negative number will subtract.

        The difference between rolling and adding is that rolling does not
        affect larger units. Rolling a $(LREF DateTime) 12 months
        gets the exact same $(LREF DateTime). However, the days can still be
        affected due to the differing number of days in each month.

        Because there are no units larger than years, there is no difference
        between adding and rolling years.

        Params:
            units         = The type of units to add ("years" or "months").
            value         = The number of months or years to add to this
                            $(LREF DateTime).
            allowOverflow = Whether the days should be allowed to overflow,
                            causing the month to increment.
      +/
    ref DateTime roll(string units)
                     (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc
        if (units == "years" || units == "months")
    {
        _date.roll!units(value, allowOverflow);
        return this;
    }

    ///
    @safe unittest
    {
        auto dt1 = DateTime(2010, 1, 1, 12, 33, 33);
        dt1.roll!"months"(1);
        assert(dt1 == DateTime(2010, 2, 1, 12, 33, 33));

        auto dt2 = DateTime(2010, 1, 1, 12, 33, 33);
        dt2.roll!"months"(-1);
        assert(dt2 == DateTime(2010, 12, 1, 12, 33, 33));

        auto dt3 = DateTime(1999, 1, 29, 12, 33, 33);
        dt3.roll!"months"(1);
        assert(dt3 == DateTime(1999, 3, 1, 12, 33, 33));

        auto dt4 = DateTime(1999, 1, 29, 12, 33, 33);
        dt4.roll!"months"(1, AllowDayOverflow.no);
        assert(dt4 == DateTime(1999, 2, 28, 12, 33, 33));

        auto dt5 = DateTime(2000, 2, 29, 12, 30, 33);
        dt5.roll!"years"(1);
        assert(dt5 == DateTime(2001, 3, 1, 12, 30, 33));

        auto dt6 = DateTime(2000, 2, 29, 12, 30, 33);
        dt6.roll!"years"(1, AllowDayOverflow.no);
        assert(dt6 == DateTime(2001, 2, 28, 12, 30, 33));
    }

    @safe unittest
    {
        auto dt = DateTime(2000, 1, 31);
        dt.roll!"years"(7).roll!"months"(-4);
        assert(dt == DateTime(2007, 10, 1));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.roll!"years"(4)));
        static assert(!__traits(compiles, idt.roll!"years"(4)));
        static assert(!__traits(compiles, cdt.roll!"months"(4)));
        static assert(!__traits(compiles, idt.roll!"months"(4)));
    }


    /++
        Adds the given number of units to this $(LREF DateTime). A negative
        number will subtract.

        The difference between rolling and adding is that rolling does not
        affect larger units. For instance, rolling a $(LREF DateTime) one
        year's worth of days gets the exact same $(LREF DateTime).

        Accepted units are `"days"`, `"minutes"`, `"hours"`,
        `"minutes"`, and `"seconds"`.

        Params:
            units = The units to add.
            value = The number of $(D_PARAM units) to add to this
                    $(LREF DateTime).
      +/
    ref DateTime roll(string units)(long value) @safe pure nothrow @nogc
        if (units == "days")
    {
        _date.roll!"days"(value);
        return this;
    }

    ///
    @safe unittest
    {
        auto dt1 = DateTime(2010, 1, 1, 11, 23, 12);
        dt1.roll!"days"(1);
        assert(dt1 == DateTime(2010, 1, 2, 11, 23, 12));
        dt1.roll!"days"(365);
        assert(dt1 == DateTime(2010, 1, 26, 11, 23, 12));
        dt1.roll!"days"(-32);
        assert(dt1 == DateTime(2010, 1, 25, 11, 23, 12));

        auto dt2 = DateTime(2010, 7, 4, 12, 0, 0);
        dt2.roll!"hours"(1);
        assert(dt2 == DateTime(2010, 7, 4, 13, 0, 0));

        auto dt3 = DateTime(2010, 1, 1, 0, 0, 0);
        dt3.roll!"seconds"(-1);
        assert(dt3 == DateTime(2010, 1, 1, 0, 0, 59));
    }

    @safe unittest
    {
        auto dt = DateTime(2000, 1, 31);
        dt.roll!"days"(7).roll!"days"(-4);
        assert(dt == DateTime(2000, 1, 3));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.roll!"days"(4)));
        static assert(!__traits(compiles, idt.roll!"days"(4)));
    }


    /// ditto
    ref DateTime roll(string units)(long value) @safe pure nothrow @nogc
        if (units == "hours" ||
            units == "minutes" ||
            units == "seconds")
    {
        _tod.roll!units(value);
        return this;
    }

    // Test roll!"hours"().
    @safe unittest
    {
        static void testDT(DateTime orig, int hours, DateTime expected, size_t line = __LINE__)
        {
            orig.roll!"hours"(hours);
            assert(orig == expected);
        }

        // Test A.D.
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 6,
               DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7,
               DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 8,
               DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 9,
               DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 11,
               DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 12,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 13,
               DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 14,
               DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 16,
               DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 17,
               DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 18,
               DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 19,
               DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 20,
               DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 21,
               DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 22,
               DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 23,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 24,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 25,
               DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -6,
               DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -7,
               DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -8,
               DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -9,
               DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -11,
               DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -12,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -13,
               DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -14,
               DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -16,
               DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -17,
               DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -18,
               DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -19,
               DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -20,
               DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -21,
               DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -22,
               DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -23,
               DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -24,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -25,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33)));

        testDT(DateTime(Date(1999, 7, 31), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(1999, 7, 31), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 8, 1), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(1999, 8, 1), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(1999, 12, 31), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(1999, 12, 31), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(2000, 1, 1), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(2000, 1, 1), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(1999, 2, 28), TimeOfDay(23, 30, 33)), 25,
               DateTime(Date(1999, 2, 28), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(1999, 3, 2), TimeOfDay(0, 30, 33)), -25,
               DateTime(Date(1999, 3, 2), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(2000, 2, 28), TimeOfDay(23, 30, 33)), 25,
               DateTime(Date(2000, 2, 28), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(2000, 3, 1), TimeOfDay(0, 30, 33)), -25,
               DateTime(Date(2000, 3, 1), TimeOfDay(23, 30, 33)));

        // Test B.C.
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 6,
               DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7,
               DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 8,
               DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 9,
               DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 11,
               DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 12,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 13,
               DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 14,
               DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 16,
               DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 17,
               DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 18,
               DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 19,
               DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 20,
               DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 21,
               DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 22,
               DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 23,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 24,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 25,
               DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -6,
               DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -7,
               DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -8,
               DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -9,
               DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -11,
               DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -12,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -13,
               DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -14,
               DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -16,
               DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -17,
               DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -18,
               DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -19,
               DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -20,
               DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -21,
               DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -22,
               DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -23,
               DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -24,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -25,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 31), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(-1999, 7, 31), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-1999, 8, 1), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(-1999, 8, 1), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(-2001, 12, 31), TimeOfDay(23, 30, 33)), 1,
               DateTime(Date(-2001, 12, 31), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-2000, 1, 1), TimeOfDay(0, 30, 33)), -1,
               DateTime(Date(-2000, 1, 1), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(-2001, 2, 28), TimeOfDay(23, 30, 33)), 25,
               DateTime(Date(-2001, 2, 28), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-2001, 3, 2), TimeOfDay(0, 30, 33)), -25,
               DateTime(Date(-2001, 3, 2), TimeOfDay(23, 30, 33)));

        testDT(DateTime(Date(-2000, 2, 28), TimeOfDay(23, 30, 33)), 25,
               DateTime(Date(-2000, 2, 28), TimeOfDay(0, 30, 33)));
        testDT(DateTime(Date(-2000, 3, 1), TimeOfDay(0, 30, 33)), -25,
               DateTime(Date(-2000, 3, 1), TimeOfDay(23, 30, 33)));

        // Test Both
        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 17_546,
               DateTime(Date(-1, 1, 1), TimeOfDay(13, 30, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -17_546,
               DateTime(Date(1, 1, 1), TimeOfDay(11, 30, 33)));

        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
        dt.roll!"hours"(27).roll!"hours"(-9);
        assert(dt == DateTime(2000, 1, 31, 3, 7, 6));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.roll!"hours"(4)));
        static assert(!__traits(compiles, idt.roll!"hours"(4)));
    }

    // Test roll!"minutes"().
    @safe unittest
    {
        static void testDT(DateTime orig, int minutes, DateTime expected, size_t line = __LINE__)
        {
            orig.roll!"minutes"(minutes);
            assert(orig == expected);
        }

        // Test A.D.
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 32, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 34, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 35, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 40, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 29,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 45,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 75,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 90,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 100,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 10, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 689,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 690,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 691,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 960,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1439,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1440,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1441,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2880,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 28, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 27, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 26, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 25, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 20, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -29,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -30,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -45,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -75,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -90,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -100,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 50, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -749,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -750,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -751,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -960,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1439,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1440,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1441,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2880,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(11, 58, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 1, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 59, 33)));

        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 1,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 0, 33)));
        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 0,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)));
        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), -1,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 58, 33)));

        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 1,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 0, 33)));
        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 0,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)));
        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), -1,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 58, 33)));

        // Test B.C.
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 32, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 33, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 34, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 35, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 40, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 29,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 45,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 75,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 90,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 100,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 10, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 689,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 690,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 691,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 960,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1439,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1440,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1441,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2880,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 28, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 27, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 26, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 25, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 20, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -29,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -30,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -45,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -75,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -90,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -100,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 50, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -749,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -750,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -751,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -960,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1439,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1440,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1441,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2880,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(11, 58, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 1, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 59, 33)));

        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 1,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 0, 33)));
        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 0,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)));
        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), -1,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 58, 33)));

        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 1,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 0, 33)));
        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 0,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)));
        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), -1,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 58, 33)));

        // Test Both
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(1, 1, 1), TimeOfDay(0, 59, 0)));
        testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0)), 1,
               DateTime(Date(0, 12, 31), TimeOfDay(23, 0, 0)));

        testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(0, 1, 1), TimeOfDay(0, 59, 0)));
        testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0)), 1,
               DateTime(Date(-1, 12, 31), TimeOfDay(23, 0, 0)));

        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_760,
               DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -1_052_760,
               DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));

        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_782,
               DateTime(Date(-1, 1, 1), TimeOfDay(11, 52, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 52, 33)), -1_052_782,
               DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));

        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
        dt.roll!"minutes"(92).roll!"minutes"(-292);
        assert(dt == DateTime(2000, 1, 31, 9, 47, 6));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.roll!"minutes"(4)));
        static assert(!__traits(compiles, idt.roll!"minutes"(4)));
    }

    // Test roll!"seconds"().
    @safe unittest
    {
        static void testDT(DateTime orig, int seconds, DateTime expected, size_t line = __LINE__)
        {
            orig.roll!"seconds"(seconds);
            assert(orig == expected);
        }

        // Test A.D.
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 35)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 36)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 37)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 38)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 43)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 48)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 26,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 27,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 3)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 59,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 61,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1766,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1767,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1768,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2007,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3599,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3600,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3601,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7200,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 31)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 30)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 29)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 28)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 23)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 18)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -33,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -34,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -35,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 58)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -59,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -61,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 1)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 59)));

        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 1,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 1)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 0,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)));
        testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 59)));

        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 0)));
        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 0,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)));
        testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), -1,
               DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 58)));

        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 0)));
        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 0,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)));
        testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), -1,
               DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 58)));

        // Test B.C.
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 35)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 36)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 37)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 38)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 43)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 48)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 26,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 27,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 3)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 59,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 61,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1766,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1767,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1768,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2007,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3599,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3600,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3601,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7200,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 31)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 30)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 29)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 28)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 23)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 18)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -33,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -34,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -35,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 58)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -59,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -61,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 1)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 59)));

        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 1)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 0,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)));
        testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 59)));

        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 0)));
        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 0,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)));
        testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), -1,
               DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 58)));

        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 0)));
        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 0,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)));
        testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), -1,
               DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 58)));

        // Test Both
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 59)));
        testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0)));

        testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1,
               DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 59)));
        testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 59)), 1,
               DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0)));

        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_600L,
               DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -63_165_600L,
               DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));

        testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_617L,
               DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 50)));
        testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 50)), -63_165_617L,
               DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)));

        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
        dt.roll!"seconds"(92).roll!"seconds"(-292);
        assert(dt == DateTime(2000, 1, 31, 9, 7, 46));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt.roll!"seconds"(4)));
        static assert(!__traits(compiles, idt.roll!"seconds"(4)));
    }


    import core.time : Duration;
    /++
        Gives the result of adding or subtracting a $(REF Duration, core,time)
        from this $(LREF DateTime).

        The legal types of arithmetic for $(LREF DateTime) using this operator
        are

        $(BOOKTABLE,
        $(TR $(TD DateTime) $(TD +) $(TD Duration) $(TD -->) $(TD DateTime))
        $(TR $(TD DateTime) $(TD -) $(TD Duration) $(TD -->) $(TD DateTime))
        )

        Params:
            duration = The $(REF Duration, core,time) to add to or subtract from
                       this $(LREF DateTime).
      +/
    DateTime opBinary(string op)(Duration duration) const @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        DateTime retval = this;
        immutable seconds = duration.total!"seconds";
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    ///
    @safe unittest
    {
        import core.time : hours, seconds;

        assert(DateTime(2015, 12, 31, 23, 59, 59) + seconds(1) ==
               DateTime(2016, 1, 1, 0, 0, 0));

        assert(DateTime(2015, 12, 31, 23, 59, 59) + hours(1) ==
               DateTime(2016, 1, 1, 0, 59, 59));

        assert(DateTime(2016, 1, 1, 0, 0, 0) - seconds(1) ==
               DateTime(2015, 12, 31, 23, 59, 59));

        assert(DateTime(2016, 1, 1, 0, 59, 59) - hours(1) ==
               DateTime(2015, 12, 31, 23, 59, 59));
    }

    @safe unittest
    {
        import core.time : dur;

        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));

        assert(dt + dur!"weeks"(7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
        assert(dt + dur!"weeks"(-7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
        assert(dt + dur!"days"(7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
        assert(dt + dur!"days"(-7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));

        assert(dt + dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        assert(dt + dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        assert(dt + dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
        assert(dt + dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
        assert(dt + dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt + dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt + dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt + dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt + dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt + dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt + dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt + dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));

        assert(dt - dur!"weeks"(-7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
        assert(dt - dur!"weeks"(7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
        assert(dt - dur!"days"(-7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
        assert(dt - dur!"days"(7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));

        assert(dt - dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        assert(dt - dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        assert(dt - dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
        assert(dt - dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
        assert(dt - dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt - dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt - dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt - dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt - dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt - dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(dt - dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(dt - dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));

        auto duration = dur!"seconds"(12);
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt + duration == DateTime(1999, 7, 6, 12, 30, 45));
        assert(idt + duration == DateTime(1999, 7, 6, 12, 30, 45));
        assert(cdt - duration == DateTime(1999, 7, 6, 12, 30, 21));
        assert(idt - duration == DateTime(1999, 7, 6, 12, 30, 21));
    }


    /++
        Gives the result of adding or subtracting a duration from this
        $(LREF DateTime), as well as assigning the result to this
        $(LREF DateTime).

        The legal types of arithmetic for $(LREF DateTime) using this operator
        are

        $(BOOKTABLE,
        $(TR $(TD DateTime) $(TD +) $(TD duration) $(TD -->) $(TD DateTime))
        $(TR $(TD DateTime) $(TD -) $(TD duration) $(TD -->) $(TD DateTime))
        )

        Params:
            duration = The duration to add to or subtract from this
                       $(LREF DateTime).
      +/
    ref DateTime opOpAssign(string op)(Duration duration) @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        import core.time : convert;
        import std.format : format;

        DateTime retval = this;
        immutable hnsecs = duration.total!"hnsecs";

        mixin(format(`return _addSeconds(convert!("hnsecs", "seconds")(%shnsecs));`, op));
    }

    @safe unittest
    {
        import core.time : dur;
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(7) ==
               DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(-7) ==
               DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(7) ==
               DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(-7) ==
               DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(7_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(-7_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(7_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(-7_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(70_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(-70_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(-7) ==
               DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(7) ==
               DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(-7) ==
               DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(7) ==
               DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33)));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(-7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(7) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(-7_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(7_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(-7_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(7_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(-70_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(70_000_000) ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));

        auto dt = DateTime(2000, 1, 31, 9, 7, 6);
        (dt += dur!"seconds"(92)) -= dur!"days"(-500);
        assert(dt == DateTime(2001, 6, 14, 9, 8, 38));

        auto duration = dur!"seconds"(12);
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        static assert(!__traits(compiles, cdt += duration));
        static assert(!__traits(compiles, idt += duration));
        static assert(!__traits(compiles, cdt -= duration));
        static assert(!__traits(compiles, idt -= duration));
    }


    /++
        Gives the difference between two $(LREF DateTime)s.

        The legal types of arithmetic for $(LREF DateTime) using this operator are

        $(BOOKTABLE,
        $(TR $(TD DateTime) $(TD -) $(TD DateTime) $(TD -->) $(TD duration))
        )
      +/
    Duration opBinary(string op)(DateTime rhs) const @safe pure nothrow @nogc
        if (op == "-")
    {
        immutable dateResult = _date - rhs.date;
        immutable todResult = _tod - rhs._tod;

        import core.time : dur;
        return dur!"hnsecs"(dateResult.total!"hnsecs" + todResult.total!"hnsecs");
    }

    @safe unittest
    {
        auto dt = DateTime(1999, 7, 6, 12, 30, 33);

        import core.time : dur;
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(31_536_000));
        assert(DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(-31_536_000));

        assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(26_78_400));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(-26_78_400));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(86_400));
        assert(DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(-86_400));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) ==
               dur!"seconds"(3600));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(-3600));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(60));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) ==
               dur!"seconds"(-60));

        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) ==
               dur!"seconds"(1));
        assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) ==
               dur!"seconds"(-1));

        assert(DateTime(1, 1, 1, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(45033));
        assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(1, 1, 1, 12, 30, 33) == dur!"seconds"(-45033));
        assert(DateTime(0, 12, 31, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(-41367));
        assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(0, 12, 31, 12, 30, 33) == dur!"seconds"(41367));

        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt - dt == Duration.zero);
        assert(cdt - dt == Duration.zero);
        assert(idt - dt == Duration.zero);

        assert(dt - cdt == Duration.zero);
        assert(cdt - cdt == Duration.zero);
        assert(idt - cdt == Duration.zero);

        assert(dt - idt == Duration.zero);
        assert(cdt - idt == Duration.zero);
        assert(idt - idt == Duration.zero);
    }


    /++
        Returns the difference between the two $(LREF DateTime)s in months.

        To get the difference in years, subtract the year property
        of two $(LREF DateTime)s. To get the difference in days or weeks,
        subtract the $(LREF DateTime)s themselves and use the
        $(REF Duration, core,time) that results. Because converting between
        months and smaller units requires a specific date (which
        $(REF Duration, core,time)s don't have), getting the difference in
        months requires some math using both the year and month properties, so
        this is a convenience function for getting the difference in months.

        Note that the number of days in the months or how far into the month
        either date is is irrelevant. It is the difference in the month property
        combined with the difference in years * 12. So, for instance,
        December 31st and January 1st are one month apart just as December 1st
        and January 31st are one month apart.

        Params:
            rhs = The $(LREF DateTime) to subtract from this one.
      +/
    int diffMonths(DateTime rhs) const @safe pure nothrow @nogc
    {
        return _date.diffMonths(rhs._date);
    }

    ///
    @safe unittest
    {
        assert(DateTime(1999, 2, 1, 12, 2, 3).diffMonths(
                   DateTime(1999, 1, 31, 23, 59, 59)) == 1);

        assert(DateTime(1999, 1, 31, 0, 0, 0).diffMonths(
                   DateTime(1999, 2, 1, 12, 3, 42)) == -1);

        assert(DateTime(1999, 3, 1, 5, 30, 0).diffMonths(
                   DateTime(1999, 1, 1, 2, 4, 7)) == 2);

        assert(DateTime(1999, 1, 1, 7, 2, 4).diffMonths(
                   DateTime(1999, 3, 31, 0, 30, 58)) == -2);
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt.diffMonths(dt) == 0);
        assert(cdt.diffMonths(dt) == 0);
        assert(idt.diffMonths(dt) == 0);

        assert(dt.diffMonths(cdt) == 0);
        assert(cdt.diffMonths(cdt) == 0);
        assert(idt.diffMonths(cdt) == 0);

        assert(dt.diffMonths(idt) == 0);
        assert(cdt.diffMonths(idt) == 0);
        assert(idt.diffMonths(idt) == 0);
    }


    /++
        Whether this $(LREF DateTime) is in a leap year.
     +/
    @property bool isLeapYear() const @safe pure nothrow @nogc
    {
        return _date.isLeapYear;
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(!dt.isLeapYear);
        assert(!cdt.isLeapYear);
        assert(!idt.isLeapYear);
    }


    /++
        Day of the week this $(LREF DateTime) is on.
      +/
    @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc
    {
        return _date.dayOfWeek;
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt.dayOfWeek == DayOfWeek.tue);
        assert(cdt.dayOfWeek == DayOfWeek.tue);
        assert(idt.dayOfWeek == DayOfWeek.tue);
    }


    /++
        Day of the year this $(LREF DateTime) is on.
      +/
    @property ushort dayOfYear() const @safe pure nothrow @nogc
    {
        return _date.dayOfYear;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 1, 1), TimeOfDay(12, 22, 7)).dayOfYear == 1);
        assert(DateTime(Date(1999, 12, 31), TimeOfDay(7, 2, 59)).dayOfYear == 365);
        assert(DateTime(Date(2000, 12, 31), TimeOfDay(21, 20, 0)).dayOfYear == 366);
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt.dayOfYear == 187);
        assert(cdt.dayOfYear == 187);
        assert(idt.dayOfYear == 187);
    }


    /++
        Day of the year.

        Params:
            day = The day of the year to set which day of the year this
                  $(LREF DateTime) is on.
      +/
    @property void dayOfYear(int day) @safe pure
    {
        _date.dayOfYear = day;
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        dt.dayOfYear = 12;
        assert(dt.dayOfYear == 12);
        static assert(!__traits(compiles, cdt.dayOfYear = 12));
        static assert(!__traits(compiles, idt.dayOfYear = 12));
    }


    /++
        The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on.
     +/
    @property int dayOfGregorianCal() const @safe pure nothrow @nogc
    {
        return _date.dayOfGregorianCal;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).dayOfGregorianCal == 1);
        assert(DateTime(Date(1, 12, 31), TimeOfDay(23, 59, 59)).dayOfGregorianCal == 365);
        assert(DateTime(Date(2, 1, 1), TimeOfDay(2, 2, 2)).dayOfGregorianCal == 366);

        assert(DateTime(Date(0, 12, 31), TimeOfDay(7, 7, 7)).dayOfGregorianCal == 0);
        assert(DateTime(Date(0, 1, 1), TimeOfDay(19, 30, 0)).dayOfGregorianCal == -365);
        assert(DateTime(Date(-1, 12, 31), TimeOfDay(4, 7, 0)).dayOfGregorianCal == -366);

        assert(DateTime(Date(2000, 1, 1), TimeOfDay(9, 30, 20)).dayOfGregorianCal == 730_120);
        assert(DateTime(Date(2010, 12, 31), TimeOfDay(15, 45, 50)).dayOfGregorianCal == 734_137);
    }

    @safe unittest
    {
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.dayOfGregorianCal == 729_941);
        assert(idt.dayOfGregorianCal == 729_941);
    }


    /++
        The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on.
        Setting this property does not affect the time portion of
        $(LREF DateTime).

        Params:
            days = The day of the Gregorian Calendar to set this $(LREF DateTime)
                   to.
     +/
    @property void dayOfGregorianCal(int days) @safe pure nothrow @nogc
    {
        _date.dayOfGregorianCal = days;
    }

    ///
    @safe unittest
    {
        auto dt = DateTime(Date.init, TimeOfDay(12, 0, 0));
        dt.dayOfGregorianCal = 1;
        assert(dt == DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = 365;
        assert(dt == DateTime(Date(1, 12, 31), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = 366;
        assert(dt == DateTime(Date(2, 1, 1), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = 0;
        assert(dt == DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = -365;
        assert(dt == DateTime(Date(-0, 1, 1), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = -366;
        assert(dt == DateTime(Date(-1, 12, 31), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = 730_120;
        assert(dt == DateTime(Date(2000, 1, 1), TimeOfDay(12, 0, 0)));

        dt.dayOfGregorianCal = 734_137;
        assert(dt == DateTime(Date(2010, 12, 31), TimeOfDay(12, 0, 0)));
    }

    @safe unittest
    {
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        static assert(!__traits(compiles, cdt.dayOfGregorianCal = 7));
        static assert(!__traits(compiles, idt.dayOfGregorianCal = 7));
    }


    /++
        The ISO 8601 week of the year that this $(LREF DateTime) is in.

        See_Also:
            $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
      +/
    @property ubyte isoWeek() const @safe pure nothrow
    {
        return _date.isoWeek;
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt.isoWeek == 27);
        assert(cdt.isoWeek == 27);
        assert(idt.isoWeek == 27);
    }


    /++
        $(LREF DateTime) for the last day in the month that this
        $(LREF DateTime) is in. The time portion of endOfMonth is always
        23:59:59.
      +/
    @property DateTime endOfMonth() const @safe pure nothrow
    {
        try
            return DateTime(_date.endOfMonth, TimeOfDay(23, 59, 59));
        catch (Exception e)
            assert(0, "DateTime constructor threw.");
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).endOfMonth ==
               DateTime(Date(1999, 1, 31), TimeOfDay(23, 59, 59)));

        assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).endOfMonth ==
               DateTime(Date(1999, 2, 28), TimeOfDay(23, 59, 59)));

        assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).endOfMonth ==
               DateTime(Date(2000, 2, 29), TimeOfDay(23, 59, 59)));

        assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).endOfMonth ==
               DateTime(Date(2000, 6, 30), TimeOfDay(23, 59, 59)));
    }

    @safe unittest
    {
        // Test A.D.
        assert(DateTime(1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(1999, 1, 31, 23, 59, 59));
        assert(DateTime(1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(1999, 2, 28, 23, 59, 59));
        assert(DateTime(2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(2000, 2, 29, 23, 59, 59));
        assert(DateTime(1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(1999, 3, 31, 23, 59, 59));
        assert(DateTime(1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(1999, 4, 30, 23, 59, 59));
        assert(DateTime(1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(1999, 5, 31, 23, 59, 59));
        assert(DateTime(1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(1999, 6, 30, 23, 59, 59));
        assert(DateTime(1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
        assert(DateTime(1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(1999, 8, 31, 23, 59, 59));
        assert(DateTime(1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(1999, 9, 30, 23, 59, 59));
        assert(DateTime(1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(1999, 10, 31, 23, 59, 59));
        assert(DateTime(1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(1999, 11, 30, 23, 59, 59));
        assert(DateTime(1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(1999, 12, 31, 23, 59, 59));

        // Test B.C.
        assert(DateTime(-1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(-1999, 1, 31, 23, 59, 59));
        assert(DateTime(-1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(-1999, 2, 28, 23, 59, 59));
        assert(DateTime(-2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(-2000, 2, 29, 23, 59, 59));
        assert(DateTime(-1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(-1999, 3, 31, 23, 59, 59));
        assert(DateTime(-1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(-1999, 4, 30, 23, 59, 59));
        assert(DateTime(-1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(-1999, 5, 31, 23, 59, 59));
        assert(DateTime(-1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(-1999, 6, 30, 23, 59, 59));
        assert(DateTime(-1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(-1999, 7, 31, 23, 59, 59));
        assert(DateTime(-1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(-1999, 8, 31, 23, 59, 59));
        assert(DateTime(-1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(-1999, 9, 30, 23, 59, 59));
        assert(DateTime(-1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(-1999, 10, 31, 23, 59, 59));
        assert(DateTime(-1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(-1999, 11, 30, 23, 59, 59));
        assert(DateTime(-1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(-1999, 12, 31, 23, 59, 59));

        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
        assert(idt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59));
    }


    /++
        The last day in the month that this $(LREF DateTime) is in.
      +/
    @property ubyte daysInMonth() const @safe pure nothrow @nogc
    {
        return _date.daysInMonth;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).daysInMonth == 31);
        assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).daysInMonth == 28);
        assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).daysInMonth == 29);
        assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).daysInMonth == 30);
    }

    @safe unittest
    {
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.daysInMonth == 31);
        assert(idt.daysInMonth == 31);
    }


    /++
        Whether the current year is a date in A.D.
      +/
    @property bool isAD() const @safe pure nothrow @nogc
    {
        return _date.isAD;
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 7, 0)).isAD);
        assert(DateTime(Date(2010, 12, 31), TimeOfDay(0, 0, 0)).isAD);
        assert(!DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)).isAD);
        assert(!DateTime(Date(-2010, 1, 1), TimeOfDay(2, 2, 2)).isAD);
    }

    @safe unittest
    {
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.isAD);
        assert(idt.isAD);
    }


    /++
        The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this
        $(LREF DateTime) at the given time. For example, prior to noon,
        1996-03-31 would be the Julian day number 2_450_173, so this function
        returns 2_450_173, while from noon onward, the julian day number would
        be 2_450_174, so this function returns 2_450_174.
      +/
    @property long julianDay() const @safe pure nothrow @nogc
    {
        if (_tod._hour < 12)
            return _date.julianDay - 1;
        else
            return _date.julianDay;
    }

    @safe unittest
    {
        assert(DateTime(Date(-4713, 11, 24), TimeOfDay(0, 0, 0)).julianDay == -1);
        assert(DateTime(Date(-4713, 11, 24), TimeOfDay(12, 0, 0)).julianDay == 0);

        assert(DateTime(Date(0, 12, 31), TimeOfDay(0, 0, 0)).julianDay == 1_721_424);
        assert(DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0)).julianDay == 1_721_425);

        assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).julianDay == 1_721_425);
        assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0)).julianDay == 1_721_426);

        assert(DateTime(Date(1582, 10, 15), TimeOfDay(0, 0, 0)).julianDay == 2_299_160);
        assert(DateTime(Date(1582, 10, 15), TimeOfDay(12, 0, 0)).julianDay == 2_299_161);

        assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).julianDay == 2_400_000);
        assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).julianDay == 2_400_001);

        assert(DateTime(Date(1982, 1, 4), TimeOfDay(0, 0, 0)).julianDay == 2_444_973);
        assert(DateTime(Date(1982, 1, 4), TimeOfDay(12, 0, 0)).julianDay == 2_444_974);

        assert(DateTime(Date(1996, 3, 31), TimeOfDay(0, 0, 0)).julianDay == 2_450_173);
        assert(DateTime(Date(1996, 3, 31), TimeOfDay(12, 0, 0)).julianDay == 2_450_174);

        assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).julianDay == 2_455_432);
        assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).julianDay == 2_455_433);

        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.julianDay == 2_451_366);
        assert(idt.julianDay == 2_451_366);
    }


    /++
        The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for any
        time on this date (since, the modified Julian day changes at midnight).
      +/
    @property long modJulianDay() const @safe pure nothrow @nogc
    {
        return _date.modJulianDay;
    }

    @safe unittest
    {
        assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).modJulianDay == 0);
        assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).modJulianDay == 0);

        assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).modJulianDay == 55_432);
        assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).modJulianDay == 55_432);

        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(cdt.modJulianDay == 51_365);
        assert(idt.modJulianDay == 51_365);
    }


    /++
        Converts this $(LREF DateTime) to a string with the format `YYYYMMDDTHHMMSS`.
        If `writer` is set, the resulting string will be written directly to it.

        Params:
            writer = A `char` accepting
            $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(18);
        try
            toISOString(w);
        catch (Exception e)
            assert(0, "toISOString() threw.");
        return w.data;
    }

    /// ditto
    void toISOString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        _date.toISOString(writer);
        formattedWrite!("T%02d%02d%02d")(
            writer,
            _tod._hour,
            _tod._minute,
            _tod._second
        );
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOString() ==
               "20100704T070612");

        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOString() ==
               "19981225T021500");

        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOString() ==
               "00000105T230959");

        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOString() ==
               "-00040105T000002");
    }

    @safe unittest
    {
        // Test A.D.
        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "00091204T000000");
        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "00991204T050612");
        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "09991204T134459");
        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "99990704T235959");
        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "+100001020T010101");

        // Test B.C.
        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOString() == "00001204T001204");
        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "-00091204T000000");
        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "-00991204T050612");
        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "-09991204T134459");
        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "-99990704T235959");
        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "-100001020T010101");

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.toISOString() == "19990706T123033");
        assert(idt.toISOString() == "19990706T123033");
    }


    /++
        Converts this $(LREF DateTime) to a string with the format
        `YYYY-MM-DDTHH:MM:SS`. If `writer` is set, the resulting
        string will be written directly to it.

        Params:
            writer = A `char` accepting
            $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOExtString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(20);
        try
            toISOExtString(w);
        catch (Exception e)
            assert(0, "toISOExtString() threw.");
        return w.data;
    }

    /// ditto
    void toISOExtString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        _date.toISOExtString(writer);
        formattedWrite!("T%02d:%02d:%02d")(
            writer,
            _tod._hour,
            _tod._minute,
            _tod._second
        );
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOExtString() ==
               "2010-07-04T07:06:12");

        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOExtString() ==
               "1998-12-25T02:15:00");

        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOExtString() ==
               "0000-01-05T23:09:59");

        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOExtString() ==
               "-0004-01-05T00:00:02");
    }

    @safe unittest
    {
        // Test A.D.
        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "0009-12-04T00:00:00");
        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "0099-12-04T05:06:12");
        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "0999-12-04T13:44:59");
        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "9999-07-04T23:59:59");
        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "+10000-10-20T01:01:01");

        // Test B.C.
        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOExtString() == "0000-12-04T00:12:04");
        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "-0009-12-04T00:00:00");
        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "-0099-12-04T05:06:12");
        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "-0999-12-04T13:44:59");
        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "-9999-07-04T23:59:59");
        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "-10000-10-20T01:01:01");

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.toISOExtString() == "1999-07-06T12:30:33");
        assert(idt.toISOExtString() == "1999-07-06T12:30:33");
    }

    /++
        Converts this $(LREF DateTime) to a string with the format
        `YYYY-Mon-DD HH:MM:SS`. If `writer` is set, the resulting
        string will be written directly to it.

        Params:
            writer = A `char` accepting
            $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toSimpleString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(22);
        try
            toSimpleString(w);
        catch (Exception e)
            assert(0, "toSimpleString() threw.");
        return w.data;
    }

    /// ditto
    void toSimpleString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        _date.toSimpleString(writer);
        formattedWrite!(" %02d:%02d:%02d")(
            writer,
            _tod._hour,
            _tod._minute,
            _tod._second
        );
    }

    ///
    @safe unittest
    {
        assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toSimpleString() ==
               "2010-Jul-04 07:06:12");

        assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toSimpleString() ==
               "1998-Dec-25 02:15:00");

        assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toSimpleString() ==
               "0000-Jan-05 23:09:59");

        assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toSimpleString() ==
               "-0004-Jan-05 00:00:02");
    }

    @safe unittest
    {
        // Test A.D.
        assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "0009-Dec-04 00:00:00");
        assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "0099-Dec-04 05:06:12");
        assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "0999-Dec-04 13:44:59");
        assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "9999-Jul-04 23:59:59");
        assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "+10000-Oct-20 01:01:01");

        // Test B.C.
        assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toSimpleString() == "0000-Dec-04 00:12:04");
        assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "-0009-Dec-04 00:00:00");
        assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "-0099-Dec-04 05:06:12");
        assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "-0999-Dec-04 13:44:59");
        assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "-9999-Jul-04 23:59:59");
        assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "-10000-Oct-20 01:01:01");

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        assert(cdt.toSimpleString() == "1999-Jul-06 12:30:33");
        assert(idt.toSimpleString() == "1999-Jul-06 12:30:33");
    }


    /++
        Converts this $(LREF DateTime) to a string.

        This function exists to make it easy to convert a $(LREF DateTime) to a
        string for code that does not care what the exact format is - just that
        it presents the information in a clear manner. It also makes it easy to
        simply convert a $(LREF DateTime) to a string when using functions such
        as `to!string`, `format`, or `writeln` which use toString to convert
        user-defined types. So, it is unlikely that much code will call
        toString directly.

        The format of the string is purposefully unspecified, and code that
        cares about the format of the string should use `toISOString`,
        `toISOExtString`, `toSimpleString`, or some other custom formatting
        function that explicitly generates the format that the code needs. The
        reason is that the code is then clear about what format it's using,
        making it less error-prone to maintain the code and interact with other
        software that consumes the generated strings. It's for this same reason
        that $(LREF DateTime) has no `fromString` function, whereas it does have
        `fromISOString`, `fromISOExtString`, and `fromSimpleString`.

        The format returned by toString may or may not change in the future.
      +/
    string toString() const @safe pure nothrow
    {
        return toSimpleString();
    }

    @safe unittest
    {
        auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
        assert(dt.toString());
        assert(cdt.toString());
        assert(idt.toString());
    }

    /// ditto
    void toString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        toSimpleString(writer);
    }

    /++
        Creates a $(LREF DateTime) from a string with the format YYYYMMDDTHHMMSS.
        Whitespace is stripped from the given string.

        Params:
            isoString = A string formatted in the ISO format for dates and times.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO format or if the resulting $(LREF DateTime) would not
            be valid.
      +/
    static DateTime fromISOString(S)(scope const S isoString) @safe pure
        if (isSomeString!S)
    {
        import std.algorithm.searching : countUntil;
        import std.exception : enforce;
        import std.format : format;
        import std.string : strip;
        import std.utf : byCodeUnit;

        auto str = strip(isoString);

        enforce(str.length >= 15, new DateTimeException(format("Invalid ISO String: %s", isoString)));
        auto t = str.byCodeUnit.countUntil('T');

        enforce(t != -1, new DateTimeException(format("Invalid ISO String: %s", isoString)));

        immutable date = Date.fromISOString(str[0 .. t]);
        immutable tod = TimeOfDay.fromISOString(str[t+1 .. $]);

        return DateTime(date, tod);
    }

    ///
    @safe unittest
    {
        assert(DateTime.fromISOString("20100704T070612") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));

        assert(DateTime.fromISOString("19981225T021500") ==
               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));

        assert(DateTime.fromISOString("00000105T230959") ==
               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));

        assert(DateTime.fromISOString("-00040105T000002") ==
               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));

        assert(DateTime.fromISOString(" 20100704T070612 ") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(DateTime.fromISOString(""));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000."));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0"));

        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromISOString("2010-12-22T172201"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Dec-22 17:22:01"));

        assert(DateTime.fromISOString("20101222T172201") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
        assert(DateTime.fromISOString("19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOString("-19990706T123033") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOString("+019990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOString("19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOString(" 19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOString(" 19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(DateTime.fromISOString(to!S("20121221T141516")) == DateTime(2012, 12, 21, 14, 15, 16));
        }
    }


    /++
        Creates a $(LREF DateTime) from a string with the format
        YYYY-MM-DDTHH:MM:SS. Whitespace is stripped from the given string.

        Params:
            isoExtString = A string formatted in the ISO Extended format for dates
                           and times.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO Extended format or if the resulting $(LREF DateTime)
            would not be valid.
      +/
    static DateTime fromISOExtString(S)(scope const S isoExtString) @safe pure
        if (isSomeString!(S))
    {
        import std.algorithm.searching : countUntil;
        import std.exception : enforce;
        import std.format : format;
        import std.string : strip;
        import std.utf : byCodeUnit;

        auto str = strip(isoExtString);

        enforce(str.length >= 15, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        auto t = str.byCodeUnit.countUntil('T');

        enforce(t != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        immutable date = Date.fromISOExtString(str[0 .. t]);
        immutable tod = TimeOfDay.fromISOExtString(str[t+1 .. $]);

        return DateTime(date, tod);
    }

    ///
    @safe unittest
    {
        assert(DateTime.fromISOExtString("2010-07-04T07:06:12") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));

        assert(DateTime.fromISOExtString("1998-12-25T02:15:00") ==
               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));

        assert(DateTime.fromISOExtString("0000-01-05T23:09:59") ==
               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));

        assert(DateTime.fromISOExtString("-0004-01-05T00:00:02") ==
               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));

        assert(DateTime.fromISOExtString(" 2010-07-04T07:06:12 ") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(DateTime.fromISOExtString(""));
        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704000000"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704 000000"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704t000000"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000."));
        assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000.0"));

        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07:0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromISOExtString("20101222T172201"));
        assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Dec-22 17:22:01"));

        assert(DateTime.fromISOExtString("2010-12-22T17:22:01") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
        assert(DateTime.fromISOExtString("1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOExtString("-1999-07-06T12:30:33") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOExtString("+01999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOExtString("1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(DateTime.fromISOExtString(to!S("2012-12-21T14:15:16")) == DateTime(2012, 12, 21, 14, 15, 16));
        }
    }


    /++
        Creates a $(LREF DateTime) from a string with the format
        YYYY-Mon-DD HH:MM:SS. Whitespace is stripped from the given string.

        Params:
            simpleString = A string formatted in the way that toSimpleString
                           formats dates and times.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the correct format or if the resulting $(LREF DateTime)
            would not be valid.
      +/
    static DateTime fromSimpleString(S)(scope const S simpleString) @safe pure
        if (isSomeString!(S))
    {
        import std.algorithm.searching : countUntil;
        import std.exception : enforce;
        import std.format : format;
        import std.string : strip;
        import std.utf : byCodeUnit;

        auto str = strip(simpleString);

        enforce(str.length >= 15, new DateTimeException(format("Invalid string format: %s", simpleString)));
        auto t = str.byCodeUnit.countUntil(' ');

        enforce(t != -1, new DateTimeException(format("Invalid string format: %s", simpleString)));

        immutable date = Date.fromSimpleString(str[0 .. t]);
        immutable tod = TimeOfDay.fromISOExtString(str[t+1 .. $]);

        return DateTime(date, tod);
    }

    ///
    @safe unittest
    {
        assert(DateTime.fromSimpleString("2010-Jul-04 07:06:12") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
        assert(DateTime.fromSimpleString("1998-Dec-25 02:15:00") ==
               DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)));
        assert(DateTime.fromSimpleString("0000-Jan-05 23:09:59") ==
               DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)));
        assert(DateTime.fromSimpleString("-0004-Jan-05 00:00:02") ==
               DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)));
        assert(DateTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") ==
               DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(DateTime.fromISOString(""));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000"));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000."));
        assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0"));

        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00"));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00."));
        assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0"));

        assertThrown!DateTimeException(DateTime.fromSimpleString("20101222T172201"));
        assertThrown!DateTimeException(DateTime.fromSimpleString("2010-12-22T172201"));

        assert(DateTime.fromSimpleString("2010-Dec-22 17:22:01") ==
               DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01)));
        assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33") ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromSimpleString("-1999-Jul-06 12:30:33") ==
               DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromSimpleString("+01999-Jul-06 12:30:33") ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33 ") ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33") ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
        assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33 ") ==
               DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(DateTime.fromSimpleString(to!S("2012-Dec-21 14:15:16")) == DateTime(2012, 12, 21, 14, 15, 16));
        }
    }


    /++
        Returns the $(LREF DateTime) farthest in the past which is representable
        by $(LREF DateTime).
      +/
    @property static DateTime min() @safe pure nothrow @nogc
    out(result)
    {
        assert(result._date == Date.min);
        assert(result._tod == TimeOfDay.min);
    }
    do
    {
        auto dt = DateTime.init;
        dt._date._year = short.min;
        dt._date._month = Month.jan;
        dt._date._day = 1;

        return dt;
    }

    @safe unittest
    {
        assert(DateTime.min.year < 0);
        assert(DateTime.min < DateTime.max);
    }


    /++
        Returns the $(LREF DateTime) farthest in the future which is
        representable by $(LREF DateTime).
      +/
    @property static DateTime max() @safe pure nothrow @nogc
    out(result)
    {
        assert(result._date == Date.max);
        assert(result._tod == TimeOfDay.max);
    }
    do
    {
        auto dt = DateTime.init;
        dt._date._year = short.max;
        dt._date._month = Month.dec;
        dt._date._day = 31;
        dt._tod._hour = TimeOfDay.maxHour;
        dt._tod._minute = TimeOfDay.maxMinute;
        dt._tod._second = TimeOfDay.maxSecond;

        return dt;
    }

    @safe unittest
    {
        assert(DateTime.max.year > 0);
        assert(DateTime.max > DateTime.min);
    }


private:

    /+
        Add seconds to the time of day. Negative values will subtract. If the
        number of seconds overflows (or underflows), then the seconds will wrap,
        increasing (or decreasing) the number of minutes accordingly. The
        same goes for any larger units.

        Params:
            seconds = The number of seconds to add to this $(LREF DateTime).
      +/
    ref DateTime _addSeconds(long seconds) return @safe pure nothrow @nogc
    {
        import core.time : convert;
        long hnsecs = convert!("seconds", "hnsecs")(seconds);
        hnsecs += convert!("hours", "hnsecs")(_tod._hour);
        hnsecs += convert!("minutes", "hnsecs")(_tod._minute);
        hnsecs += convert!("seconds", "hnsecs")(_tod._second);

        auto days = splitUnitsFromHNSecs!"days"(hnsecs);

        if (hnsecs < 0)
        {
            hnsecs += convert!("days", "hnsecs")(1);
            --days;
        }

        _date._addDays(days);

        immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs);
        immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
        immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs);

        _tod._hour = cast(ubyte) newHours;
        _tod._minute = cast(ubyte) newMinutes;
        _tod._second = cast(ubyte) newSeconds;

        return this;
    }

    @safe unittest
    {
        static void testDT(DateTime orig, int seconds, DateTime expected, size_t line = __LINE__)
        {
            orig._addSeconds(seconds);
            assert(orig == expected);
        }

        // Test A.D.
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 0, DateTime(1999, 7, 6, 12, 30, 33));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1, DateTime(1999, 7, 6, 12, 30, 34));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 2, DateTime(1999, 7, 6, 12, 30, 35));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3, DateTime(1999, 7, 6, 12, 30, 36));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 4, DateTime(1999, 7, 6, 12, 30, 37));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 5, DateTime(1999, 7, 6, 12, 30, 38));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 10, DateTime(1999, 7, 6, 12, 30, 43));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 15, DateTime(1999, 7, 6, 12, 30, 48));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 26, DateTime(1999, 7, 6, 12, 30, 59));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 27, DateTime(1999, 7, 6, 12, 31, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 30, DateTime(1999, 7, 6, 12, 31, 3));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 59, DateTime(1999, 7, 6, 12, 31, 32));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 60, DateTime(1999, 7, 6, 12, 31, 33));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 61, DateTime(1999, 7, 6, 12, 31, 34));

        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1766, DateTime(1999, 7, 6, 12, 59, 59));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1767, DateTime(1999, 7, 6, 13, 0, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 1768, DateTime(1999, 7, 6, 13, 0, 1));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 2007, DateTime(1999, 7, 6, 13, 4, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3599, DateTime(1999, 7, 6, 13, 30, 32));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3600, DateTime(1999, 7, 6, 13, 30, 33));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 3601, DateTime(1999, 7, 6, 13, 30, 34));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), 7200, DateTime(1999, 7, 6, 14, 30, 33));
        testDT(DateTime(1999, 7, 6, 23, 0, 0), 432_123, DateTime(1999, 7, 11, 23, 2, 3));

        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1, DateTime(1999, 7, 6, 12, 30, 32));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -2, DateTime(1999, 7, 6, 12, 30, 31));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3, DateTime(1999, 7, 6, 12, 30, 30));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -4, DateTime(1999, 7, 6, 12, 30, 29));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -5, DateTime(1999, 7, 6, 12, 30, 28));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -10, DateTime(1999, 7, 6, 12, 30, 23));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -15, DateTime(1999, 7, 6, 12, 30, 18));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -33, DateTime(1999, 7, 6, 12, 30, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -34, DateTime(1999, 7, 6, 12, 29, 59));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -35, DateTime(1999, 7, 6, 12, 29, 58));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -59, DateTime(1999, 7, 6, 12, 29, 34));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -60, DateTime(1999, 7, 6, 12, 29, 33));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -61, DateTime(1999, 7, 6, 12, 29, 32));

        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1833, DateTime(1999, 7, 6, 12, 0, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -1834, DateTime(1999, 7, 6, 11, 59, 59));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3600, DateTime(1999, 7, 6, 11, 30, 33));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -3601, DateTime(1999, 7, 6, 11, 30, 32));
        testDT(DateTime(1999, 7, 6, 12, 30, 33), -5134, DateTime(1999, 7, 6, 11, 4, 59));
        testDT(DateTime(1999, 7, 6, 23, 0, 0), -432_123, DateTime(1999, 7, 1, 22, 57, 57));

        testDT(DateTime(1999, 7, 6, 12, 30, 0), 1, DateTime(1999, 7, 6, 12, 30, 1));
        testDT(DateTime(1999, 7, 6, 12, 30, 0), 0, DateTime(1999, 7, 6, 12, 30, 0));
        testDT(DateTime(1999, 7, 6, 12, 30, 0), -1, DateTime(1999, 7, 6, 12, 29, 59));

        testDT(DateTime(1999, 7, 6, 12, 0, 0), 1, DateTime(1999, 7, 6, 12, 0, 1));
        testDT(DateTime(1999, 7, 6, 12, 0, 0), 0, DateTime(1999, 7, 6, 12, 0, 0));
        testDT(DateTime(1999, 7, 6, 12, 0, 0), -1, DateTime(1999, 7, 6, 11, 59, 59));

        testDT(DateTime(1999, 7, 6, 0, 0, 0), 1, DateTime(1999, 7, 6, 0, 0, 1));
        testDT(DateTime(1999, 7, 6, 0, 0, 0), 0, DateTime(1999, 7, 6, 0, 0, 0));
        testDT(DateTime(1999, 7, 6, 0, 0, 0), -1, DateTime(1999, 7, 5, 23, 59, 59));

        testDT(DateTime(1999, 7, 5, 23, 59, 59), 1, DateTime(1999, 7, 6, 0, 0, 0));
        testDT(DateTime(1999, 7, 5, 23, 59, 59), 0, DateTime(1999, 7, 5, 23, 59, 59));
        testDT(DateTime(1999, 7, 5, 23, 59, 59), -1, DateTime(1999, 7, 5, 23, 59, 58));

        testDT(DateTime(1998, 12, 31, 23, 59, 59), 1, DateTime(1999, 1, 1, 0, 0, 0));
        testDT(DateTime(1998, 12, 31, 23, 59, 59), 0, DateTime(1998, 12, 31, 23, 59, 59));
        testDT(DateTime(1998, 12, 31, 23, 59, 59), -1, DateTime(1998, 12, 31, 23, 59, 58));

        testDT(DateTime(1998, 1, 1, 0, 0, 0), 1, DateTime(1998, 1, 1, 0, 0, 1));
        testDT(DateTime(1998, 1, 1, 0, 0, 0), 0, DateTime(1998, 1, 1, 0, 0, 0));
        testDT(DateTime(1998, 1, 1, 0, 0, 0), -1, DateTime(1997, 12, 31, 23, 59, 59));

        // Test B.C.
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 0, DateTime(-1999, 7, 6, 12, 30, 33));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1, DateTime(-1999, 7, 6, 12, 30, 34));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2, DateTime(-1999, 7, 6, 12, 30, 35));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3, DateTime(-1999, 7, 6, 12, 30, 36));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 4, DateTime(-1999, 7, 6, 12, 30, 37));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 5, DateTime(-1999, 7, 6, 12, 30, 38));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 10, DateTime(-1999, 7, 6, 12, 30, 43));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 15, DateTime(-1999, 7, 6, 12, 30, 48));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 26, DateTime(-1999, 7, 6, 12, 30, 59));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 27, DateTime(-1999, 7, 6, 12, 31, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 30, DateTime(-1999, 7, 6, 12, 31, 3));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 59, DateTime(-1999, 7, 6, 12, 31, 32));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 60, DateTime(-1999, 7, 6, 12, 31, 33));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 61, DateTime(-1999, 7, 6, 12, 31, 34));

        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1766, DateTime(-1999, 7, 6, 12, 59, 59));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1767, DateTime(-1999, 7, 6, 13, 0, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1768, DateTime(-1999, 7, 6, 13, 0, 1));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2007, DateTime(-1999, 7, 6, 13, 4, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3599, DateTime(-1999, 7, 6, 13, 30, 32));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3600, DateTime(-1999, 7, 6, 13, 30, 33));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3601, DateTime(-1999, 7, 6, 13, 30, 34));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), 7200, DateTime(-1999, 7, 6, 14, 30, 33));
        testDT(DateTime(-1999, 7, 6, 23, 0, 0), 432_123, DateTime(-1999, 7, 11, 23, 2, 3));

        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1, DateTime(-1999, 7, 6, 12, 30, 32));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -2, DateTime(-1999, 7, 6, 12, 30, 31));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3, DateTime(-1999, 7, 6, 12, 30, 30));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -4, DateTime(-1999, 7, 6, 12, 30, 29));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5, DateTime(-1999, 7, 6, 12, 30, 28));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -10, DateTime(-1999, 7, 6, 12, 30, 23));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -15, DateTime(-1999, 7, 6, 12, 30, 18));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -33, DateTime(-1999, 7, 6, 12, 30, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -34, DateTime(-1999, 7, 6, 12, 29, 59));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -35, DateTime(-1999, 7, 6, 12, 29, 58));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -59, DateTime(-1999, 7, 6, 12, 29, 34));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -60, DateTime(-1999, 7, 6, 12, 29, 33));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -61, DateTime(-1999, 7, 6, 12, 29, 32));

        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1833, DateTime(-1999, 7, 6, 12, 0, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1834, DateTime(-1999, 7, 6, 11, 59, 59));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3600, DateTime(-1999, 7, 6, 11, 30, 33));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3601, DateTime(-1999, 7, 6, 11, 30, 32));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5134, DateTime(-1999, 7, 6, 11, 4, 59));
        testDT(DateTime(-1999, 7, 6, 12, 30, 33), -7200, DateTime(-1999, 7, 6, 10, 30, 33));
        testDT(DateTime(-1999, 7, 6, 23, 0, 0), -432_123, DateTime(-1999, 7, 1, 22, 57, 57));

        testDT(DateTime(-1999, 7, 6, 12, 30, 0), 1, DateTime(-1999, 7, 6, 12, 30, 1));
        testDT(DateTime(-1999, 7, 6, 12, 30, 0), 0, DateTime(-1999, 7, 6, 12, 30, 0));
        testDT(DateTime(-1999, 7, 6, 12, 30, 0), -1, DateTime(-1999, 7, 6, 12, 29, 59));

        testDT(DateTime(-1999, 7, 6, 12, 0, 0), 1, DateTime(-1999, 7, 6, 12, 0, 1));
        testDT(DateTime(-1999, 7, 6, 12, 0, 0), 0, DateTime(-1999, 7, 6, 12, 0, 0));
        testDT(DateTime(-1999, 7, 6, 12, 0, 0), -1, DateTime(-1999, 7, 6, 11, 59, 59));

        testDT(DateTime(-1999, 7, 6, 0, 0, 0), 1, DateTime(-1999, 7, 6, 0, 0, 1));
        testDT(DateTime(-1999, 7, 6, 0, 0, 0), 0, DateTime(-1999, 7, 6, 0, 0, 0));
        testDT(DateTime(-1999, 7, 6, 0, 0, 0), -1, DateTime(-1999, 7, 5, 23, 59, 59));

        testDT(DateTime(-1999, 7, 5, 23, 59, 59), 1, DateTime(-1999, 7, 6, 0, 0, 0));
        testDT(DateTime(-1999, 7, 5, 23, 59, 59), 0, DateTime(-1999, 7, 5, 23, 59, 59));
        testDT(DateTime(-1999, 7, 5, 23, 59, 59), -1, DateTime(-1999, 7, 5, 23, 59, 58));

        testDT(DateTime(-2000, 12, 31, 23, 59, 59), 1, DateTime(-1999, 1, 1, 0, 0, 0));
        testDT(DateTime(-2000, 12, 31, 23, 59, 59), 0, DateTime(-2000, 12, 31, 23, 59, 59));
        testDT(DateTime(-2000, 12, 31, 23, 59, 59), -1, DateTime(-2000, 12, 31, 23, 59, 58));

        testDT(DateTime(-2000, 1, 1, 0, 0, 0), 1, DateTime(-2000, 1, 1, 0, 0, 1));
        testDT(DateTime(-2000, 1, 1, 0, 0, 0), 0, DateTime(-2000, 1, 1, 0, 0, 0));
        testDT(DateTime(-2000, 1, 1, 0, 0, 0), -1, DateTime(-2001, 12, 31, 23, 59, 59));

        // Test Both
        testDT(DateTime(1, 1, 1, 0, 0, 0), -1, DateTime(0, 12, 31, 23, 59, 59));
        testDT(DateTime(0, 12, 31, 23, 59, 59), 1, DateTime(1, 1, 1, 0, 0, 0));

        testDT(DateTime(0, 1, 1, 0, 0, 0), -1, DateTime(-1, 12, 31, 23, 59, 59));
        testDT(DateTime(-1, 12, 31, 23, 59, 59), 1, DateTime(0, 1, 1, 0, 0, 0));

        testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_600L, DateTime(1, 1, 1, 13, 30, 33));
        testDT(DateTime(1, 1, 1, 13, 30, 33), -63_165_600L, DateTime(-1, 1, 1, 11, 30, 33));

        testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_617L, DateTime(1, 1, 1, 13, 30, 50));
        testDT(DateTime(1, 1, 1, 13, 30, 50), -63_165_617L, DateTime(-1, 1, 1, 11, 30, 33));

        const cdt = DateTime(1999, 7, 6, 12, 30, 33);
        immutable idt = DateTime(1999, 7, 6, 12, 30, 33);
        static assert(!__traits(compiles, cdt._addSeconds(4)));
        static assert(!__traits(compiles, idt._addSeconds(4)));
    }


    Date      _date;
    TimeOfDay _tod;
}

///
@safe pure unittest
{
    import core.time : days, seconds;

    auto dt = DateTime(2000, 6, 1, 10, 30, 0);

    assert(dt.date == Date(2000, 6, 1));
    assert(dt.timeOfDay == TimeOfDay(10, 30, 0));
    assert(dt.dayOfYear == 153);
    assert(dt.dayOfWeek == DayOfWeek.thu);

    dt += 10.days + 100.seconds;
    assert(dt == DateTime(2000, 6, 11, 10, 31, 40));

    assert(dt.toISOExtString() == "2000-06-11T10:31:40");
    assert(dt.toISOString() == "20000611T103140");
    assert(dt.toSimpleString() == "2000-Jun-11 10:31:40");

    assert(DateTime.fromISOExtString("2018-01-01T12:00:00") == DateTime(2018, 1, 1, 12, 0, 0));
    assert(DateTime.fromISOString("20180101T120000") == DateTime(2018, 1, 1, 12, 0, 0));
    assert(DateTime.fromSimpleString("2018-Jan-01 12:00:00") == DateTime(2018, 1, 1, 12, 0, 0));
}

/++
    Represents a date in the
    $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic
    Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years
    are A.D. Non-positive years are B.C.

    Year, month, and day are kept separately internally so that `Date` is
    optimized for calendar-based operations.

    `Date` uses the Proleptic Gregorian Calendar, so it assumes the Gregorian
    leap year calculations for its entire length. As per
    $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as
    year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C.
    as a positive integer with 1 B.C. being the year prior to 1 A.D.

    Year 0 is a leap year.
 +/
struct Date
{
public:

    /++
        Throws:
            $(REF DateTimeException,std,datetime,date) if the resulting
            $(LREF Date) would not be valid.

        Params:
            year  = Year of the Gregorian Calendar. Positive values are A.D.
                    Non-positive values are B.C. with year 0 being the year
                    prior to 1 A.D.
            month = Month of the year (January is 1).
            day   = Day of the month.
     +/
    this(int year, int month, int day) @safe pure
    {
        enforceValid!"months"(cast(Month) month);
        enforceValid!"days"(year, cast(Month) month, day);

        _year  = cast(short) year;
        _month = cast(Month) month;
        _day   = cast(ubyte) day;
    }

    @safe unittest
    {
        import std.exception : assertNotThrown;
        assert(Date(1, 1, 1) == Date.init);

        static void testDate(Date date, int year, int month, int day)
        {
            assert(date._year == year);
            assert(date._month == month);
            assert(date._day == day);
        }

        testDate(Date(1999, 1 , 1), 1999, Month.jan, 1);
        testDate(Date(1999, 7 , 1), 1999, Month.jul, 1);
        testDate(Date(1999, 7 , 6), 1999, Month.jul, 6);

        // Test A.D.
        assertThrown!DateTimeException(Date(1, 0, 1));
        assertThrown!DateTimeException(Date(1, 1, 0));
        assertThrown!DateTimeException(Date(1999, 13, 1));
        assertThrown!DateTimeException(Date(1999, 1, 32));
        assertThrown!DateTimeException(Date(1999, 2, 29));
        assertThrown!DateTimeException(Date(2000, 2, 30));
        assertThrown!DateTimeException(Date(1999, 3, 32));
        assertThrown!DateTimeException(Date(1999, 4, 31));
        assertThrown!DateTimeException(Date(1999, 5, 32));
        assertThrown!DateTimeException(Date(1999, 6, 31));
        assertThrown!DateTimeException(Date(1999, 7, 32));
        assertThrown!DateTimeException(Date(1999, 8, 32));
        assertThrown!DateTimeException(Date(1999, 9, 31));
        assertThrown!DateTimeException(Date(1999, 10, 32));
        assertThrown!DateTimeException(Date(1999, 11, 31));
        assertThrown!DateTimeException(Date(1999, 12, 32));

        assertNotThrown!DateTimeException(Date(1999, 1, 31));
        assertNotThrown!DateTimeException(Date(1999, 2, 28));
        assertNotThrown!DateTimeException(Date(2000, 2, 29));
        assertNotThrown!DateTimeException(Date(1999, 3, 31));
        assertNotThrown!DateTimeException(Date(1999, 4, 30));
        assertNotThrown!DateTimeException(Date(1999, 5, 31));
        assertNotThrown!DateTimeException(Date(1999, 6, 30));
        assertNotThrown!DateTimeException(Date(1999, 7, 31));
        assertNotThrown!DateTimeException(Date(1999, 8, 31));
        assertNotThrown!DateTimeException(Date(1999, 9, 30));
        assertNotThrown!DateTimeException(Date(1999, 10, 31));
        assertNotThrown!DateTimeException(Date(1999, 11, 30));
        assertNotThrown!DateTimeException(Date(1999, 12, 31));

        // Test B.C.
        assertNotThrown!DateTimeException(Date(0, 1, 1));
        assertNotThrown!DateTimeException(Date(-1, 1, 1));
        assertNotThrown!DateTimeException(Date(-1, 12, 31));
        assertNotThrown!DateTimeException(Date(-1, 2, 28));
        assertNotThrown!DateTimeException(Date(-4, 2, 29));

        assertThrown!DateTimeException(Date(-1, 2, 29));
        assertThrown!DateTimeException(Date(-2, 2, 29));
        assertThrown!DateTimeException(Date(-3, 2, 29));
    }


    /++
        Params:
            day = The Xth day of the Gregorian Calendar that the constructed
                  $(LREF Date) will be for.
     +/
    this(int day) @safe pure nothrow @nogc
    {
        if (day > 0)
        {
            int years = (day / daysIn400Years) * 400 + 1;
            day %= daysIn400Years;

            {
                immutable tempYears = day / daysIn100Years;

                if (tempYears == 4)
                {
                    years += 300;
                    day -= daysIn100Years * 3;
                }
                else
                {
                    years += tempYears * 100;
                    day %= daysIn100Years;
                }
            }

            years += (day / daysIn4Years) * 4;
            day %= daysIn4Years;

            {
                immutable tempYears = day / daysInYear;

                if (tempYears == 4)
                {
                    years += 3;
                    day -= daysInYear * 3;
                }
                else
                {
                    years += tempYears;
                    day %= daysInYear;
                }
            }

            if (day == 0)
            {
                _year = cast(short)(years - 1);
                _month = Month.dec;
                _day = 31;
            }
            else
            {
                _year = cast(short) years;

                setDayOfYear(day);
            }
        }
        else if (day <= 0 && -day < daysInLeapYear)
        {
            _year = 0;

            setDayOfYear(daysInLeapYear + day);
        }
        else
        {
            day += daysInLeapYear - 1;
            int years = (day / daysIn400Years) * 400 - 1;
            day %= daysIn400Years;

            {
                immutable tempYears = day / daysIn100Years;

                if (tempYears == -4)
                {
                    years -= 300;
                    day += daysIn100Years * 3;
                }
                else
                {
                    years += tempYears * 100;
                    day %= daysIn100Years;
                }
            }

            years += (day / daysIn4Years) * 4;
            day %= daysIn4Years;

            {
                immutable tempYears = day / daysInYear;

                if (tempYears == -4)
                {
                    years -= 3;
                    day += daysInYear * 3;
                }
                else
                {
                    years += tempYears;
                    day %= daysInYear;
                }
            }

            if (day == 0)
            {
                _year = cast(short)(years + 1);
                _month = Month.jan;
                _day = 1;
            }
            else
            {
                _year = cast(short) years;
                immutable newDoY = (yearIsLeapYear(_year) ? daysInLeapYear : daysInYear) + day + 1;

                setDayOfYear(newDoY);
            }
        }
    }

    @safe unittest
    {
        import std.range : chain;

        // Test A.D.
        foreach (gd; chain(testGregDaysBC, testGregDaysAD))
            assert(Date(gd.day) == gd.date);
    }


    /++
        Compares this $(LREF Date) with the given $(LREF Date).

        Returns:
            $(BOOKTABLE,
            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
            $(TR $(TD this == rhs) $(TD 0))
            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
            )
     +/
    int opCmp(Date rhs) const @safe pure nothrow @nogc
    {
        if (_year < rhs._year)
            return -1;
        if (_year > rhs._year)
            return 1;

        if (_month < rhs._month)
            return -1;
        if (_month > rhs._month)
            return 1;

        if (_day < rhs._day)
            return -1;
        if (_day > rhs._day)
            return 1;

        return 0;
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(1, 1, 1).opCmp(Date.init) == 0);

        assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0);
        assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0);
        assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0);

        assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0);
        assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0);

        assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0);

        assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0);
        assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0);
        assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0);
        assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0);
        assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0);
        assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0);

        assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0);
        assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0);
        assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0);
        assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0);
        assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0);
        assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0);

        // Test B.C.
        assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0);
        assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0);
        assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0);
        assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0);

        assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0);
        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0);

        assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0);

        assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0);
        assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0);
        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0);
        assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0);
        assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
        assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0);

        assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0);
        assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0);
        assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0);
        assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0);
        assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0);
        assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0);

        // Test Both
        assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0);
        assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0);

        assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0);
        assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0);

        assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0);
        assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0);

        assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0);
        assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0);

        assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0);
        assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0);

        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date.opCmp(date) == 0);
        assert(date.opCmp(cdate) == 0);
        assert(date.opCmp(idate) == 0);
        assert(cdate.opCmp(date) == 0);
        assert(cdate.opCmp(cdate) == 0);
        assert(cdate.opCmp(idate) == 0);
        assert(idate.opCmp(date) == 0);
        assert(idate.opCmp(cdate) == 0);
        assert(idate.opCmp(idate) == 0);
    }


    /++
        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
        are B.C.
     +/
    @property short year() const @safe pure nothrow @nogc
    {
        return _year;
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 7, 6).year == 1999);
        assert(Date(2010, 10, 4).year == 2010);
        assert(Date(-7, 4, 5).year == -7);
    }

    @safe unittest
    {
        assert(Date.init.year == 1);
        assert(Date(1999, 7, 6).year == 1999);
        assert(Date(-1999, 7, 6).year == -1999);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.year == 1999);
        assert(idate.year == 1999);
    }

    /++
        Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive
        are B.C.

        Params:
            year = The year to set this Date's year to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the new year is not
            a leap year and the resulting date would be on February 29th.
     +/
    @property void year(int year) @safe pure
    {
        enforceValid!"days"(year, _month, _day);
        _year = cast(short) year;
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 7, 6).year == 1999);
        assert(Date(2010, 10, 4).year == 2010);
        assert(Date(-7, 4, 5).year == -7);
    }

    @safe unittest
    {
        static void testDateInvalid(Date date, int year)
        {
            date.year = year;
        }

        static void testDate(Date date, int year, Date expected)
        {
            date.year = year;
            assert(date == expected);
        }

        assertThrown!DateTimeException(testDateInvalid(Date(4, 2, 29), 1));

        testDate(Date(1, 1, 1), 1999, Date(1999, 1, 1));
        testDate(Date(1, 1, 1), 0, Date(0, 1, 1));
        testDate(Date(1, 1, 1), -1999, Date(-1999, 1, 1));

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.year = 1999));
        static assert(!__traits(compiles, idate.year = 1999));
    }


    /++
        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.

        Throws:
            $(REF DateTimeException,std,datetime,date) if `isAD` is true.
     +/
    @property ushort yearBC() const @safe pure
    {
        import std.format : format;

        if (isAD)
            throw new DateTimeException(format("Year %s is A.D.", _year));
        return cast(ushort)((_year * -1) + 1);
    }

    ///
    @safe unittest
    {
        assert(Date(0, 1, 1).yearBC == 1);
        assert(Date(-1, 1, 1).yearBC == 2);
        assert(Date(-100, 1, 1).yearBC == 101);
    }

    @safe unittest
    {
        assertThrown!DateTimeException((Date date){date.yearBC;}(Date(1, 1, 1)));

        auto date = Date(0, 7, 6);
        const cdate = Date(0, 7, 6);
        immutable idate = Date(0, 7, 6);
        assert(date.yearBC == 1);
        assert(cdate.yearBC == 1);
        assert(idate.yearBC == 1);
    }


    /++
        Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C.

        Params:
            year = The year B.C. to set this $(LREF Date)'s year to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if a non-positive value
            is given.
     +/
    @property void yearBC(int year) @safe pure
    {
        if (year <= 0)
            throw new DateTimeException("The given year is not a year B.C.");
        _year = cast(short)((year - 1) * -1);
    }

    ///
    @safe unittest
    {
        auto date = Date(2010, 1, 1);
        date.yearBC = 1;
        assert(date == Date(0, 1, 1));

        date.yearBC = 10;
        assert(date == Date(-9, 1, 1));
    }

    @safe unittest
    {
        assertThrown!DateTimeException((Date date){date.yearBC = -1;}(Date(1, 1, 1)));

        auto date = Date(0, 7, 6);
        const cdate = Date(0, 7, 6);
        immutable idate = Date(0, 7, 6);
        date.yearBC = 7;
        assert(date.yearBC == 7);
        static assert(!__traits(compiles, cdate.yearBC = 7));
        static assert(!__traits(compiles, idate.yearBC = 7));
    }


    /++
        Month of a Gregorian Year.
     +/
    @property Month month() const @safe pure nothrow @nogc
    {
        return _month;
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 7, 6).month == 7);
        assert(Date(2010, 10, 4).month == 10);
        assert(Date(-7, 4, 5).month == 4);
    }

    @safe unittest
    {
        assert(Date.init.month == 1);
        assert(Date(1999, 7, 6).month == 7);
        assert(Date(-1999, 7, 6).month == 7);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.month == 7);
        assert(idate.month == 7);
    }

    /++
        Month of a Gregorian Year.

        Params:
            month = The month to set this $(LREF Date)'s month to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given month is
            not a valid month or if the current day would not be valid in the
            given month.
     +/
    @property void month(Month month) @safe pure
    {
        enforceValid!"months"(month);
        enforceValid!"days"(_year, month, _day);
        _month = cast(Month) month;
    }

    @safe unittest
    {
        static void testDate(Date date, Month month, Date expected = Date.init)
        {
            date.month = month;
            assert(expected != Date.init);
            assert(date == expected);
        }

        assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month) 0));
        assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month) 13));
        assertThrown!DateTimeException(testDate(Date(1, 1, 29), cast(Month) 2));
        assertThrown!DateTimeException(testDate(Date(0, 1, 30), cast(Month) 2));

        testDate(Date(1, 1, 1), cast(Month) 7, Date(1, 7, 1));
        testDate(Date(-1, 1, 1), cast(Month) 7, Date(-1, 7, 1));

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.month = 7));
        static assert(!__traits(compiles, idate.month = 7));
    }


    /++
        Day of a Gregorian Month.
     +/
    @property ubyte day() const @safe pure nothrow @nogc
    {
        return _day;
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 7, 6).day == 6);
        assert(Date(2010, 10, 4).day == 4);
        assert(Date(-7, 4, 5).day == 5);
    }

    @safe unittest
    {
        import std.format : format;
        import std.range : chain;

        static void test(Date date, int expected)
        {
            assert(date.day == expected, format("Value given: %s", date));
        }

        foreach (year; chain(testYearsBC, testYearsAD))
        {
            foreach (md; testMonthDays)
                test(Date(year, md.month, md.day), md.day);
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.day == 6);
        assert(idate.day == 6);
    }

    /++
        Day of a Gregorian Month.

        Params:
            day = The day of the month to set this $(LREF Date)'s day to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given day is not
            a valid day of the current month.
     +/
    @property void day(int day) @safe pure
    {
        enforceValid!"days"(_year, _month, day);
        _day = cast(ubyte) day;
    }

    @safe unittest
    {
        import std.exception : assertNotThrown;

        static void testDate(Date date, int day)
        {
            date.day = day;
        }

        // Test A.D.
        assertThrown!DateTimeException(testDate(Date(1, 1, 1), 0));
        assertThrown!DateTimeException(testDate(Date(1, 1, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 2, 1), 29));
        assertThrown!DateTimeException(testDate(Date(4, 2, 1), 30));
        assertThrown!DateTimeException(testDate(Date(1, 3, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 4, 1), 31));
        assertThrown!DateTimeException(testDate(Date(1, 5, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 6, 1), 31));
        assertThrown!DateTimeException(testDate(Date(1, 7, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 8, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 9, 1), 31));
        assertThrown!DateTimeException(testDate(Date(1, 10, 1), 32));
        assertThrown!DateTimeException(testDate(Date(1, 11, 1), 31));
        assertThrown!DateTimeException(testDate(Date(1, 12, 1), 32));

        assertNotThrown!DateTimeException(testDate(Date(1, 1, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 2, 1), 28));
        assertNotThrown!DateTimeException(testDate(Date(4, 2, 1), 29));
        assertNotThrown!DateTimeException(testDate(Date(1, 3, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 4, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(1, 5, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 6, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(1, 7, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 8, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 9, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(1, 10, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(1, 11, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(1, 12, 1), 31));

        {
            auto date = Date(1, 1, 1);
            date.day = 6;
            assert(date == Date(1, 1, 6));
        }

        // Test B.C.
        assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 0));
        assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 2, 1), 29));
        assertThrown!DateTimeException(testDate(Date(0, 2, 1), 30));
        assertThrown!DateTimeException(testDate(Date(-1, 3, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 4, 1), 31));
        assertThrown!DateTimeException(testDate(Date(-1, 5, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 6, 1), 31));
        assertThrown!DateTimeException(testDate(Date(-1, 7, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 8, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 9, 1), 31));
        assertThrown!DateTimeException(testDate(Date(-1, 10, 1), 32));
        assertThrown!DateTimeException(testDate(Date(-1, 11, 1), 31));
        assertThrown!DateTimeException(testDate(Date(-1, 12, 1), 32));

        assertNotThrown!DateTimeException(testDate(Date(-1, 1, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 2, 1), 28));
        assertNotThrown!DateTimeException(testDate(Date(0, 2, 1), 29));
        assertNotThrown!DateTimeException(testDate(Date(-1, 3, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 4, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(-1, 5, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 6, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(-1, 7, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 8, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 9, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(-1, 10, 1), 31));
        assertNotThrown!DateTimeException(testDate(Date(-1, 11, 1), 30));
        assertNotThrown!DateTimeException(testDate(Date(-1, 12, 1), 31));

        {
            auto date = Date(-1, 1, 1);
            date.day = 6;
            assert(date == Date(-1, 1, 6));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.day = 6));
        static assert(!__traits(compiles, idate.day = 6));
    }


    /++
        Adds the given number of years or months to this $(LREF Date). A
        negative number will subtract.

        Note that if day overflow is allowed, and the date with the adjusted
        year/month overflows the number of days in the new month, then the month
        will be incremented by one, and the day set to the number of days
        overflowed. (e.g. if the day were 31 and the new month were June, then
        the month would be incremented to July, and the new day would be 1). If
        day overflow is not allowed, then the day will be set to the last valid
        day in the month (e.g. June 31st would become June 30th).

        Params:
            units         = The type of units to add ("years" or "months").
            value         = The number of months or years to add to this
                            $(LREF Date).
            allowOverflow = Whether the day should be allowed to overflow,
                            causing the month to increment.
      +/
    @safe pure nothrow @nogc
    ref Date add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
        if (units == "years")
    {
        _year += value;

        if (_month == Month.feb && _day == 29 && !yearIsLeapYear(_year))
        {
            if (allowOverflow == AllowDayOverflow.yes)
            {
                _month = Month.mar;
                _day = 1;
            }
            else
                _day = 28;
        }

        return this;
    }

    ///
    @safe unittest
    {
        auto d1 = Date(2010, 1, 1);
        d1.add!"months"(11);
        assert(d1 == Date(2010, 12, 1));

        auto d2 = Date(2010, 1, 1);
        d2.add!"months"(-11);
        assert(d2 == Date(2009, 2, 1));

        auto d3 = Date(2000, 2, 29);
        d3.add!"years"(1);
        assert(d3 == Date(2001, 3, 1));

        auto d4 = Date(2000, 2, 29);
        d4.add!"years"(1, AllowDayOverflow.no);
        assert(d4 == Date(2001, 2, 28));
    }

    // Test add!"years"() with AllowDayOverflow.yes
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.add!"years"(7);
            assert(date == Date(2006, 7, 6));
            date.add!"years"(-9);
            assert(date == Date(1997, 7, 6));
        }

        {
            auto date = Date(1999, 2, 28);
            date.add!"years"(1);
            assert(date == Date(2000, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.add!"years"(-1);
            assert(date == Date(1999, 3, 1));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.add!"years"(-7);
            assert(date == Date(-2006, 7, 6));
            date.add!"years"(9);
            assert(date == Date(-1997, 7, 6));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.add!"years"(-1);
            assert(date == Date(-2000, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.add!"years"(1);
            assert(date == Date(-1999, 3, 1));
        }

        // Test Both
        {
            auto date = Date(4, 7, 6);
            date.add!"years"(-5);
            assert(date == Date(-1, 7, 6));
            date.add!"years"(5);
            assert(date == Date(4, 7, 6));
        }

        {
            auto date = Date(-4, 7, 6);
            date.add!"years"(5);
            assert(date == Date(1, 7, 6));
            date.add!"years"(-5);
            assert(date == Date(-4, 7, 6));
        }

        {
            auto date = Date(4, 7, 6);
            date.add!"years"(-8);
            assert(date == Date(-4, 7, 6));
            date.add!"years"(8);
            assert(date == Date(4, 7, 6));
        }

        {
            auto date = Date(-4, 7, 6);
            date.add!"years"(8);
            assert(date == Date(4, 7, 6));
            date.add!"years"(-8);
            assert(date == Date(-4, 7, 6));
        }

        {
            auto date = Date(-4, 2, 29);
            date.add!"years"(5);
            assert(date == Date(1, 3, 1));
        }

        {
            auto date = Date(4, 2, 29);
            date.add!"years"(-5);
            assert(date == Date(-1, 3, 1));
        }

        {
            auto date = Date(4, 2, 29);
            date.add!"years"(-5).add!"years"(7);
            assert(date == Date(6, 3, 1));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.add!"years"(7)));
        static assert(!__traits(compiles, idate.add!"years"(7)));
    }

    // Test add!"years"() with AllowDayOverflow.no
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.add!"years"(7, AllowDayOverflow.no);
            assert(date == Date(2006, 7, 6));
            date.add!"years"(-9, AllowDayOverflow.no);
            assert(date == Date(1997, 7, 6));
        }

        {
            auto date = Date(1999, 2, 28);
            date.add!"years"(1, AllowDayOverflow.no);
            assert(date == Date(2000, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.add!"years"(-1, AllowDayOverflow.no);
            assert(date == Date(1999, 2, 28));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.add!"years"(-7, AllowDayOverflow.no);
            assert(date == Date(-2006, 7, 6));
            date.add!"years"(9, AllowDayOverflow.no);
            assert(date == Date(-1997, 7, 6));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.add!"years"(-1, AllowDayOverflow.no);
            assert(date == Date(-2000, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.add!"years"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 2, 28));
        }

        // Test Both
        {
            auto date = Date(4, 7, 6);
            date.add!"years"(-5, AllowDayOverflow.no);
            assert(date == Date(-1, 7, 6));
            date.add!"years"(5, AllowDayOverflow.no);
            assert(date == Date(4, 7, 6));
        }

        {
            auto date = Date(-4, 7, 6);
            date.add!"years"(5, AllowDayOverflow.no);
            assert(date == Date(1, 7, 6));
            date.add!"years"(-5, AllowDayOverflow.no);
            assert(date == Date(-4, 7, 6));
        }

        {
            auto date = Date(4, 7, 6);
            date.add!"years"(-8, AllowDayOverflow.no);
            assert(date == Date(-4, 7, 6));
            date.add!"years"(8, AllowDayOverflow.no);
            assert(date == Date(4, 7, 6));
        }

        {
            auto date = Date(-4, 7, 6);
            date.add!"years"(8, AllowDayOverflow.no);
            assert(date == Date(4, 7, 6));
            date.add!"years"(-8, AllowDayOverflow.no);
            assert(date == Date(-4, 7, 6));
        }

        {
            auto date = Date(-4, 2, 29);
            date.add!"years"(5, AllowDayOverflow.no);
            assert(date == Date(1, 2, 28));
        }

        {
            auto date = Date(4, 2, 29);
            date.add!"years"(-5, AllowDayOverflow.no);
            assert(date == Date(-1, 2, 28));
        }

        {
            auto date = Date(4, 2, 29);
            date.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no);
            assert(date == Date(6, 2, 28));
        }
    }


    // Shares documentation with "years" version.
    @safe pure nothrow @nogc
    ref Date add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
        if (units == "months")
    {
        auto years = months / 12;
        months %= 12;
        auto newMonth = _month + months;

        if (months < 0)
        {
            if (newMonth < 1)
            {
                newMonth += 12;
                --years;
            }
        }
        else if (newMonth > 12)
        {
            newMonth -= 12;
            ++years;
        }

        _year += years;
        _month = cast(Month) newMonth;

        immutable currMaxDay = maxDay(_year, _month);
        immutable overflow = _day - currMaxDay;

        if (overflow > 0)
        {
            if (allowOverflow == AllowDayOverflow.yes)
            {
                ++_month;
                _day = cast(ubyte) overflow;
            }
            else
                _day = cast(ubyte) currMaxDay;
        }

        return this;
    }

    // Test add!"months"() with AllowDayOverflow.yes
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(3);
            assert(date == Date(1999, 10, 6));
            date.add!"months"(-4);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(6);
            assert(date == Date(2000, 1, 6));
            date.add!"months"(-6);
            assert(date == Date(1999, 7, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(27);
            assert(date == Date(2001, 10, 6));
            date.add!"months"(-28);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 5, 31);
            date.add!"months"(1);
            assert(date == Date(1999, 7, 1));
        }

        {
            auto date = Date(1999, 5, 31);
            date.add!"months"(-1);
            assert(date == Date(1999, 5, 1));
        }

        {
            auto date = Date(1999, 2, 28);
            date.add!"months"(12);
            assert(date == Date(2000, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.add!"months"(12);
            assert(date == Date(2001, 3, 1));
        }

        {
            auto date = Date(1999, 7, 31);
            date.add!"months"(1);
            assert(date == Date(1999, 8, 31));
            date.add!"months"(1);
            assert(date == Date(1999, 10, 1));
        }

        {
            auto date = Date(1998, 8, 31);
            date.add!"months"(13);
            assert(date == Date(1999, 10, 1));
            date.add!"months"(-13);
            assert(date == Date(1998, 9, 1));
        }

        {
            auto date = Date(1997, 12, 31);
            date.add!"months"(13);
            assert(date == Date(1999, 1, 31));
            date.add!"months"(-13);
            assert(date == Date(1997, 12, 31));
        }

        {
            auto date = Date(1997, 12, 31);
            date.add!"months"(14);
            assert(date == Date(1999, 3, 3));
            date.add!"months"(-14);
            assert(date == Date(1998, 1, 3));
        }

        {
            auto date = Date(1998, 12, 31);
            date.add!"months"(14);
            assert(date == Date(2000, 3, 2));
            date.add!"months"(-14);
            assert(date == Date(1999, 1, 2));
        }

        {
            auto date = Date(1999, 12, 31);
            date.add!"months"(14);
            assert(date == Date(2001, 3, 3));
            date.add!"months"(-14);
            assert(date == Date(2000, 1, 3));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(3);
            assert(date == Date(-1999, 10, 6));
            date.add!"months"(-4);
            assert(date == Date(-1999, 6, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(6);
            assert(date == Date(-1998, 1, 6));
            date.add!"months"(-6);
            assert(date == Date(-1999, 7, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(-27);
            assert(date == Date(-2001, 4, 6));
            date.add!"months"(28);
            assert(date == Date(-1999, 8, 6));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.add!"months"(1);
            assert(date == Date(-1999, 7, 1));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.add!"months"(-1);
            assert(date == Date(-1999, 5, 1));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.add!"months"(-12);
            assert(date == Date(-2000, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.add!"months"(-12);
            assert(date == Date(-2001, 3, 1));
        }

        {
            auto date = Date(-1999, 7, 31);
            date.add!"months"(1);
            assert(date == Date(-1999, 8, 31));
            date.add!"months"(1);
            assert(date == Date(-1999, 10, 1));
        }

        {
            auto date = Date(-1998, 8, 31);
            date.add!"months"(13);
            assert(date == Date(-1997, 10, 1));
            date.add!"months"(-13);
            assert(date == Date(-1998, 9, 1));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.add!"months"(13);
            assert(date == Date(-1995, 1, 31));
            date.add!"months"(-13);
            assert(date == Date(-1997, 12, 31));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.add!"months"(14);
            assert(date == Date(-1995, 3, 3));
            date.add!"months"(-14);
            assert(date == Date(-1996, 1, 3));
        }

        {
            auto date = Date(-2002, 12, 31);
            date.add!"months"(14);
            assert(date == Date(-2000, 3, 2));
            date.add!"months"(-14);
            assert(date == Date(-2001, 1, 2));
        }

        {
            auto date = Date(-2001, 12, 31);
            date.add!"months"(14);
            assert(date == Date(-1999, 3, 3));
            date.add!"months"(-14);
            assert(date == Date(-2000, 1, 3));
        }

        // Test Both
        {
            auto date = Date(1, 1, 1);
            date.add!"months"(-1);
            assert(date == Date(0, 12, 1));
            date.add!"months"(1);
            assert(date == Date(1, 1, 1));
        }

        {
            auto date = Date(4, 1, 1);
            date.add!"months"(-48);
            assert(date == Date(0, 1, 1));
            date.add!"months"(48);
            assert(date == Date(4, 1, 1));
        }

        {
            auto date = Date(4, 3, 31);
            date.add!"months"(-49);
            assert(date == Date(0, 3, 2));
            date.add!"months"(49);
            assert(date == Date(4, 4, 2));
        }

        {
            auto date = Date(4, 3, 31);
            date.add!"months"(-85);
            assert(date == Date(-3, 3, 3));
            date.add!"months"(85);
            assert(date == Date(4, 4, 3));
        }

        {
            auto date = Date(-3, 3, 31);
            date.add!"months"(85).add!"months"(-83);
            assert(date == Date(-3, 6, 1));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.add!"months"(3)));
        static assert(!__traits(compiles, idate.add!"months"(3)));
    }

    // Test add!"months"() with AllowDayOverflow.no
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(3, AllowDayOverflow.no);
            assert(date == Date(1999, 10, 6));
            date.add!"months"(-4, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(6, AllowDayOverflow.no);
            assert(date == Date(2000, 1, 6));
            date.add!"months"(-6, AllowDayOverflow.no);
            assert(date == Date(1999, 7, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.add!"months"(27, AllowDayOverflow.no);
            assert(date == Date(2001, 10, 6));
            date.add!"months"(-28, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 5, 31);
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 30));
        }

        {
            auto date = Date(1999, 5, 31);
            date.add!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(1999, 4, 30));
        }

        {
            auto date = Date(1999, 2, 28);
            date.add!"months"(12, AllowDayOverflow.no);
            assert(date == Date(2000, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.add!"months"(12, AllowDayOverflow.no);
            assert(date == Date(2001, 2, 28));
        }

        {
            auto date = Date(1999, 7, 31);
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 8, 31));
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 9, 30));
        }

        {
            auto date = Date(1998, 8, 31);
            date.add!"months"(13, AllowDayOverflow.no);
            assert(date == Date(1999, 9, 30));
            date.add!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(1998, 8, 30));
        }

        {
            auto date = Date(1997, 12, 31);
            date.add!"months"(13, AllowDayOverflow.no);
            assert(date == Date(1999, 1, 31));
            date.add!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(1997, 12, 31));
        }

        {
            auto date = Date(1997, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(1999, 2, 28));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1997, 12, 28));
        }

        {
            auto date = Date(1998, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(2000, 2, 29));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1998, 12, 29));
        }

        {
            auto date = Date(1999, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(2001, 2, 28));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1999, 12, 28));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(3, AllowDayOverflow.no);
            assert(date == Date(-1999, 10, 6));
            date.add!"months"(-4, AllowDayOverflow.no);
            assert(date == Date(-1999, 6, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(6, AllowDayOverflow.no);
            assert(date == Date(-1998, 1, 6));
            date.add!"months"(-6, AllowDayOverflow.no);
            assert(date == Date(-1999, 7, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.add!"months"(-27, AllowDayOverflow.no);
            assert(date == Date(-2001, 4, 6));
            date.add!"months"(28, AllowDayOverflow.no);
            assert(date == Date(-1999, 8, 6));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 6, 30));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.add!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(-1999, 4, 30));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.add!"months"(-12, AllowDayOverflow.no);
            assert(date == Date(-2000, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.add!"months"(-12, AllowDayOverflow.no);
            assert(date == Date(-2001, 2, 28));
        }

        {
            auto date = Date(-1999, 7, 31);
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 8, 31));
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 9, 30));
        }

        {
            auto date = Date(-1998, 8, 31);
            date.add!"months"(13, AllowDayOverflow.no);
            assert(date == Date(-1997, 9, 30));
            date.add!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(-1998, 8, 30));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.add!"months"(13, AllowDayOverflow.no);
            assert(date == Date(-1995, 1, 31));
            date.add!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(-1997, 12, 31));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-1995, 2, 28));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-1997, 12, 28));
        }

        {
            auto date = Date(-2002, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-2000, 2, 29));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-2002, 12, 29));
        }

        {
            auto date = Date(-2001, 12, 31);
            date.add!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-1999, 2, 28));
            date.add!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-2001, 12, 28));
        }

        // Test Both
        {
            auto date = Date(1, 1, 1);
            date.add!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(0, 12, 1));
            date.add!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1, 1, 1));
        }

        {
            auto date = Date(4, 1, 1);
            date.add!"months"(-48, AllowDayOverflow.no);
            assert(date == Date(0, 1, 1));
            date.add!"months"(48, AllowDayOverflow.no);
            assert(date == Date(4, 1, 1));
        }

        {
            auto date = Date(4, 3, 31);
            date.add!"months"(-49, AllowDayOverflow.no);
            assert(date == Date(0, 2, 29));
            date.add!"months"(49, AllowDayOverflow.no);
            assert(date == Date(4, 3, 29));
        }

        {
            auto date = Date(4, 3, 31);
            date.add!"months"(-85, AllowDayOverflow.no);
            assert(date == Date(-3, 2, 28));
            date.add!"months"(85, AllowDayOverflow.no);
            assert(date == Date(4, 3, 28));
        }

        {
            auto date = Date(-3, 3, 31);
            date.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no);
            assert(date == Date(-3, 5, 30));
        }
    }


    /++
        Adds the given number of years or months to this $(LREF Date). A negative
        number will subtract.

        The difference between rolling and adding is that rolling does not
        affect larger units. Rolling a $(LREF Date) 12 months gets
        the exact same $(LREF Date). However, the days can still be affected due
        to the differing number of days in each month.

        Because there are no units larger than years, there is no difference
        between adding and rolling years.

        Params:
            units         = The type of units to add ("years" or "months").
            value         = The number of months or years to add to this
                            $(LREF Date).
            allowOverflow = Whether the day should be allowed to overflow,
                            causing the month to increment.
      +/
    @safe pure nothrow @nogc
    ref Date roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
        if (units == "years")
    {
        return add!"years"(value, allowOverflow);
    }

    ///
    @safe unittest
    {
        auto d1 = Date(2010, 1, 1);
        d1.roll!"months"(1);
        assert(d1 == Date(2010, 2, 1));

        auto d2 = Date(2010, 1, 1);
        d2.roll!"months"(-1);
        assert(d2 == Date(2010, 12, 1));

        auto d3 = Date(1999, 1, 29);
        d3.roll!"months"(1);
        assert(d3 == Date(1999, 3, 1));

        auto d4 = Date(1999, 1, 29);
        d4.roll!"months"(1, AllowDayOverflow.no);
        assert(d4 == Date(1999, 2, 28));

        auto d5 = Date(2000, 2, 29);
        d5.roll!"years"(1);
        assert(d5 == Date(2001, 3, 1));

        auto d6 = Date(2000, 2, 29);
        d6.roll!"years"(1, AllowDayOverflow.no);
        assert(d6 == Date(2001, 2, 28));
    }

    @safe unittest
    {
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.roll!"years"(3)));
        static assert(!__traits(compiles, idate.rolYears(3)));
    }


    // Shares documentation with "years" version.
    @safe pure nothrow @nogc
    ref Date roll(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
        if (units == "months")
    {
        months %= 12;
        auto newMonth = _month + months;

        if (months < 0)
        {
            if (newMonth < 1)
                newMonth += 12;
        }
        else
        {
            if (newMonth > 12)
                newMonth -= 12;
        }

        _month = cast(Month) newMonth;

        immutable currMaxDay = maxDay(_year, _month);
        immutable overflow = _day - currMaxDay;

        if (overflow > 0)
        {
            if (allowOverflow == AllowDayOverflow.yes)
            {
                ++_month;
                _day = cast(ubyte) overflow;
            }
            else
                _day = cast(ubyte) currMaxDay;
        }

        return this;
    }

    // Test roll!"months"() with AllowDayOverflow.yes
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(3);
            assert(date == Date(1999, 10, 6));
            date.roll!"months"(-4);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(6);
            assert(date == Date(1999, 1, 6));
            date.roll!"months"(-6);
            assert(date == Date(1999, 7, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(27);
            assert(date == Date(1999, 10, 6));
            date.roll!"months"(-28);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 5, 31);
            date.roll!"months"(1);
            assert(date == Date(1999, 7, 1));
        }

        {
            auto date = Date(1999, 5, 31);
            date.roll!"months"(-1);
            assert(date == Date(1999, 5, 1));
        }

        {
            auto date = Date(1999, 2, 28);
            date.roll!"months"(12);
            assert(date == Date(1999, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.roll!"months"(12);
            assert(date == Date(2000, 2, 29));
        }

        {
            auto date = Date(1999, 7, 31);
            date.roll!"months"(1);
            assert(date == Date(1999, 8, 31));
            date.roll!"months"(1);
            assert(date == Date(1999, 10, 1));
        }

        {
            auto date = Date(1998, 8, 31);
            date.roll!"months"(13);
            assert(date == Date(1998, 10, 1));
            date.roll!"months"(-13);
            assert(date == Date(1998, 9, 1));
        }

        {
            auto date = Date(1997, 12, 31);
            date.roll!"months"(13);
            assert(date == Date(1997, 1, 31));
            date.roll!"months"(-13);
            assert(date == Date(1997, 12, 31));
        }

        {
            auto date = Date(1997, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(1997, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(1997, 1, 3));
        }

        {
            auto date = Date(1998, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(1998, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(1998, 1, 3));
        }

        {
            auto date = Date(1999, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(1999, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(1999, 1, 3));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(3);
            assert(date == Date(-1999, 10, 6));
            date.roll!"months"(-4);
            assert(date == Date(-1999, 6, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(6);
            assert(date == Date(-1999, 1, 6));
            date.roll!"months"(-6);
            assert(date == Date(-1999, 7, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(-27);
            assert(date == Date(-1999, 4, 6));
            date.roll!"months"(28);
            assert(date == Date(-1999, 8, 6));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.roll!"months"(1);
            assert(date == Date(-1999, 7, 1));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.roll!"months"(-1);
            assert(date == Date(-1999, 5, 1));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.roll!"months"(-12);
            assert(date == Date(-1999, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.roll!"months"(-12);
            assert(date == Date(-2000, 2, 29));
        }

        {
            auto date = Date(-1999, 7, 31);
            date.roll!"months"(1);
            assert(date == Date(-1999, 8, 31));
            date.roll!"months"(1);
            assert(date == Date(-1999, 10, 1));
        }

        {
            auto date = Date(-1998, 8, 31);
            date.roll!"months"(13);
            assert(date == Date(-1998, 10, 1));
            date.roll!"months"(-13);
            assert(date == Date(-1998, 9, 1));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.roll!"months"(13);
            assert(date == Date(-1997, 1, 31));
            date.roll!"months"(-13);
            assert(date == Date(-1997, 12, 31));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(-1997, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(-1997, 1, 3));
        }

        {
            auto date = Date(-2002, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(-2002, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(-2002, 1, 3));
        }

        {
            auto date = Date(-2001, 12, 31);
            date.roll!"months"(14);
            assert(date == Date(-2001, 3, 3));
            date.roll!"months"(-14);
            assert(date == Date(-2001, 1, 3));
        }

        // Test Both
        {
            auto date = Date(1, 1, 1);
            date.roll!"months"(-1);
            assert(date == Date(1, 12, 1));
            date.roll!"months"(1);
            assert(date == Date(1, 1, 1));
        }

        {
            auto date = Date(4, 1, 1);
            date.roll!"months"(-48);
            assert(date == Date(4, 1, 1));
            date.roll!"months"(48);
            assert(date == Date(4, 1, 1));
        }

        {
            auto date = Date(4, 3, 31);
            date.roll!"months"(-49);
            assert(date == Date(4, 3, 2));
            date.roll!"months"(49);
            assert(date == Date(4, 4, 2));
        }

        {
            auto date = Date(4, 3, 31);
            date.roll!"months"(-85);
            assert(date == Date(4, 3, 2));
            date.roll!"months"(85);
            assert(date == Date(4, 4, 2));
        }

        {
            auto date = Date(-1, 1, 1);
            date.roll!"months"(-1);
            assert(date == Date(-1, 12, 1));
            date.roll!"months"(1);
            assert(date == Date(-1, 1, 1));
        }

        {
            auto date = Date(-4, 1, 1);
            date.roll!"months"(-48);
            assert(date == Date(-4, 1, 1));
            date.roll!"months"(48);
            assert(date == Date(-4, 1, 1));
        }

        {
            auto date = Date(-4, 3, 31);
            date.roll!"months"(-49);
            assert(date == Date(-4, 3, 2));
            date.roll!"months"(49);
            assert(date == Date(-4, 4, 2));
        }

        {
            auto date = Date(-4, 3, 31);
            date.roll!"months"(-85);
            assert(date == Date(-4, 3, 2));
            date.roll!"months"(85);
            assert(date == Date(-4, 4, 2));
        }

        {
            auto date = Date(-3, 3, 31);
            date.roll!"months"(85).roll!"months"(-83);
            assert(date == Date(-3, 6, 1));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.roll!"months"(3)));
        static assert(!__traits(compiles, idate.roll!"months"(3)));
    }

    // Test roll!"months"() with AllowDayOverflow.no
    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(3, AllowDayOverflow.no);
            assert(date == Date(1999, 10, 6));
            date.roll!"months"(-4, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(6, AllowDayOverflow.no);
            assert(date == Date(1999, 1, 6));
            date.roll!"months"(-6, AllowDayOverflow.no);
            assert(date == Date(1999, 7, 6));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"months"(27, AllowDayOverflow.no);
            assert(date == Date(1999, 10, 6));
            date.roll!"months"(-28, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 6));
        }

        {
            auto date = Date(1999, 5, 31);
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 6, 30));
        }

        {
            auto date = Date(1999, 5, 31);
            date.roll!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(1999, 4, 30));
        }

        {
            auto date = Date(1999, 2, 28);
            date.roll!"months"(12, AllowDayOverflow.no);
            assert(date == Date(1999, 2, 28));
        }

        {
            auto date = Date(2000, 2, 29);
            date.roll!"months"(12, AllowDayOverflow.no);
            assert(date == Date(2000, 2, 29));
        }

        {
            auto date = Date(1999, 7, 31);
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 8, 31));
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1999, 9, 30));
        }

        {
            auto date = Date(1998, 8, 31);
            date.roll!"months"(13, AllowDayOverflow.no);
            assert(date == Date(1998, 9, 30));
            date.roll!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(1998, 8, 30));
        }

        {
            auto date = Date(1997, 12, 31);
            date.roll!"months"(13, AllowDayOverflow.no);
            assert(date == Date(1997, 1, 31));
            date.roll!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(1997, 12, 31));
        }

        {
            auto date = Date(1997, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(1997, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1997, 12, 28));
        }

        {
            auto date = Date(1998, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(1998, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1998, 12, 28));
        }

        {
            auto date = Date(1999, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(1999, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(1999, 12, 28));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(3, AllowDayOverflow.no);
            assert(date == Date(-1999, 10, 6));
            date.roll!"months"(-4, AllowDayOverflow.no);
            assert(date == Date(-1999, 6, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(6, AllowDayOverflow.no);
            assert(date == Date(-1999, 1, 6));
            date.roll!"months"(-6, AllowDayOverflow.no);
            assert(date == Date(-1999, 7, 6));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"months"(-27, AllowDayOverflow.no);
            assert(date == Date(-1999, 4, 6));
            date.roll!"months"(28, AllowDayOverflow.no);
            assert(date == Date(-1999, 8, 6));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 6, 30));
        }

        {
            auto date = Date(-1999, 5, 31);
            date.roll!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(-1999, 4, 30));
        }

        {
            auto date = Date(-1999, 2, 28);
            date.roll!"months"(-12, AllowDayOverflow.no);
            assert(date == Date(-1999, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 29);
            date.roll!"months"(-12, AllowDayOverflow.no);
            assert(date == Date(-2000, 2, 29));
        }

        {
            auto date = Date(-1999, 7, 31);
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 8, 31));
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1999, 9, 30));
        }

        {
            auto date = Date(-1998, 8, 31);
            date.roll!"months"(13, AllowDayOverflow.no);
            assert(date == Date(-1998, 9, 30));
            date.roll!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(-1998, 8, 30));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.roll!"months"(13, AllowDayOverflow.no);
            assert(date == Date(-1997, 1, 31));
            date.roll!"months"(-13, AllowDayOverflow.no);
            assert(date == Date(-1997, 12, 31));
        }

        {
            auto date = Date(-1997, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-1997, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-1997, 12, 28));
        }

        {
            auto date = Date(-2002, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-2002, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-2002, 12, 28));
        }

        {
            auto date = Date(-2001, 12, 31);
            date.roll!"months"(14, AllowDayOverflow.no);
            assert(date == Date(-2001, 2, 28));
            date.roll!"months"(-14, AllowDayOverflow.no);
            assert(date == Date(-2001, 12, 28));
        }

        // Test Both
        {
            auto date = Date(1, 1, 1);
            date.roll!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(1, 12, 1));
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(1, 1, 1));
        }

        {
            auto date = Date(4, 1, 1);
            date.roll!"months"(-48, AllowDayOverflow.no);
            assert(date == Date(4, 1, 1));
            date.roll!"months"(48, AllowDayOverflow.no);
            assert(date == Date(4, 1, 1));
        }

        {
            auto date = Date(4, 3, 31);
            date.roll!"months"(-49, AllowDayOverflow.no);
            assert(date == Date(4, 2, 29));
            date.roll!"months"(49, AllowDayOverflow.no);
            assert(date == Date(4, 3, 29));
        }

        {
            auto date = Date(4, 3, 31);
            date.roll!"months"(-85, AllowDayOverflow.no);
            assert(date == Date(4, 2, 29));
            date.roll!"months"(85, AllowDayOverflow.no);
            assert(date == Date(4, 3, 29));
        }

        {
            auto date = Date(-1, 1, 1);
            date.roll!"months"(-1, AllowDayOverflow.no);
            assert(date == Date(-1, 12, 1));
            date.roll!"months"(1, AllowDayOverflow.no);
            assert(date == Date(-1, 1, 1));
        }

        {
            auto date = Date(-4, 1, 1);
            date.roll!"months"(-48, AllowDayOverflow.no);
            assert(date == Date(-4, 1, 1));
            date.roll!"months"(48, AllowDayOverflow.no);
            assert(date == Date(-4, 1, 1));
        }

        {
            auto date = Date(-4, 3, 31);
            date.roll!"months"(-49, AllowDayOverflow.no);
            assert(date == Date(-4, 2, 29));
            date.roll!"months"(49, AllowDayOverflow.no);
            assert(date == Date(-4, 3, 29));
        }

        {
            auto date = Date(-4, 3, 31);
            date.roll!"months"(-85, AllowDayOverflow.no);
            assert(date == Date(-4, 2, 29));
            date.roll!"months"(85, AllowDayOverflow.no);
            assert(date == Date(-4, 3, 29));
        }

        {
            auto date = Date(-3, 3, 31);
            date.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no);
            assert(date == Date(-3, 5, 30));
        }
    }


    /++
        Adds the given number of units to this $(LREF Date). A negative number
        will subtract.

        The difference between rolling and adding is that rolling does not
        affect larger units. For instance, rolling a $(LREF Date) one
        year's worth of days gets the exact same $(LREF Date).

        The only accepted units are `"days"`.

        Params:
            units = The units to add. Must be `"days"`.
            days  = The number of days to add to this $(LREF Date).
      +/
    ref Date roll(string units)(long days) @safe pure nothrow @nogc
        if (units == "days")
    {
        immutable limit = maxDay(_year, _month);
        days %= limit;
        auto newDay = _day + days;

        if (days < 0)
        {
            if (newDay < 1)
                newDay += limit;
        }
        else if (newDay > limit)
            newDay -= limit;

        _day = cast(ubyte) newDay;
        return this;
    }

    ///
    @safe unittest
    {
        auto d = Date(2010, 1, 1);
        d.roll!"days"(1);
        assert(d == Date(2010, 1, 2));
        d.roll!"days"(365);
        assert(d == Date(2010, 1, 26));
        d.roll!"days"(-32);
        assert(d == Date(2010, 1, 25));
    }

    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 2, 28);
            date.roll!"days"(1);
            assert(date == Date(1999, 2, 1));
            date.roll!"days"(-1);
            assert(date == Date(1999, 2, 28));
        }

        {
            auto date = Date(2000, 2, 28);
            date.roll!"days"(1);
            assert(date == Date(2000, 2, 29));
            date.roll!"days"(1);
            assert(date == Date(2000, 2, 1));
            date.roll!"days"(-1);
            assert(date == Date(2000, 2, 29));
        }

        {
            auto date = Date(1999, 6, 30);
            date.roll!"days"(1);
            assert(date == Date(1999, 6, 1));
            date.roll!"days"(-1);
            assert(date == Date(1999, 6, 30));
        }

        {
            auto date = Date(1999, 7, 31);
            date.roll!"days"(1);
            assert(date == Date(1999, 7, 1));
            date.roll!"days"(-1);
            assert(date == Date(1999, 7, 31));
        }

        {
            auto date = Date(1999, 1, 1);
            date.roll!"days"(-1);
            assert(date == Date(1999, 1, 31));
            date.roll!"days"(1);
            assert(date == Date(1999, 1, 1));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"days"(9);
            assert(date == Date(1999, 7, 15));
            date.roll!"days"(-11);
            assert(date == Date(1999, 7, 4));
            date.roll!"days"(30);
            assert(date == Date(1999, 7, 3));
            date.roll!"days"(-3);
            assert(date == Date(1999, 7, 31));
        }

        {
            auto date = Date(1999, 7, 6);
            date.roll!"days"(365);
            assert(date == Date(1999, 7, 30));
            date.roll!"days"(-365);
            assert(date == Date(1999, 7, 6));
            date.roll!"days"(366);
            assert(date == Date(1999, 7, 31));
            date.roll!"days"(730);
            assert(date == Date(1999, 7, 17));
            date.roll!"days"(-1096);
            assert(date == Date(1999, 7, 6));
        }

        {
            auto date = Date(1999, 2, 6);
            date.roll!"days"(365);
            assert(date == Date(1999, 2, 7));
            date.roll!"days"(-365);
            assert(date == Date(1999, 2, 6));
            date.roll!"days"(366);
            assert(date == Date(1999, 2, 8));
            date.roll!"days"(730);
            assert(date == Date(1999, 2, 10));
            date.roll!"days"(-1096);
            assert(date == Date(1999, 2, 6));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 2, 28);
            date.roll!"days"(1);
            assert(date == Date(-1999, 2, 1));
            date.roll!"days"(-1);
            assert(date == Date(-1999, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 28);
            date.roll!"days"(1);
            assert(date == Date(-2000, 2, 29));
            date.roll!"days"(1);
            assert(date == Date(-2000, 2, 1));
            date.roll!"days"(-1);
            assert(date == Date(-2000, 2, 29));
        }

        {
            auto date = Date(-1999, 6, 30);
            date.roll!"days"(1);
            assert(date == Date(-1999, 6, 1));
            date.roll!"days"(-1);
            assert(date == Date(-1999, 6, 30));
        }

        {
            auto date = Date(-1999, 7, 31);
            date.roll!"days"(1);
            assert(date == Date(-1999, 7, 1));
            date.roll!"days"(-1);
            assert(date == Date(-1999, 7, 31));
        }

        {
            auto date = Date(-1999, 1, 1);
            date.roll!"days"(-1);
            assert(date == Date(-1999, 1, 31));
            date.roll!"days"(1);
            assert(date == Date(-1999, 1, 1));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"days"(9);
            assert(date == Date(-1999, 7, 15));
            date.roll!"days"(-11);
            assert(date == Date(-1999, 7, 4));
            date.roll!"days"(30);
            assert(date == Date(-1999, 7, 3));
            date.roll!"days"(-3);
            assert(date == Date(-1999, 7, 31));
        }

        {
            auto date = Date(-1999, 7, 6);
            date.roll!"days"(365);
            assert(date == Date(-1999, 7, 30));
            date.roll!"days"(-365);
            assert(date == Date(-1999, 7, 6));
            date.roll!"days"(366);
            assert(date == Date(-1999, 7, 31));
            date.roll!"days"(730);
            assert(date == Date(-1999, 7, 17));
            date.roll!"days"(-1096);
            assert(date == Date(-1999, 7, 6));
        }

        // Test Both
        {
            auto date = Date(1, 7, 6);
            date.roll!"days"(-365);
            assert(date == Date(1, 7, 13));
            date.roll!"days"(365);
            assert(date == Date(1, 7, 6));
            date.roll!"days"(-731);
            assert(date == Date(1, 7, 19));
            date.roll!"days"(730);
            assert(date == Date(1, 7, 5));
        }

        {
            auto date = Date(0, 7, 6);
            date.roll!"days"(-365);
            assert(date == Date(0, 7, 13));
            date.roll!"days"(365);
            assert(date == Date(0, 7, 6));
            date.roll!"days"(-731);
            assert(date == Date(0, 7, 19));
            date.roll!"days"(730);
            assert(date == Date(0, 7, 5));
        }

        {
            auto date = Date(0, 7, 6);
            date.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730);
            assert(date == Date(0, 7, 8));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.roll!"days"(12)));
        static assert(!__traits(compiles, idate.roll!"days"(12)));
    }

    import core.time : Duration;
    /++
        Gives the result of adding or subtracting a $(REF Duration, core,time)
        from

        The legal types of arithmetic for $(LREF Date) using this operator are

        $(BOOKTABLE,
        $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date))
        $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date))
        )

        Params:
            duration = The $(REF Duration, core,time) to add to or subtract from
                       this $(LREF Date).
      +/
    Date opBinary(string op)(Duration duration) const @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        Date retval = this;
        immutable days = duration.total!"days";
        mixin("return retval._addDays(" ~ op ~ "days);");
    }

    ///
    @safe unittest
    {
        import core.time : days;

        assert(Date(2015, 12, 31) + days(1) == Date(2016, 1, 1));
        assert(Date(2004, 2, 26) + days(4) == Date(2004, 3, 1));

        assert(Date(2016, 1, 1) - days(1) == Date(2015, 12, 31));
        assert(Date(2004, 3, 1) - days(4) == Date(2004, 2, 26));
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);

        import core.time : dur;
        assert(date + dur!"weeks"(7) == Date(1999, 8, 24));
        assert(date + dur!"weeks"(-7) == Date(1999, 5, 18));
        assert(date + dur!"days"(7) == Date(1999, 7, 13));
        assert(date + dur!"days"(-7) == Date(1999, 6, 29));

        assert(date + dur!"hours"(24) == Date(1999, 7, 7));
        assert(date + dur!"hours"(-24) == Date(1999, 7, 5));
        assert(date + dur!"minutes"(1440) == Date(1999, 7, 7));
        assert(date + dur!"minutes"(-1440) == Date(1999, 7, 5));
        assert(date + dur!"seconds"(86_400) == Date(1999, 7, 7));
        assert(date + dur!"seconds"(-86_400) == Date(1999, 7, 5));
        assert(date + dur!"msecs"(86_400_000) == Date(1999, 7, 7));
        assert(date + dur!"msecs"(-86_400_000) == Date(1999, 7, 5));
        assert(date + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7));
        assert(date + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5));
        assert(date + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7));
        assert(date + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5));

        assert(date - dur!"weeks"(-7) == Date(1999, 8, 24));
        assert(date - dur!"weeks"(7) == Date(1999, 5, 18));
        assert(date - dur!"days"(-7) == Date(1999, 7, 13));
        assert(date - dur!"days"(7) == Date(1999, 6, 29));

        assert(date - dur!"hours"(-24) == Date(1999, 7, 7));
        assert(date - dur!"hours"(24) == Date(1999, 7, 5));
        assert(date - dur!"minutes"(-1440) == Date(1999, 7, 7));
        assert(date - dur!"minutes"(1440) == Date(1999, 7, 5));
        assert(date - dur!"seconds"(-86_400) == Date(1999, 7, 7));
        assert(date - dur!"seconds"(86_400) == Date(1999, 7, 5));
        assert(date - dur!"msecs"(-86_400_000) == Date(1999, 7, 7));
        assert(date - dur!"msecs"(86_400_000) == Date(1999, 7, 5));
        assert(date - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
        assert(date - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5));
        assert(date - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7));
        assert(date - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5));

        auto duration = dur!"days"(12);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date + duration == Date(1999, 7, 18));
        assert(cdate + duration == Date(1999, 7, 18));
        assert(idate + duration == Date(1999, 7, 18));

        assert(date - duration == Date(1999, 6, 24));
        assert(cdate - duration == Date(1999, 6, 24));
        assert(idate - duration == Date(1999, 6, 24));
    }


    /++
        Gives the result of adding or subtracting a $(REF Duration, core,time)
        from this $(LREF Date), as well as assigning the result to this
        $(LREF Date).

        The legal types of arithmetic for $(LREF Date) using this operator are

        $(BOOKTABLE,
        $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date))
        $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date))
        )

        Params:
            duration = The $(REF Duration, core,time) to add to or subtract from
                       this $(LREF Date).
      +/
    ref Date opOpAssign(string op)(Duration duration) @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        immutable days = duration.total!"days";
        mixin("return _addDays(" ~ op ~ "days);");
    }

    @safe unittest
    {
        import core.time : dur;
        assert(Date(1999, 7, 6) + dur!"weeks"(7) == Date(1999, 8, 24));
        assert(Date(1999, 7, 6) + dur!"weeks"(-7) == Date(1999, 5, 18));
        assert(Date(1999, 7, 6) + dur!"days"(7) == Date(1999, 7, 13));
        assert(Date(1999, 7, 6) + dur!"days"(-7) == Date(1999, 6, 29));

        assert(Date(1999, 7, 6) + dur!"hours"(24) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"hours"(-24) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) + dur!"minutes"(1440) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"minutes"(-1440) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) + dur!"seconds"(86_400) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"seconds"(-86_400) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) + dur!"msecs"(86_400_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"msecs"(-86_400_000) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5));

        assert(Date(1999, 7, 6) - dur!"weeks"(-7) == Date(1999, 8, 24));
        assert(Date(1999, 7, 6) - dur!"weeks"(7) == Date(1999, 5, 18));
        assert(Date(1999, 7, 6) - dur!"days"(-7) == Date(1999, 7, 13));
        assert(Date(1999, 7, 6) - dur!"days"(7) == Date(1999, 6, 29));

        assert(Date(1999, 7, 6) - dur!"hours"(-24) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"hours"(24) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) - dur!"minutes"(-1440) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"minutes"(1440) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) - dur!"seconds"(-86_400) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"seconds"(86_400) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) - dur!"msecs"(-86_400_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"msecs"(86_400_000) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5));
        assert(Date(1999, 7, 6) - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7));
        assert(Date(1999, 7, 6) - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5));

        {
            auto date = Date(0, 1, 31);
            (date += dur!"days"(507)) += dur!"days"(-2);
            assert(date == Date(1, 6, 19));
        }

        auto duration = dur!"days"(12);
        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        date += duration;
        static assert(!__traits(compiles, cdate += duration));
        static assert(!__traits(compiles, idate += duration));

        date -= duration;
        static assert(!__traits(compiles, cdate -= duration));
        static assert(!__traits(compiles, idate -= duration));
    }


    /++
        Gives the difference between two $(LREF Date)s.

        The legal types of arithmetic for $(LREF Date) using this operator are

        $(BOOKTABLE,
        $(TR $(TD Date) $(TD -) $(TD Date) $(TD -->) $(TD duration))
        )
      +/
    Duration opBinary(string op)(Date rhs) const @safe pure nothrow @nogc
        if (op == "-")
    {
        import core.time : dur;
        return dur!"days"(this.dayOfGregorianCal - rhs.dayOfGregorianCal);
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);

        import core.time : dur;
        assert(Date(1999, 7, 6) - Date(1998, 7, 6) == dur!"days"(365));
        assert(Date(1998, 7, 6) - Date(1999, 7, 6) == dur!"days"(-365));
        assert(Date(1999, 6, 6) - Date(1999, 5, 6) == dur!"days"(31));
        assert(Date(1999, 5, 6) - Date(1999, 6, 6) == dur!"days"(-31));
        assert(Date(1999, 1, 1) - Date(1998, 12, 31) == dur!"days"(1));
        assert(Date(1998, 12, 31) - Date(1999, 1, 1) == dur!"days"(-1));

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date - date == Duration.zero);
        assert(cdate - date == Duration.zero);
        assert(idate - date == Duration.zero);

        assert(date - cdate == Duration.zero);
        assert(cdate - cdate == Duration.zero);
        assert(idate - cdate == Duration.zero);

        assert(date - idate == Duration.zero);
        assert(cdate - idate == Duration.zero);
        assert(idate - idate == Duration.zero);
    }


    /++
        Returns the difference between the two $(LREF Date)s in months.

        To get the difference in years, subtract the year property
        of two $(LREF Date)s. To get the difference in days or weeks,
        subtract the $(LREF Date)s themselves and use the
        $(REF Duration, core,time) that results. Because converting between
        months and smaller units requires a specific date (which
        $(REF Duration, core,time)s don't have), getting the difference in
        months requires some math using both the year and month properties, so
        this is a convenience function for getting the difference in months.

        Note that the number of days in the months or how far into the month
        either $(LREF Date) is is irrelevant. It is the difference in the month
        property combined with the difference in years * 12. So, for instance,
        December 31st and January 1st are one month apart just as December 1st
        and January 31st are one month apart.

        Params:
            rhs = The $(LREF Date) to subtract from this one.
      +/
    int diffMonths(Date rhs) const @safe pure nothrow @nogc
    {
        immutable yearDiff = _year - rhs._year;
        immutable monthDiff = _month - rhs._month;

        return yearDiff * 12 + monthDiff;
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 2, 1).diffMonths(Date(1999, 1, 31)) == 1);
        assert(Date(1999, 1, 31).diffMonths(Date(1999, 2, 1)) == -1);
        assert(Date(1999, 3, 1).diffMonths(Date(1999, 1, 1)) == 2);
        assert(Date(1999, 1, 1).diffMonths(Date(1999, 3, 31)) == -2);
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);

        // Test A.D.
        assert(date.diffMonths(Date(1998, 6, 5)) == 13);
        assert(date.diffMonths(Date(1998, 7, 5)) == 12);
        assert(date.diffMonths(Date(1998, 8, 5)) == 11);
        assert(date.diffMonths(Date(1998, 9, 5)) == 10);
        assert(date.diffMonths(Date(1998, 10, 5)) == 9);
        assert(date.diffMonths(Date(1998, 11, 5)) == 8);
        assert(date.diffMonths(Date(1998, 12, 5)) == 7);
        assert(date.diffMonths(Date(1999, 1, 5)) == 6);
        assert(date.diffMonths(Date(1999, 2, 6)) == 5);
        assert(date.diffMonths(Date(1999, 3, 6)) == 4);
        assert(date.diffMonths(Date(1999, 4, 6)) == 3);
        assert(date.diffMonths(Date(1999, 5, 6)) == 2);
        assert(date.diffMonths(Date(1999, 6, 6)) == 1);
        assert(date.diffMonths(date) == 0);
        assert(date.diffMonths(Date(1999, 8, 6)) == -1);
        assert(date.diffMonths(Date(1999, 9, 6)) == -2);
        assert(date.diffMonths(Date(1999, 10, 6)) == -3);
        assert(date.diffMonths(Date(1999, 11, 6)) == -4);
        assert(date.diffMonths(Date(1999, 12, 6)) == -5);
        assert(date.diffMonths(Date(2000, 1, 6)) == -6);
        assert(date.diffMonths(Date(2000, 2, 6)) == -7);
        assert(date.diffMonths(Date(2000, 3, 6)) == -8);
        assert(date.diffMonths(Date(2000, 4, 6)) == -9);
        assert(date.diffMonths(Date(2000, 5, 6)) == -10);
        assert(date.diffMonths(Date(2000, 6, 6)) == -11);
        assert(date.diffMonths(Date(2000, 7, 6)) == -12);
        assert(date.diffMonths(Date(2000, 8, 6)) == -13);

        assert(Date(1998, 6, 5).diffMonths(date) == -13);
        assert(Date(1998, 7, 5).diffMonths(date) == -12);
        assert(Date(1998, 8, 5).diffMonths(date) == -11);
        assert(Date(1998, 9, 5).diffMonths(date) == -10);
        assert(Date(1998, 10, 5).diffMonths(date) == -9);
        assert(Date(1998, 11, 5).diffMonths(date) == -8);
        assert(Date(1998, 12, 5).diffMonths(date) == -7);
        assert(Date(1999, 1, 5).diffMonths(date) == -6);
        assert(Date(1999, 2, 6).diffMonths(date) == -5);
        assert(Date(1999, 3, 6).diffMonths(date) == -4);
        assert(Date(1999, 4, 6).diffMonths(date) == -3);
        assert(Date(1999, 5, 6).diffMonths(date) == -2);
        assert(Date(1999, 6, 6).diffMonths(date) == -1);
        assert(Date(1999, 8, 6).diffMonths(date) == 1);
        assert(Date(1999, 9, 6).diffMonths(date) == 2);
        assert(Date(1999, 10, 6).diffMonths(date) == 3);
        assert(Date(1999, 11, 6).diffMonths(date) == 4);
        assert(Date(1999, 12, 6).diffMonths(date) == 5);
        assert(Date(2000, 1, 6).diffMonths(date) == 6);
        assert(Date(2000, 2, 6).diffMonths(date) == 7);
        assert(Date(2000, 3, 6).diffMonths(date) == 8);
        assert(Date(2000, 4, 6).diffMonths(date) == 9);
        assert(Date(2000, 5, 6).diffMonths(date) == 10);
        assert(Date(2000, 6, 6).diffMonths(date) == 11);
        assert(Date(2000, 7, 6).diffMonths(date) == 12);
        assert(Date(2000, 8, 6).diffMonths(date) == 13);

        assert(date.diffMonths(Date(1999, 6, 30)) == 1);
        assert(date.diffMonths(Date(1999, 7, 1)) == 0);
        assert(date.diffMonths(Date(1999, 7, 6)) == 0);
        assert(date.diffMonths(Date(1999, 7, 11)) == 0);
        assert(date.diffMonths(Date(1999, 7, 16)) == 0);
        assert(date.diffMonths(Date(1999, 7, 21)) == 0);
        assert(date.diffMonths(Date(1999, 7, 26)) == 0);
        assert(date.diffMonths(Date(1999, 7, 31)) == 0);
        assert(date.diffMonths(Date(1999, 8, 1)) == -1);

        assert(date.diffMonths(Date(1990, 6, 30)) == 109);
        assert(date.diffMonths(Date(1990, 7, 1)) == 108);
        assert(date.diffMonths(Date(1990, 7, 6)) == 108);
        assert(date.diffMonths(Date(1990, 7, 11)) == 108);
        assert(date.diffMonths(Date(1990, 7, 16)) == 108);
        assert(date.diffMonths(Date(1990, 7, 21)) == 108);
        assert(date.diffMonths(Date(1990, 7, 26)) == 108);
        assert(date.diffMonths(Date(1990, 7, 31)) == 108);
        assert(date.diffMonths(Date(1990, 8, 1)) == 107);

        assert(Date(1999, 6, 30).diffMonths(date) == -1);
        assert(Date(1999, 7, 1).diffMonths(date) == 0);
        assert(Date(1999, 7, 6).diffMonths(date) == 0);
        assert(Date(1999, 7, 11).diffMonths(date) == 0);
        assert(Date(1999, 7, 16).diffMonths(date) == 0);
        assert(Date(1999, 7, 21).diffMonths(date) == 0);
        assert(Date(1999, 7, 26).diffMonths(date) == 0);
        assert(Date(1999, 7, 31).diffMonths(date) == 0);
        assert(Date(1999, 8, 1).diffMonths(date) == 1);

        assert(Date(1990, 6, 30).diffMonths(date) == -109);
        assert(Date(1990, 7, 1).diffMonths(date) == -108);
        assert(Date(1990, 7, 6).diffMonths(date) == -108);
        assert(Date(1990, 7, 11).diffMonths(date) == -108);
        assert(Date(1990, 7, 16).diffMonths(date) == -108);
        assert(Date(1990, 7, 21).diffMonths(date) == -108);
        assert(Date(1990, 7, 26).diffMonths(date) == -108);
        assert(Date(1990, 7, 31).diffMonths(date) == -108);
        assert(Date(1990, 8, 1).diffMonths(date) == -107);

        // Test B.C.
        auto dateBC = Date(-1999, 7, 6);

        assert(dateBC.diffMonths(Date(-2000, 6, 5)) == 13);
        assert(dateBC.diffMonths(Date(-2000, 7, 5)) == 12);
        assert(dateBC.diffMonths(Date(-2000, 8, 5)) == 11);
        assert(dateBC.diffMonths(Date(-2000, 9, 5)) == 10);
        assert(dateBC.diffMonths(Date(-2000, 10, 5)) == 9);
        assert(dateBC.diffMonths(Date(-2000, 11, 5)) == 8);
        assert(dateBC.diffMonths(Date(-2000, 12, 5)) == 7);
        assert(dateBC.diffMonths(Date(-1999, 1, 5)) == 6);
        assert(dateBC.diffMonths(Date(-1999, 2, 6)) == 5);
        assert(dateBC.diffMonths(Date(-1999, 3, 6)) == 4);
        assert(dateBC.diffMonths(Date(-1999, 4, 6)) == 3);
        assert(dateBC.diffMonths(Date(-1999, 5, 6)) == 2);
        assert(dateBC.diffMonths(Date(-1999, 6, 6)) == 1);
        assert(dateBC.diffMonths(dateBC) == 0);
        assert(dateBC.diffMonths(Date(-1999, 8, 6)) == -1);
        assert(dateBC.diffMonths(Date(-1999, 9, 6)) == -2);
        assert(dateBC.diffMonths(Date(-1999, 10, 6)) == -3);
        assert(dateBC.diffMonths(Date(-1999, 11, 6)) == -4);
        assert(dateBC.diffMonths(Date(-1999, 12, 6)) == -5);
        assert(dateBC.diffMonths(Date(-1998, 1, 6)) == -6);
        assert(dateBC.diffMonths(Date(-1998, 2, 6)) == -7);
        assert(dateBC.diffMonths(Date(-1998, 3, 6)) == -8);
        assert(dateBC.diffMonths(Date(-1998, 4, 6)) == -9);
        assert(dateBC.diffMonths(Date(-1998, 5, 6)) == -10);
        assert(dateBC.diffMonths(Date(-1998, 6, 6)) == -11);
        assert(dateBC.diffMonths(Date(-1998, 7, 6)) == -12);
        assert(dateBC.diffMonths(Date(-1998, 8, 6)) == -13);

        assert(Date(-2000, 6, 5).diffMonths(dateBC) == -13);
        assert(Date(-2000, 7, 5).diffMonths(dateBC) == -12);
        assert(Date(-2000, 8, 5).diffMonths(dateBC) == -11);
        assert(Date(-2000, 9, 5).diffMonths(dateBC) == -10);
        assert(Date(-2000, 10, 5).diffMonths(dateBC) == -9);
        assert(Date(-2000, 11, 5).diffMonths(dateBC) == -8);
        assert(Date(-2000, 12, 5).diffMonths(dateBC) == -7);
        assert(Date(-1999, 1, 5).diffMonths(dateBC) == -6);
        assert(Date(-1999, 2, 6).diffMonths(dateBC) == -5);
        assert(Date(-1999, 3, 6).diffMonths(dateBC) == -4);
        assert(Date(-1999, 4, 6).diffMonths(dateBC) == -3);
        assert(Date(-1999, 5, 6).diffMonths(dateBC) == -2);
        assert(Date(-1999, 6, 6).diffMonths(dateBC) == -1);
        assert(Date(-1999, 8, 6).diffMonths(dateBC) == 1);
        assert(Date(-1999, 9, 6).diffMonths(dateBC) == 2);
        assert(Date(-1999, 10, 6).diffMonths(dateBC) == 3);
        assert(Date(-1999, 11, 6).diffMonths(dateBC) == 4);
        assert(Date(-1999, 12, 6).diffMonths(dateBC) == 5);
        assert(Date(-1998, 1, 6).diffMonths(dateBC) == 6);
        assert(Date(-1998, 2, 6).diffMonths(dateBC) == 7);
        assert(Date(-1998, 3, 6).diffMonths(dateBC) == 8);
        assert(Date(-1998, 4, 6).diffMonths(dateBC) == 9);
        assert(Date(-1998, 5, 6).diffMonths(dateBC) == 10);
        assert(Date(-1998, 6, 6).diffMonths(dateBC) == 11);
        assert(Date(-1998, 7, 6).diffMonths(dateBC) == 12);
        assert(Date(-1998, 8, 6).diffMonths(dateBC) == 13);

        assert(dateBC.diffMonths(Date(-1999, 6, 30)) == 1);
        assert(dateBC.diffMonths(Date(-1999, 7, 1)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 6)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 11)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 16)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 21)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 26)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 7, 31)) == 0);
        assert(dateBC.diffMonths(Date(-1999, 8, 1)) == -1);

        assert(dateBC.diffMonths(Date(-2008, 6, 30)) == 109);
        assert(dateBC.diffMonths(Date(-2008, 7, 1)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 6)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 11)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 16)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 21)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 26)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 7, 31)) == 108);
        assert(dateBC.diffMonths(Date(-2008, 8, 1)) == 107);

        assert(Date(-1999, 6, 30).diffMonths(dateBC) == -1);
        assert(Date(-1999, 7, 1).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 6).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 11).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 16).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 21).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 26).diffMonths(dateBC) == 0);
        assert(Date(-1999, 7, 31).diffMonths(dateBC) == 0);
        assert(Date(-1999, 8, 1).diffMonths(dateBC) == 1);

        assert(Date(-2008, 6, 30).diffMonths(dateBC) == -109);
        assert(Date(-2008, 7, 1).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 6).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 11).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 16).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 21).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 26).diffMonths(dateBC) == -108);
        assert(Date(-2008, 7, 31).diffMonths(dateBC) == -108);
        assert(Date(-2008, 8, 1).diffMonths(dateBC) == -107);

        // Test Both
        assert(Date(3, 3, 3).diffMonths(Date(-5, 5, 5)) == 94);
        assert(Date(-5, 5, 5).diffMonths(Date(3, 3, 3)) == -94);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date.diffMonths(date) == 0);
        assert(cdate.diffMonths(date) == 0);
        assert(idate.diffMonths(date) == 0);

        assert(date.diffMonths(cdate) == 0);
        assert(cdate.diffMonths(cdate) == 0);
        assert(idate.diffMonths(cdate) == 0);

        assert(date.diffMonths(idate) == 0);
        assert(cdate.diffMonths(idate) == 0);
        assert(idate.diffMonths(idate) == 0);
    }


    /++
        Whether this $(LREF Date) is in a leap year.
     +/
    @property bool isLeapYear() const @safe pure nothrow @nogc
    {
        return yearIsLeapYear(_year);
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, date.isLeapYear = true));
        static assert(!__traits(compiles, cdate.isLeapYear = true));
        static assert(!__traits(compiles, idate.isLeapYear = true));
    }


    /++
        Day of the week this $(LREF Date) is on.
      +/
    @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc
    {
        return getDayOfWeek(dayOfGregorianCal);
    }

    @safe unittest
    {
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.dayOfWeek == DayOfWeek.tue);
        static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun));
        assert(idate.dayOfWeek == DayOfWeek.tue);
        static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun));
    }


    /++
        Day of the year this $(LREF Date) is on.
      +/
    @property ushort dayOfYear() const @safe pure nothrow @nogc
    {
        if (_month >= Month.jan && _month <= Month.dec)
        {
            immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;
            auto monthIndex = _month - Month.jan;

            return cast(ushort)(lastDay[monthIndex] + _day);
        }
        assert(0, "Invalid month.");
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 1, 1).dayOfYear == 1);
        assert(Date(1999, 12, 31).dayOfYear == 365);
        assert(Date(2000, 12, 31).dayOfYear == 366);
    }

    @safe unittest
    {
        import std.algorithm.iteration : filter;
        import std.range : chain;

        foreach (year; filter!((a){return !yearIsLeapYear(a);})(chain(testYearsBC, testYearsAD)))
        {
            foreach (doy; testDaysOfYear)
                assert(Date(year, doy.md.month, doy.md.day).dayOfYear == doy.day);
        }

        foreach (year; filter!((a){return yearIsLeapYear(a);})(chain(testYearsBC, testYearsAD)))
        {
            foreach (doy; testDaysOfLeapYear)
                assert(Date(year, doy.md.month, doy.md.day).dayOfYear == doy.day);
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.dayOfYear == 187);
        assert(idate.dayOfYear == 187);
    }

    /++
        Day of the year.

        Params:
            day = The day of the year to set which day of the year this
                  $(LREF Date) is on.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given day is an
            invalid day of the year.
      +/
    @property void dayOfYear(int day) @safe pure
    {
        setDayOfYear!true(day);
    }

    private void setDayOfYear(bool useExceptions = false)(int day)
    {
        immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;

        bool dayOutOfRange = day <= 0 || day > (isLeapYear ? daysInLeapYear : daysInYear);
        enum errorMsg = "Invalid day of the year.";

        static if (useExceptions)
        {
            if (dayOutOfRange) throw new DateTimeException(errorMsg);
        }
        else
        {
            assert(!dayOutOfRange, errorMsg);
        }

        foreach (i; 1 .. lastDay.length)
        {
            if (day <= lastDay[i])
            {
                _month = cast(Month)(cast(int) Month.jan + i - 1);
                _day = cast(ubyte)(day - lastDay[i - 1]);
                return;
            }
        }
        assert(0, "Invalid day of the year.");
    }

    @safe unittest
    {
        static void test(Date date, int day, MonthDay expected, size_t line = __LINE__)
        {
            date.dayOfYear = day;
            assert(date.month == expected.month);
            assert(date.day == expected.day);
        }

        foreach (doy; testDaysOfYear)
        {
            test(Date(1999, 1, 1), doy.day, doy.md);
            test(Date(-1, 1, 1), doy.day, doy.md);
        }

        foreach (doy; testDaysOfLeapYear)
        {
            test(Date(2000, 1, 1), doy.day, doy.md);
            test(Date(-4, 1, 1), doy.day, doy.md);
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.dayOfYear = 187));
        static assert(!__traits(compiles, idate.dayOfYear = 187));
    }


    /++
        The Xth day of the Gregorian Calendar that this $(LREF Date) is on.
     +/
    @property int dayOfGregorianCal() const @safe pure nothrow @nogc
    {
        if (isAD)
        {
            if (_year == 1)
                return dayOfYear;

            int years = _year - 1;
            auto days = (years / 400) * daysIn400Years;
            years %= 400;

            days += (years / 100) * daysIn100Years;
            years %= 100;

            days += (years / 4) * daysIn4Years;
            years %= 4;

            days += years * daysInYear;

            days += dayOfYear;

            return days;
        }
        else if (_year == 0)
            return dayOfYear - daysInLeapYear;
        else
        {
            int years = _year;
            auto days = (years / 400) * daysIn400Years;
            years %= 400;

            days += (years / 100) * daysIn100Years;
            years %= 100;

            days += (years / 4) * daysIn4Years;
            years %= 4;

            if (years < 0)
            {
                days -= daysInLeapYear;
                ++years;

                days += years * daysInYear;

                days -= daysInYear - dayOfYear;
            }
            else
                days -= daysInLeapYear - dayOfYear;

            return days;
        }
    }

    ///
    @safe unittest
    {
        assert(Date(1, 1, 1).dayOfGregorianCal == 1);
        assert(Date(1, 12, 31).dayOfGregorianCal == 365);
        assert(Date(2, 1, 1).dayOfGregorianCal == 366);

        assert(Date(0, 12, 31).dayOfGregorianCal == 0);
        assert(Date(0, 1, 1).dayOfGregorianCal == -365);
        assert(Date(-1, 12, 31).dayOfGregorianCal == -366);

        assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120);
        assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137);
    }

    @safe unittest
    {
        import std.range : chain;

        foreach (gd; chain(testGregDaysBC, testGregDaysAD))
            assert(gd.date.dayOfGregorianCal == gd.day);

        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date.dayOfGregorianCal == 729_941);
        assert(cdate.dayOfGregorianCal == 729_941);
        assert(idate.dayOfGregorianCal == 729_941);
    }

    /++
        The Xth day of the Gregorian Calendar that this $(LREF Date) is on.

        Params:
            day = The day of the Gregorian Calendar to set this $(LREF Date) to.
     +/
    @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc
    {
        this = Date(day);
    }

    ///
    @safe unittest
    {
        auto date = Date.init;
        date.dayOfGregorianCal = 1;
        assert(date == Date(1, 1, 1));

        date.dayOfGregorianCal = 365;
        assert(date == Date(1, 12, 31));

        date.dayOfGregorianCal = 366;
        assert(date == Date(2, 1, 1));

        date.dayOfGregorianCal = 0;
        assert(date == Date(0, 12, 31));

        date.dayOfGregorianCal = -365;
        assert(date == Date(-0, 1, 1));

        date.dayOfGregorianCal = -366;
        assert(date == Date(-1, 12, 31));

        date.dayOfGregorianCal = 730_120;
        assert(date == Date(2000, 1, 1));

        date.dayOfGregorianCal = 734_137;
        assert(date == Date(2010, 12, 31));
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        date.dayOfGregorianCal = 187;
        assert(date.dayOfGregorianCal == 187);
        static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187));
        static assert(!__traits(compiles, idate.dayOfGregorianCal = 187));
    }


    /++
        The ISO 8601 week of the year that this $(LREF Date) is in.

        See_Also:
            $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
      +/
    @property ubyte isoWeek() const @safe pure nothrow
    {
        immutable weekday = dayOfWeek;
        immutable adjustedWeekday = weekday == DayOfWeek.sun ? 7 : weekday;
        immutable week = (dayOfYear - adjustedWeekday + 10) / 7;

        try
        {
            if (week == 53)
            {
                switch (Date(_year + 1, 1, 1).dayOfWeek)
                {
                    case DayOfWeek.mon:
                    case DayOfWeek.tue:
                    case DayOfWeek.wed:
                    case DayOfWeek.thu:
                        return 1;
                    case DayOfWeek.fri:
                    case DayOfWeek.sat:
                    case DayOfWeek.sun:
                        return 53;
                    default:
                        assert(0, "Invalid ISO Week");
                }
            }
            else if (week > 0)
                return cast(ubyte) week;
            else
                return Date(_year - 1, 12, 31).isoWeek;
        }
        catch (Exception e)
            assert(0, "Date's constructor threw.");
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(2009, 12, 28).isoWeek == 53);
        assert(Date(2009, 12, 29).isoWeek == 53);
        assert(Date(2009, 12, 30).isoWeek == 53);
        assert(Date(2009, 12, 31).isoWeek == 53);
        assert(Date(2010, 1, 1).isoWeek == 53);
        assert(Date(2010, 1, 2).isoWeek == 53);
        assert(Date(2010, 1, 3).isoWeek == 53);
        assert(Date(2010, 1, 4).isoWeek == 1);
        assert(Date(2010, 1, 5).isoWeek == 1);
        assert(Date(2010, 1, 6).isoWeek == 1);
        assert(Date(2010, 1, 7).isoWeek == 1);
        assert(Date(2010, 1, 8).isoWeek == 1);
        assert(Date(2010, 1, 9).isoWeek == 1);
        assert(Date(2010, 1, 10).isoWeek == 1);
        assert(Date(2010, 1, 11).isoWeek == 2);
        assert(Date(2010, 12, 31).isoWeek == 52);

        assert(Date(2004, 12, 26).isoWeek == 52);
        assert(Date(2004, 12, 27).isoWeek == 53);
        assert(Date(2004, 12, 28).isoWeek == 53);
        assert(Date(2004, 12, 29).isoWeek == 53);
        assert(Date(2004, 12, 30).isoWeek == 53);
        assert(Date(2004, 12, 31).isoWeek == 53);
        assert(Date(2005, 1, 1).isoWeek == 53);
        assert(Date(2005, 1, 2).isoWeek == 53);

        assert(Date(2005, 12, 31).isoWeek == 52);
        assert(Date(2007, 1, 1).isoWeek == 1);

        assert(Date(2007, 12, 30).isoWeek == 52);
        assert(Date(2007, 12, 31).isoWeek == 1);
        assert(Date(2008, 1, 1).isoWeek == 1);

        assert(Date(2008, 12, 28).isoWeek == 52);
        assert(Date(2008, 12, 29).isoWeek == 1);
        assert(Date(2008, 12, 30).isoWeek == 1);
        assert(Date(2008, 12, 31).isoWeek == 1);
        assert(Date(2009, 1, 1).isoWeek == 1);
        assert(Date(2009, 1, 2).isoWeek == 1);
        assert(Date(2009, 1, 3).isoWeek == 1);
        assert(Date(2009, 1, 4).isoWeek == 1);

        // Test B.C.
        // The algorithm should work identically for both A.D. and B.C. since
        // it doesn't really take the year into account, so B.C. testing
        // probably isn't really needed.
        assert(Date(0, 12, 31).isoWeek == 52);
        assert(Date(0, 1, 4).isoWeek == 1);
        assert(Date(0, 1, 1).isoWeek == 52);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.isoWeek == 27);
        static assert(!__traits(compiles, cdate.isoWeek = 3));
        assert(idate.isoWeek == 27);
        static assert(!__traits(compiles, idate.isoWeek = 3));
    }


    /++
        $(LREF Date) for the last day in the month that this $(LREF Date) is in.
      +/
    @property Date endOfMonth() const @safe pure nothrow
    {
        try
            return Date(_year, _month, maxDay(_year, _month));
        catch (Exception e)
            assert(0, "Date's constructor threw.");
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31));
        assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28));
        assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29));
        assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30));
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31));
        assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28));
        assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29));
        assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31));
        assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30));
        assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31));
        assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30));
        assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31));
        assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31));
        assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30));
        assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31));
        assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30));
        assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31));

        // Test B.C.
        assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31));
        assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28));
        assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29));
        assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31));
        assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30));
        assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31));
        assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30));
        assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31));
        assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31));
        assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30));
        assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31));
        assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30));
        assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31));

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30)));
        static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30)));
    }


    /++
        The last day in the month that this $(LREF Date) is in.
      +/
    @property ubyte daysInMonth() const @safe pure nothrow @nogc
    {
        return maxDay(_year, _month);
    }

    ///
    @safe unittest
    {
        assert(Date(1999, 1, 6).daysInMonth == 31);
        assert(Date(1999, 2, 7).daysInMonth == 28);
        assert(Date(2000, 2, 7).daysInMonth == 29);
        assert(Date(2000, 6, 4).daysInMonth == 30);
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(1999, 1, 1).daysInMonth == 31);
        assert(Date(1999, 2, 1).daysInMonth == 28);
        assert(Date(2000, 2, 1).daysInMonth == 29);
        assert(Date(1999, 3, 1).daysInMonth == 31);
        assert(Date(1999, 4, 1).daysInMonth == 30);
        assert(Date(1999, 5, 1).daysInMonth == 31);
        assert(Date(1999, 6, 1).daysInMonth == 30);
        assert(Date(1999, 7, 1).daysInMonth == 31);
        assert(Date(1999, 8, 1).daysInMonth == 31);
        assert(Date(1999, 9, 1).daysInMonth == 30);
        assert(Date(1999, 10, 1).daysInMonth == 31);
        assert(Date(1999, 11, 1).daysInMonth == 30);
        assert(Date(1999, 12, 1).daysInMonth == 31);

        // Test B.C.
        assert(Date(-1999, 1, 1).daysInMonth == 31);
        assert(Date(-1999, 2, 1).daysInMonth == 28);
        assert(Date(-2000, 2, 1).daysInMonth == 29);
        assert(Date(-1999, 3, 1).daysInMonth == 31);
        assert(Date(-1999, 4, 1).daysInMonth == 30);
        assert(Date(-1999, 5, 1).daysInMonth == 31);
        assert(Date(-1999, 6, 1).daysInMonth == 30);
        assert(Date(-1999, 7, 1).daysInMonth == 31);
        assert(Date(-1999, 8, 1).daysInMonth == 31);
        assert(Date(-1999, 9, 1).daysInMonth == 30);
        assert(Date(-1999, 10, 1).daysInMonth == 31);
        assert(Date(-1999, 11, 1).daysInMonth == 30);
        assert(Date(-1999, 12, 1).daysInMonth == 31);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate.daysInMonth = 30));
        static assert(!__traits(compiles, idate.daysInMonth = 30));
    }


    /++
        Whether the current year is a date in A.D.
      +/
    @property bool isAD() const @safe pure nothrow @nogc
    {
        return _year > 0;
    }

    ///
    @safe unittest
    {
        assert(Date(1, 1, 1).isAD);
        assert(Date(2010, 12, 31).isAD);
        assert(!Date(0, 12, 31).isAD);
        assert(!Date(-2010, 1, 1).isAD);
    }

    @safe unittest
    {
        assert(Date(2010, 7, 4).isAD);
        assert(Date(1, 1, 1).isAD);
        assert(!Date(0, 1, 1).isAD);
        assert(!Date(-1, 1, 1).isAD);
        assert(!Date(-2010, 7, 4).isAD);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.isAD);
        assert(idate.isAD);
    }


    /++
        The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this
        $(LREF Date) at noon (since the Julian day changes at noon).
      +/
    @property long julianDay() const @safe pure nothrow @nogc
    {
        return dayOfGregorianCal + 1_721_425;
    }

    @safe unittest
    {
        assert(Date(-4713, 11, 24).julianDay == 0);
        assert(Date(0, 12, 31).julianDay == 1_721_425);
        assert(Date(1, 1, 1).julianDay == 1_721_426);
        assert(Date(1582, 10, 15).julianDay == 2_299_161);
        assert(Date(1858, 11, 17).julianDay == 2_400_001);
        assert(Date(1982, 1, 4).julianDay == 2_444_974);
        assert(Date(1996, 3, 31).julianDay == 2_450_174);
        assert(Date(2010, 8, 24).julianDay == 2_455_433);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.julianDay == 2_451_366);
        assert(idate.julianDay == 2_451_366);
    }


    /++
        The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for
        any time on this date (since, the modified Julian day changes at
        midnight).
      +/
    @property long modJulianDay() const @safe pure nothrow @nogc
    {
        return julianDay - 2_400_001;
    }

    @safe unittest
    {
        assert(Date(1858, 11, 17).modJulianDay == 0);
        assert(Date(2010, 8, 24).modJulianDay == 55_432);

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.modJulianDay == 51_365);
        assert(idate.modJulianDay == 51_365);
    }


    /++
        Converts this $(LREF Date) to a string with the format `YYYYMMDD`.
        If `writer` is set, the resulting string will be written directly
        to it.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(8);
        try
            toISOString(w);
        catch (Exception e)
            assert(0, "toISOString() threw.");
        return w.data;
    }

    ///
    @safe unittest
    {
        assert(Date(2010, 7, 4).toISOString() == "20100704");
        assert(Date(1998, 12, 25).toISOString() == "19981225");
        assert(Date(0, 1, 5).toISOString() == "00000105");
        assert(Date(-4, 1, 5).toISOString() == "-00040105");
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(9, 12, 4).toISOString() == "00091204");
        assert(Date(99, 12, 4).toISOString() == "00991204");
        assert(Date(999, 12, 4).toISOString() == "09991204");
        assert(Date(9999, 7, 4).toISOString() == "99990704");
        assert(Date(10000, 10, 20).toISOString() == "+100001020");

        // Test B.C.
        assert(Date(0, 12, 4).toISOString() == "00001204");
        assert(Date(-9, 12, 4).toISOString() == "-00091204");
        assert(Date(-99, 12, 4).toISOString() == "-00991204");
        assert(Date(-999, 12, 4).toISOString() == "-09991204");
        assert(Date(-9999, 7, 4).toISOString() == "-99990704");
        assert(Date(-10000, 10, 20).toISOString() == "-100001020");

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.toISOString() == "19990706");
        assert(idate.toISOString() == "19990706");
    }

    /// ditto
    void toISOString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        if (_year >= 0)
        {
            if (_year < 10_000)
                formattedWrite(writer, "%04d%02d%02d", _year, _month, _day);
            else
                formattedWrite(writer, "+%05d%02d%02d", _year, _month, _day);
        }
        else if (_year > -10_000)
            formattedWrite(writer, "%05d%02d%02d", _year, _month, _day);
        else
            formattedWrite(writer, "%06d%02d%02d", _year, _month, _day);
    }

    @safe pure unittest
    {
        import std.array : appender;

        auto w = appender!(char[])();
        Date(2010, 7, 4).toISOString(w);
        assert(w.data == "20100704");
        w.clear();
        Date(1998, 12, 25).toISOString(w);
        assert(w.data == "19981225");
    }

    /++
        Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`.
        If `writer` is set, the resulting string will be written directly
        to it.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOExtString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(10);
        try
            toISOExtString(w);
        catch (Exception e)
            assert(0, "toISOExtString() threw.");
        return w.data;
    }

    ///
    @safe unittest
    {
        assert(Date(2010, 7, 4).toISOExtString() == "2010-07-04");
        assert(Date(1998, 12, 25).toISOExtString() == "1998-12-25");
        assert(Date(0, 1, 5).toISOExtString() == "0000-01-05");
        assert(Date(-4, 1, 5).toISOExtString() == "-0004-01-05");
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(9, 12, 4).toISOExtString() == "0009-12-04");
        assert(Date(99, 12, 4).toISOExtString() == "0099-12-04");
        assert(Date(999, 12, 4).toISOExtString() == "0999-12-04");
        assert(Date(9999, 7, 4).toISOExtString() == "9999-07-04");
        assert(Date(10000, 10, 20).toISOExtString() == "+10000-10-20");

        // Test B.C.
        assert(Date(0, 12, 4).toISOExtString() == "0000-12-04");
        assert(Date(-9, 12, 4).toISOExtString() == "-0009-12-04");
        assert(Date(-99, 12, 4).toISOExtString() == "-0099-12-04");
        assert(Date(-999, 12, 4).toISOExtString() == "-0999-12-04");
        assert(Date(-9999, 7, 4).toISOExtString() == "-9999-07-04");
        assert(Date(-10000, 10, 20).toISOExtString() == "-10000-10-20");

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.toISOExtString() == "1999-07-06");
        assert(idate.toISOExtString() == "1999-07-06");
    }

    /// ditto
    void toISOExtString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        if (_year >= 0)
        {
            if (_year < 10_000)
                formattedWrite(writer, "%04d-%02d-%02d", _year, _month, _day);
            else
                formattedWrite(writer, "+%05d-%02d-%02d", _year, _month, _day);
        }
        else if (_year > -10_000)
            formattedWrite(writer, "%05d-%02d-%02d", _year, _month, _day);
        else
            formattedWrite(writer, "%06d-%02d-%02d", _year, _month, _day);
    }

    @safe pure unittest
    {
        import std.array : appender;

        auto w = appender!(char[])();
        Date(2010, 7, 4).toISOExtString(w);
        assert(w.data == "2010-07-04");
        w.clear();
        Date(-4, 1, 5).toISOExtString(w);
        assert(w.data == "-0004-01-05");
    }

    /++
        Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`.
        If `writer` is set, the resulting string will be written directly
        to it.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toSimpleString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(11);
        try
            toSimpleString(w);
        catch (Exception e)
            assert(0, "toSimpleString() threw.");
        return w.data;
    }

    ///
    @safe unittest
    {
        assert(Date(2010, 7, 4).toSimpleString() == "2010-Jul-04");
        assert(Date(1998, 12, 25).toSimpleString() == "1998-Dec-25");
        assert(Date(0, 1, 5).toSimpleString() == "0000-Jan-05");
        assert(Date(-4, 1, 5).toSimpleString() == "-0004-Jan-05");
    }

    @safe unittest
    {
        // Test A.D.
        assert(Date(9, 12, 4).toSimpleString() == "0009-Dec-04");
        assert(Date(99, 12, 4).toSimpleString() == "0099-Dec-04");
        assert(Date(999, 12, 4).toSimpleString() == "0999-Dec-04");
        assert(Date(9999, 7, 4).toSimpleString() == "9999-Jul-04");
        assert(Date(10000, 10, 20).toSimpleString() == "+10000-Oct-20");

        // Test B.C.
        assert(Date(0, 12, 4).toSimpleString() == "0000-Dec-04");
        assert(Date(-9, 12, 4).toSimpleString() == "-0009-Dec-04");
        assert(Date(-99, 12, 4).toSimpleString() == "-0099-Dec-04");
        assert(Date(-999, 12, 4).toSimpleString() == "-0999-Dec-04");
        assert(Date(-9999, 7, 4).toSimpleString() == "-9999-Jul-04");
        assert(Date(-10000, 10, 20).toSimpleString() == "-10000-Oct-20");

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(cdate.toSimpleString() == "1999-Jul-06");
        assert(idate.toSimpleString() == "1999-Jul-06");
    }

    /// ditto
    void toSimpleString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        if (_year >= 0)
        {
            if (_year < 10_000)
                formattedWrite(writer, "%04d-%s-%02d", _year, monthToString(_month), _day);
            else
                formattedWrite(writer, "+%05d-%s-%02d", _year, monthToString(_month), _day);
        }
        else if (_year > -10_000)
            formattedWrite(writer, "%05d-%s-%02d", _year, monthToString(_month), _day);
        else
            formattedWrite(writer, "%06d-%s-%02d", _year, monthToString(_month), _day);
    }

    @safe pure unittest
    {
        import std.array : appender;

        auto w = appender!(char[])();
        Date(9, 12, 4).toSimpleString(w);
        assert(w.data == "0009-Dec-04");
        w.clear();
        Date(-10000, 10, 20).toSimpleString(w);
        assert(w.data == "-10000-Oct-20");
    }

    /++
        Converts this $(LREF Date) to a string.

        This function exists to make it easy to convert a $(LREF Date) to a
        string for code that does not care what the exact format is - just that
        it presents the information in a clear manner. It also makes it easy to
        simply convert a $(LREF Date) to a string when using functions such as
        `to!string`, `format`, or `writeln` which use toString to convert
        user-defined types. So, it is unlikely that much code will call
        toString directly.

        The format of the string is purposefully unspecified, and code that
        cares about the format of the string should use `toISOString`,
        `toISOExtString`, `toSimpleString`, or some other custom formatting
        function that explicitly generates the format that the code needs. The
        reason is that the code is then clear about what format it's using,
        making it less error-prone to maintain the code and interact with other
        software that consumes the generated strings. It's for this same reason
        $(LREF Date) has no `fromString` function, whereas it does have
        `fromISOString`, `fromISOExtString`, and `fromSimpleString`.

        The format returned by toString may or may not change in the future.
      +/
    string toString() const @safe pure nothrow
    {
        return toSimpleString();
    }

    @safe unittest
    {
        auto date = Date(1999, 7, 6);
        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        assert(date.toString());
        assert(cdate.toString());
        assert(idate.toString());
    }

    /// ditto
    void toString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        toSimpleString(writer);
    }

    /++
        Creates a $(LREF Date) from a string with the format YYYYMMDD. Whitespace
        is stripped from the given string.

        Params:
            isoString = A string formatted in the ISO format for dates.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO format or if the resulting $(LREF Date) would not be
            valid.
      +/
    static Date fromISOString(S)(scope const S isoString) @safe pure
        if (isSomeString!S)
    {
        import std.algorithm.searching : startsWith;
        import std.conv : to, text, ConvException;
        import std.exception : enforce;
        import std.string : strip;

        auto str = isoString.strip;

        enforce!DateTimeException(str.length >= 8, text("Invalid ISO String: ", isoString));

        int day, month, year;
        auto yearStr = str[0 .. $ - 4];

        try
        {
            // using conversion to uint plus cast because it checks for +/-
            // for us quickly while throwing ConvException
            day = cast(int) to!uint(str[$ - 2 .. $]);
            month = cast(int) to!uint(str[$ - 4 .. $ - 2]);

            if (yearStr.length > 4)
            {
                enforce!DateTimeException(yearStr.startsWith('-', '+'),
                        text("Invalid ISO String: ", isoString));
                year = to!int(yearStr);
            }
            else
            {
                year = cast(int) to!uint(yearStr);
            }
        }
        catch (ConvException)
        {
            throw new DateTimeException(text("Invalid ISO String: ", isoString));
        }

        return Date(year, month, day);
    }

    ///
    @safe unittest
    {
        assert(Date.fromISOString("20100704") == Date(2010, 7, 4));
        assert(Date.fromISOString("19981225") == Date(1998, 12, 25));
        assert(Date.fromISOString("00000105") == Date(0, 1, 5));
        assert(Date.fromISOString("-00040105") == Date(-4, 1, 5));
        assert(Date.fromISOString(" 20100704 ") == Date(2010, 7, 4));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(Date.fromISOString(""));
        assertThrown!DateTimeException(Date.fromISOString("990704"));
        assertThrown!DateTimeException(Date.fromISOString("0100704"));
        assertThrown!DateTimeException(Date.fromISOString("2010070"));
        assertThrown!DateTimeException(Date.fromISOString("2010070 "));
        assertThrown!DateTimeException(Date.fromISOString("120100704"));
        assertThrown!DateTimeException(Date.fromISOString("-0100704"));
        assertThrown!DateTimeException(Date.fromISOString("+0100704"));
        assertThrown!DateTimeException(Date.fromISOString("2010070a"));
        assertThrown!DateTimeException(Date.fromISOString("20100a04"));
        assertThrown!DateTimeException(Date.fromISOString("2010a704"));

        assertThrown!DateTimeException(Date.fromISOString("99-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("010-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-07-0"));
        assertThrown!DateTimeException(Date.fromISOString("2010-07-0 "));
        assertThrown!DateTimeException(Date.fromISOString("12010-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("-010-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("+010-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-07-0a"));
        assertThrown!DateTimeException(Date.fromISOString("2010-0a-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-a7-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010/07/04"));
        assertThrown!DateTimeException(Date.fromISOString("2010/7/04"));
        assertThrown!DateTimeException(Date.fromISOString("2010/7/4"));
        assertThrown!DateTimeException(Date.fromISOString("2010/07/4"));
        assertThrown!DateTimeException(Date.fromISOString("2010-7-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-7-4"));
        assertThrown!DateTimeException(Date.fromISOString("2010-07-4"));

        assertThrown!DateTimeException(Date.fromISOString("99Jul04"));
        assertThrown!DateTimeException(Date.fromISOString("010Jul04"));
        assertThrown!DateTimeException(Date.fromISOString("2010Jul0"));
        assertThrown!DateTimeException(Date.fromISOString("2010Jul0 "));
        assertThrown!DateTimeException(Date.fromISOString("12010Jul04"));
        assertThrown!DateTimeException(Date.fromISOString("-010Jul04"));
        assertThrown!DateTimeException(Date.fromISOString("+010Jul04"));
        assertThrown!DateTimeException(Date.fromISOString("2010Jul0a"));
        assertThrown!DateTimeException(Date.fromISOString("2010Jua04"));
        assertThrown!DateTimeException(Date.fromISOString("2010aul04"));

        assertThrown!DateTimeException(Date.fromISOString("99-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOString("010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0 "));
        assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-aul-04"));

        assertThrown!DateTimeException(Date.fromISOString("2010-07-04"));
        assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04"));

        assert(Date.fromISOString("19990706") == Date(1999, 7, 6));
        assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6));
        assert(Date.fromISOString("+019990706") == Date(1999, 7, 6));
        assert(Date.fromISOString("19990706 ") == Date(1999, 7, 6));
        assert(Date.fromISOString(" 19990706") == Date(1999, 7, 6));
        assert(Date.fromISOString(" 19990706 ") == Date(1999, 7, 6));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21));
        }
    }


    /++
        Creates a $(LREF Date) from a string with the format YYYY-MM-DD.
        Whitespace is stripped from the given string.

        Params:
            isoExtString = A string formatted in the ISO Extended format for
                           dates.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO Extended format or if the resulting $(LREF Date)
            would not be valid.
      +/
    static Date fromISOExtString(S)(scope const S isoExtString) @safe pure
        if (isSomeString!(S))
    {
        import std.algorithm.searching : startsWith;
        import std.conv : to, ConvException;
        import std.format : format;
        import std.string : strip;

        auto str = strip(isoExtString);
        short year;
        ubyte month, day;

        if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-')
            throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString));

        auto yearStr = str[0 .. $-6];
        auto signAtBegining = cast(bool) yearStr.startsWith('-', '+');
        if ((yearStr.length > 4) != signAtBegining)
        {
            throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString));
        }

        try
        {
            day = to!ubyte(str[$-2 .. $]);
            month = to!ubyte(str[$-5 .. $-3]);
            year = to!short(yearStr);
        }
        catch (ConvException)
        {
            throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString));
        }

        return Date(year, month, day);
    }

    ///
    @safe unittest
    {
        assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4));
        assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25));
        assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5));
        assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5));
        assert(Date.fromISOExtString(" 2010-07-04 ") == Date(2010, 7, 4));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(Date.fromISOExtString(""));
        assertThrown!DateTimeException(Date.fromISOExtString("990704"));
        assertThrown!DateTimeException(Date.fromISOExtString("0100704"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010070"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010070 "));
        assertThrown!DateTimeException(Date.fromISOExtString("120100704"));
        assertThrown!DateTimeException(Date.fromISOExtString("-0100704"));
        assertThrown!DateTimeException(Date.fromISOExtString("+0100704"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010070a"));
        assertThrown!DateTimeException(Date.fromISOExtString("20100a04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010a704"));

        assertThrown!DateTimeException(Date.fromISOExtString("99-07-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("010-07-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0 "));
        assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4"));

        assertThrown!DateTimeException(Date.fromISOExtString("99Jul04"));
        assertThrown!DateTimeException(Date.fromISOExtString("010Jul04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0 "));
        assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04"));
        assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04"));
        assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010aul04"));

        assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0 "));
        assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04"));

        assertThrown!DateTimeException(Date.fromISOExtString("20100704"));
        assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04"));

        assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6));
        assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6));
        assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6));
        assert(Date.fromISOExtString("1999-07-06 ") == Date(1999, 7, 6));
        assert(Date.fromISOExtString(" 1999-07-06") == Date(1999, 7, 6));
        assert(Date.fromISOExtString(" 1999-07-06 ") == Date(1999, 7, 6));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21));
        }
    }


    /++
        Creates a $(LREF Date) from a string with the format YYYY-Mon-DD.
        Whitespace is stripped from the given string.

        Params:
            simpleString = A string formatted in the way that toSimpleString
                           formats dates.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the correct format or if the resulting $(LREF Date) would not
            be valid.
      +/
    static Date fromSimpleString(S)(scope const S simpleString) @safe pure
        if (isSomeString!(S))
    {
        import std.algorithm.searching : startsWith;
        import std.conv : to, ConvException;
        import std.format : format;
        import std.string : strip;

        auto str = strip(simpleString);

        if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-')
            throw new DateTimeException(format!"Invalid string format: %s"(simpleString));

        int year;
        uint day;
        auto month = monthFromString(str[$ - 6 .. $ - 3]);
        auto yearStr = str[0 .. $ - 7];
        auto signAtBegining = cast(bool) yearStr.startsWith('-', '+');
        if ((yearStr.length > 4) != signAtBegining)
        {
            throw new DateTimeException(format!"Invalid string format: %s"(simpleString));
        }

        try
        {
            day = to!uint(str[$ - 2 .. $]);
            year = to!int(yearStr);
        }
        catch (ConvException)
        {
            throw new DateTimeException(format!"Invalid string format: %s"(simpleString));
        }

        return Date(year, month, day);
    }

    ///
    @safe unittest
    {
        assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4));
        assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25));
        assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5));
        assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5));
        assert(Date.fromSimpleString(" 2010-Jul-04 ") == Date(2010, 7, 4));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(Date.fromSimpleString(""));
        assertThrown!DateTimeException(Date.fromSimpleString("990704"));
        assertThrown!DateTimeException(Date.fromSimpleString("0100704"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010070"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010070 "));
        assertThrown!DateTimeException(Date.fromSimpleString("120100704"));
        assertThrown!DateTimeException(Date.fromSimpleString("-0100704"));
        assertThrown!DateTimeException(Date.fromSimpleString("+0100704"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010070a"));
        assertThrown!DateTimeException(Date.fromSimpleString("20100a04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010a704"));

        assertThrown!DateTimeException(Date.fromSimpleString("99-07-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("010-07-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0 "));
        assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4"));

        assertThrown!DateTimeException(Date.fromSimpleString("99Jul04"));
        assertThrown!DateTimeException(Date.fromSimpleString("010Jul04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0 "));
        assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04"));
        assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04"));
        assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010aul04"));

        assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0 "));
        assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04"));

        assertThrown!DateTimeException(Date.fromSimpleString("20100704"));
        assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04"));

        assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6));
        assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6));
        assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6));
        assert(Date.fromSimpleString("1999-Jul-06 ") == Date(1999, 7, 6));
        assert(Date.fromSimpleString(" 1999-Jul-06") == Date(1999, 7, 6));
        assert(Date.fromSimpleString(" 1999-Jul-06 ") == Date(1999, 7, 6));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21));
        }
    }


    /++
        Returns the $(LREF Date) farthest in the past which is representable by
        $(LREF Date).
      +/
    @property static Date min() @safe pure nothrow @nogc
    {
        auto date = Date.init;
        date._year = short.min;
        date._month = Month.jan;
        date._day = 1;

        return date;
    }

    @safe unittest
    {
        assert(Date.min.year < 0);
        assert(Date.min < Date.max);
    }


    /++
        Returns the $(LREF Date) farthest in the future which is representable
        by $(LREF Date).
      +/
    @property static Date max() @safe pure nothrow @nogc
    {
        auto date = Date.init;
        date._year = short.max;
        date._month = Month.dec;
        date._day = 31;

        return date;
    }

    @safe unittest
    {
        assert(Date.max.year > 0);
        assert(Date.max > Date.min);
    }


private:

    /+
        Whether the given values form a valid date.

        Params:
            year  = The year to test.
            month = The month of the Gregorian Calendar to test.
            day   = The day of the month to test.
     +/
    static bool _valid(int year, int month, int day) @safe pure nothrow @nogc
    {
        if (!valid!"months"(month))
            return false;
        return valid!"days"(year, month, day);
    }


package:

    /+
        Adds the given number of days to this $(LREF Date). A negative number
        will subtract.

        The month will be adjusted along with the day if the number of days
        added (or subtracted) would overflow (or underflow) the current month.
        The year will be adjusted along with the month if the increase (or
        decrease) to the month would cause it to overflow (or underflow) the
        current year.

        `_addDays(numDays)` is effectively equivalent to
        $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days).

        Params:
            days = The number of days to add to this Date.
      +/
    ref Date _addDays(long days) return @safe pure nothrow @nogc
    {
        dayOfGregorianCal = cast(int)(dayOfGregorianCal + days);
        return this;
    }

    @safe unittest
    {
        // Test A.D.
        {
            auto date = Date(1999, 2, 28);
            date._addDays(1);
            assert(date == Date(1999, 3, 1));
            date._addDays(-1);
            assert(date == Date(1999, 2, 28));
        }

        {
            auto date = Date(2000, 2, 28);
            date._addDays(1);
            assert(date == Date(2000, 2, 29));
            date._addDays(1);
            assert(date == Date(2000, 3, 1));
            date._addDays(-1);
            assert(date == Date(2000, 2, 29));
        }

        {
            auto date = Date(1999, 6, 30);
            date._addDays(1);
            assert(date == Date(1999, 7, 1));
            date._addDays(-1);
            assert(date == Date(1999, 6, 30));
        }

        {
            auto date = Date(1999, 7, 31);
            date._addDays(1);
            assert(date == Date(1999, 8, 1));
            date._addDays(-1);
            assert(date == Date(1999, 7, 31));
        }

        {
            auto date = Date(1999, 1, 1);
            date._addDays(-1);
            assert(date == Date(1998, 12, 31));
            date._addDays(1);
            assert(date == Date(1999, 1, 1));
        }

        {
            auto date = Date(1999, 7, 6);
            date._addDays(9);
            assert(date == Date(1999, 7, 15));
            date._addDays(-11);
            assert(date == Date(1999, 7, 4));
            date._addDays(30);
            assert(date == Date(1999, 8, 3));
            date._addDays(-3);
            assert(date == Date(1999, 7, 31));
        }

        {
            auto date = Date(1999, 7, 6);
            date._addDays(365);
            assert(date == Date(2000, 7, 5));
            date._addDays(-365);
            assert(date == Date(1999, 7, 6));
            date._addDays(366);
            assert(date == Date(2000, 7, 6));
            date._addDays(730);
            assert(date == Date(2002, 7, 6));
            date._addDays(-1096);
            assert(date == Date(1999, 7, 6));
        }

        // Test B.C.
        {
            auto date = Date(-1999, 2, 28);
            date._addDays(1);
            assert(date == Date(-1999, 3, 1));
            date._addDays(-1);
            assert(date == Date(-1999, 2, 28));
        }

        {
            auto date = Date(-2000, 2, 28);
            date._addDays(1);
            assert(date == Date(-2000, 2, 29));
            date._addDays(1);
            assert(date == Date(-2000, 3, 1));
            date._addDays(-1);
            assert(date == Date(-2000, 2, 29));
        }

        {
            auto date = Date(-1999, 6, 30);
            date._addDays(1);
            assert(date == Date(-1999, 7, 1));
            date._addDays(-1);
            assert(date == Date(-1999, 6, 30));
        }

        {
            auto date = Date(-1999, 7, 31);
            date._addDays(1);
            assert(date == Date(-1999, 8, 1));
            date._addDays(-1);
            assert(date == Date(-1999, 7, 31));
        }

        {
            auto date = Date(-1999, 1, 1);
            date._addDays(-1);
            assert(date == Date(-2000, 12, 31));
            date._addDays(1);
            assert(date == Date(-1999, 1, 1));
        }

        {
            auto date = Date(-1999, 7, 6);
            date._addDays(9);
            assert(date == Date(-1999, 7, 15));
            date._addDays(-11);
            assert(date == Date(-1999, 7, 4));
            date._addDays(30);
            assert(date == Date(-1999, 8, 3));
            date._addDays(-3);
        }

        {
            auto date = Date(-1999, 7, 6);
            date._addDays(365);
            assert(date == Date(-1998, 7, 6));
            date._addDays(-365);
            assert(date == Date(-1999, 7, 6));
            date._addDays(366);
            assert(date == Date(-1998, 7, 7));
            date._addDays(730);
            assert(date == Date(-1996, 7, 6));
            date._addDays(-1096);
            assert(date == Date(-1999, 7, 6));
        }

        // Test Both
        {
            auto date = Date(1, 7, 6);
            date._addDays(-365);
            assert(date == Date(0, 7, 6));
            date._addDays(365);
            assert(date == Date(1, 7, 6));
            date._addDays(-731);
            assert(date == Date(-1, 7, 6));
            date._addDays(730);
            assert(date == Date(1, 7, 5));
        }

        const cdate = Date(1999, 7, 6);
        immutable idate = Date(1999, 7, 6);
        static assert(!__traits(compiles, cdate._addDays(12)));
        static assert(!__traits(compiles, idate._addDays(12)));
    }


    @safe pure invariant()
    {
        import std.format : format;
        assert(valid!"months"(_month),
               format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day));
        assert(valid!"days"(_year, _month, _day),
               format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day));
    }

    short _year  = 1;
    Month _month = Month.jan;
    ubyte _day   = 1;
}

///
@safe pure unittest
{
    import core.time : days;

    auto d = Date(2000, 6, 1);

    assert(d.dayOfYear == 153);
    assert(d.dayOfWeek == DayOfWeek.thu);

    d += 10.days;
    assert(d == Date(2000, 6, 11));

    assert(d.toISOExtString() == "2000-06-11");
    assert(d.toISOString() == "20000611");
    assert(d.toSimpleString() == "2000-Jun-11");

    assert(Date.fromISOExtString("2018-01-01") == Date(2018, 1, 1));
    assert(Date.fromISOString("20180101") == Date(2018, 1, 1));
    assert(Date.fromSimpleString("2018-Jan-01") == Date(2018, 1, 1));
}


/++
    Represents a time of day with hours, minutes, and seconds. It uses 24 hour
    time.
+/
struct TimeOfDay
{
public:

    /++
        Params:
            hour   = Hour of the day [0 - 24$(RPAREN).
            minute = Minute of the hour [0 - 60$(RPAREN).
            second = Second of the minute [0 - 60$(RPAREN).

        Throws:
            $(REF DateTimeException,std,datetime,date) if the resulting
            $(LREF TimeOfDay) would be not be valid.
     +/
    this(int hour, int minute, int second = 0) @safe pure
    {
        enforceValid!"hours"(hour);
        enforceValid!"minutes"(minute);
        enforceValid!"seconds"(second);

        _hour   = cast(ubyte) hour;
        _minute = cast(ubyte) minute;
        _second = cast(ubyte) second;
    }

    @safe unittest
    {
        assert(TimeOfDay(0, 0) == TimeOfDay.init);

        {
            auto tod = TimeOfDay(0, 0);
            assert(tod._hour == 0);
            assert(tod._minute == 0);
            assert(tod._second == 0);
        }

        {
            auto tod = TimeOfDay(12, 30, 33);
            assert(tod._hour == 12);
            assert(tod._minute == 30);
            assert(tod._second == 33);
        }

        {
            auto tod = TimeOfDay(23, 59, 59);
            assert(tod._hour == 23);
            assert(tod._minute == 59);
            assert(tod._second == 59);
        }

        assertThrown!DateTimeException(TimeOfDay(24, 0, 0));
        assertThrown!DateTimeException(TimeOfDay(0, 60, 0));
        assertThrown!DateTimeException(TimeOfDay(0, 0, 60));
    }


    /++
        Compares this $(LREF TimeOfDay) with the given $(LREF TimeOfDay).

        Returns:
            $(BOOKTABLE,
            $(TR $(TD this &lt; rhs) $(TD &lt; 0))
            $(TR $(TD this == rhs) $(TD 0))
            $(TR $(TD this &gt; rhs) $(TD &gt; 0))
            )
     +/
    int opCmp(TimeOfDay rhs) const @safe pure nothrow @nogc
    {
        if (_hour < rhs._hour)
            return -1;
        if (_hour > rhs._hour)
            return 1;

        if (_minute < rhs._minute)
            return -1;
        if (_minute > rhs._minute)
            return 1;

        if (_second < rhs._second)
            return -1;
        if (_second > rhs._second)
            return 1;

        return 0;
    }

    @safe unittest
    {
        assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay.init) == 0);

        assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay(0, 0, 0)) == 0);
        assert(TimeOfDay(12, 0, 0).opCmp(TimeOfDay(12, 0, 0)) == 0);
        assert(TimeOfDay(0, 30, 0).opCmp(TimeOfDay(0, 30, 0)) == 0);
        assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0);

        assert(TimeOfDay(12, 30, 0).opCmp(TimeOfDay(12, 30, 0)) == 0);
        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 33)) == 0);

        assert(TimeOfDay(0, 30, 33).opCmp(TimeOfDay(0, 30, 33)) == 0);
        assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0);

        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(13, 30, 33)) < 0);
        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 33)) > 0);
        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 31, 33)) < 0);
        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 33)) > 0);
        assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 34)) < 0);
        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 30, 33)) > 0);

        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 34)) > 0);
        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(13, 30, 33)) < 0);
        assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 31, 33)) > 0);
        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(13, 30, 33)) < 0);

        assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 34)) > 0);
        assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 31, 33)) < 0);

        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(ctod.opCmp(itod) == 0);
        assert(itod.opCmp(ctod) == 0);
    }


    /++
        Hours past midnight.
     +/
    @property ubyte hour() const @safe pure nothrow @nogc
    {
        return _hour;
    }

    @safe unittest
    {
        assert(TimeOfDay.init.hour == 0);
        assert(TimeOfDay(12, 0, 0).hour == 12);

        const ctod = TimeOfDay(12, 0, 0);
        immutable itod = TimeOfDay(12, 0, 0);
        assert(ctod.hour == 12);
        assert(itod.hour == 12);
    }


    /++
        Hours past midnight.

        Params:
            hour = The hour of the day to set this $(LREF TimeOfDay)'s hour to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given hour would
            result in an invalid $(LREF TimeOfDay).
     +/
    @property void hour(int hour) @safe pure
    {
        enforceValid!"hours"(hour);
        _hour = cast(ubyte) hour;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).hour = 24;}());

        auto tod = TimeOfDay(0, 0, 0);
        tod.hour = 12;
        assert(tod == TimeOfDay(12, 0, 0));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.hour = 12));
        static assert(!__traits(compiles, itod.hour = 12));
    }


    /++
        Minutes past the hour.
     +/
    @property ubyte minute() const @safe pure nothrow @nogc
    {
        return _minute;
    }

    @safe unittest
    {
        assert(TimeOfDay.init.minute == 0);
        assert(TimeOfDay(0, 30, 0).minute == 30);

        const ctod = TimeOfDay(0, 30, 0);
        immutable itod = TimeOfDay(0, 30, 0);
        assert(ctod.minute == 30);
        assert(itod.minute == 30);
    }


    /++
        Minutes past the hour.

        Params:
            minute = The minute to set this $(LREF TimeOfDay)'s minute to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given minute
            would result in an invalid $(LREF TimeOfDay).
     +/
    @property void minute(int minute) @safe pure
    {
        enforceValid!"minutes"(minute);
        _minute = cast(ubyte) minute;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).minute = 60;}());

        auto tod = TimeOfDay(0, 0, 0);
        tod.minute = 30;
        assert(tod == TimeOfDay(0, 30, 0));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.minute = 30));
        static assert(!__traits(compiles, itod.minute = 30));
    }


    /++
        Seconds past the minute.
     +/
    @property ubyte second() const @safe pure nothrow @nogc
    {
        return _second;
    }

    @safe unittest
    {
        assert(TimeOfDay.init.second == 0);
        assert(TimeOfDay(0, 0, 33).second == 33);

        const ctod = TimeOfDay(0, 0, 33);
        immutable itod = TimeOfDay(0, 0, 33);
        assert(ctod.second == 33);
        assert(itod.second == 33);
    }


    /++
        Seconds past the minute.

        Params:
            second = The second to set this $(LREF TimeOfDay)'s second to.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given second
            would result in an invalid $(LREF TimeOfDay).
     +/
    @property void second(int second) @safe pure
    {
        enforceValid!"seconds"(second);
        _second = cast(ubyte) second;
    }

    @safe unittest
    {
        assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).second = 60;}());

        auto tod = TimeOfDay(0, 0, 0);
        tod.second = 33;
        assert(tod == TimeOfDay(0, 0, 33));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.second = 33));
        static assert(!__traits(compiles, itod.second = 33));
    }


    /++
        Adds the given number of units to this $(LREF TimeOfDay). A negative
        number will subtract.

        The difference between rolling and adding is that rolling does not
        affect larger units. For instance, rolling a $(LREF TimeOfDay)
        one hours's worth of minutes gets the exact same
        $(LREF TimeOfDay).

        Accepted units are `"hours"`, `"minutes"`, and `"seconds"`.

        Params:
            units = The units to add.
            value = The number of $(D_PARAM units) to add to this
                    $(LREF TimeOfDay).
      +/
    ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc
        if (units == "hours")
    {
        import core.time : dur;
        return this += dur!"hours"(value);
    }

    ///
    @safe unittest
    {
        auto tod1 = TimeOfDay(7, 12, 0);
        tod1.roll!"hours"(1);
        assert(tod1 == TimeOfDay(8, 12, 0));

        auto tod2 = TimeOfDay(7, 12, 0);
        tod2.roll!"hours"(-1);
        assert(tod2 == TimeOfDay(6, 12, 0));

        auto tod3 = TimeOfDay(23, 59, 0);
        tod3.roll!"minutes"(1);
        assert(tod3 == TimeOfDay(23, 0, 0));

        auto tod4 = TimeOfDay(0, 0, 0);
        tod4.roll!"minutes"(-1);
        assert(tod4 == TimeOfDay(0, 59, 0));

        auto tod5 = TimeOfDay(23, 59, 59);
        tod5.roll!"seconds"(1);
        assert(tod5 == TimeOfDay(23, 59, 0));

        auto tod6 = TimeOfDay(0, 0, 0);
        tod6.roll!"seconds"(-1);
        assert(tod6 == TimeOfDay(0, 0, 59));
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 27, 2);
        tod.roll!"hours"(22).roll!"hours"(-7);
        assert(tod == TimeOfDay(3, 27, 2));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.roll!"hours"(53)));
        static assert(!__traits(compiles, itod.roll!"hours"(53)));
    }


    /// ditto
    ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc
        if (units == "minutes" || units == "seconds")
    {
        import std.format : format;

        enum memberVarStr = units[0 .. $ - 1];
        value %= 60;
        mixin(format("auto newVal = cast(ubyte)(_%s) + value;", memberVarStr));

        if (value < 0)
        {
            if (newVal < 0)
                newVal += 60;
        }
        else if (newVal >= 60)
            newVal -= 60;

        mixin(format("_%s = cast(ubyte) newVal;", memberVarStr));
        return this;
    }

    // Test roll!"minutes"().
    @safe unittest
    {
        static void testTOD(TimeOfDay orig, int minutes, TimeOfDay expected, size_t line = __LINE__)
        {
            orig.roll!"minutes"(minutes);
            assert(orig == expected);
        }

        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 31, 33));
        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 32, 33));
        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 33, 33));
        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 34, 33));
        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 35, 33));
        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 40, 33));
        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 45, 33));
        testTOD(TimeOfDay(12, 30, 33), 29, TimeOfDay(12, 59, 33));
        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), 45, TimeOfDay(12, 15, 33));
        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 75, TimeOfDay(12, 45, 33));
        testTOD(TimeOfDay(12, 30, 33), 90, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), 100, TimeOfDay(12, 10, 33));

        testTOD(TimeOfDay(12, 30, 33), 689, TimeOfDay(12, 59, 33));
        testTOD(TimeOfDay(12, 30, 33), 690, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), 691, TimeOfDay(12, 1, 33));
        testTOD(TimeOfDay(12, 30, 33), 960, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 1439, TimeOfDay(12, 29, 33));
        testTOD(TimeOfDay(12, 30, 33), 1440, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 1441, TimeOfDay(12, 31, 33));
        testTOD(TimeOfDay(12, 30, 33), 2880, TimeOfDay(12, 30, 33));

        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 29, 33));
        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 28, 33));
        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 27, 33));
        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 26, 33));
        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 25, 33));
        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 20, 33));
        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 15, 33));
        testTOD(TimeOfDay(12, 30, 33), -29, TimeOfDay(12, 1, 33));
        testTOD(TimeOfDay(12, 30, 33), -30, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), -45, TimeOfDay(12, 45, 33));
        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), -75, TimeOfDay(12, 15, 33));
        testTOD(TimeOfDay(12, 30, 33), -90, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), -100, TimeOfDay(12, 50, 33));

        testTOD(TimeOfDay(12, 30, 33), -749, TimeOfDay(12, 1, 33));
        testTOD(TimeOfDay(12, 30, 33), -750, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 30, 33), -751, TimeOfDay(12, 59, 33));
        testTOD(TimeOfDay(12, 30, 33), -960, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), -1439, TimeOfDay(12, 31, 33));
        testTOD(TimeOfDay(12, 30, 33), -1440, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), -1441, TimeOfDay(12, 29, 33));
        testTOD(TimeOfDay(12, 30, 33), -2880, TimeOfDay(12, 30, 33));

        testTOD(TimeOfDay(12, 0, 33), 1, TimeOfDay(12, 1, 33));
        testTOD(TimeOfDay(12, 0, 33), 0, TimeOfDay(12, 0, 33));
        testTOD(TimeOfDay(12, 0, 33), -1, TimeOfDay(12, 59, 33));

        testTOD(TimeOfDay(11, 59, 33), 1, TimeOfDay(11, 0, 33));
        testTOD(TimeOfDay(11, 59, 33), 0, TimeOfDay(11, 59, 33));
        testTOD(TimeOfDay(11, 59, 33), -1, TimeOfDay(11, 58, 33));

        testTOD(TimeOfDay(0, 0, 33), 1, TimeOfDay(0, 1, 33));
        testTOD(TimeOfDay(0, 0, 33), 0, TimeOfDay(0, 0, 33));
        testTOD(TimeOfDay(0, 0, 33), -1, TimeOfDay(0, 59, 33));

        testTOD(TimeOfDay(23, 59, 33), 1, TimeOfDay(23, 0, 33));
        testTOD(TimeOfDay(23, 59, 33), 0, TimeOfDay(23, 59, 33));
        testTOD(TimeOfDay(23, 59, 33), -1, TimeOfDay(23, 58, 33));

        auto tod = TimeOfDay(12, 27, 2);
        tod.roll!"minutes"(97).roll!"minutes"(-102);
        assert(tod == TimeOfDay(12, 22, 2));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.roll!"minutes"(7)));
        static assert(!__traits(compiles, itod.roll!"minutes"(7)));
    }

    // Test roll!"seconds"().
    @safe unittest
    {
        static void testTOD(TimeOfDay orig, int seconds, TimeOfDay expected, size_t line = __LINE__)
        {
            orig.roll!"seconds"(seconds);
            assert(orig == expected);
        }

        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34));
        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35));
        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36));
        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37));
        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38));
        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43));
        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48));
        testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59));
        testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 30, 3));
        testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 30, 34));

        testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 30, 59));
        testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(12, 30, 1));
        testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(12, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(12, 30, 34));
        testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(12, 30, 33));

        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31));
        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30));
        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29));
        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28));
        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23));
        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18));
        testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 30, 59));
        testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 30, 58));
        testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 30, 34));
        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 30, 32));

        testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1));
        testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 30, 59));

        testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1));
        testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0));
        testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(12, 0, 59));

        testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1));
        testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0));
        testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(0, 0, 59));

        testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(23, 59, 0));
        testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59));
        testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58));

        auto tod = TimeOfDay(12, 27, 2);
        tod.roll!"seconds"(105).roll!"seconds"(-77);
        assert(tod == TimeOfDay(12, 27, 30));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod.roll!"seconds"(7)));
        static assert(!__traits(compiles, itod.roll!"seconds"(7)));
    }


    import core.time : Duration;
    /++
        Gives the result of adding or subtracting a $(REF Duration, core,time)
        from this $(LREF TimeOfDay).

        The legal types of arithmetic for $(LREF TimeOfDay) using this operator
        are

        $(BOOKTABLE,
        $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay))
        $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay))
        )

        Params:
            duration = The $(REF Duration, core,time) to add to or subtract from
                       this $(LREF TimeOfDay).
      +/
    TimeOfDay opBinary(string op)(Duration duration) const @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        TimeOfDay retval = this;
        immutable seconds = duration.total!"seconds";
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    ///
    @safe unittest
    {
        import core.time : hours, minutes, seconds;

        assert(TimeOfDay(12, 12, 12) + seconds(1) == TimeOfDay(12, 12, 13));
        assert(TimeOfDay(12, 12, 12) + minutes(1) == TimeOfDay(12, 13, 12));
        assert(TimeOfDay(12, 12, 12) + hours(1) == TimeOfDay(13, 12, 12));
        assert(TimeOfDay(23, 59, 59) + seconds(1) == TimeOfDay(0, 0, 0));

        assert(TimeOfDay(12, 12, 12) - seconds(1) == TimeOfDay(12, 12, 11));
        assert(TimeOfDay(12, 12, 12) - minutes(1) == TimeOfDay(12, 11, 12));
        assert(TimeOfDay(12, 12, 12) - hours(1) == TimeOfDay(11, 12, 12));
        assert(TimeOfDay(0, 0, 0) - seconds(1) == TimeOfDay(23, 59, 59));
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 30, 33);

        import core.time : dur;
        assert(tod + dur!"hours"(7) == TimeOfDay(19, 30, 33));
        assert(tod + dur!"hours"(-7) == TimeOfDay(5, 30, 33));
        assert(tod + dur!"minutes"(7) == TimeOfDay(12, 37, 33));
        assert(tod + dur!"minutes"(-7) == TimeOfDay(12, 23, 33));
        assert(tod + dur!"seconds"(7) == TimeOfDay(12, 30, 40));
        assert(tod + dur!"seconds"(-7) == TimeOfDay(12, 30, 26));

        assert(tod + dur!"msecs"(7000) == TimeOfDay(12, 30, 40));
        assert(tod + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26));
        assert(tod + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
        assert(tod + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));
        assert(tod + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40));
        assert(tod + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26));

        assert(tod - dur!"hours"(-7) == TimeOfDay(19, 30, 33));
        assert(tod - dur!"hours"(7) == TimeOfDay(5, 30, 33));
        assert(tod - dur!"minutes"(-7) == TimeOfDay(12, 37, 33));
        assert(tod - dur!"minutes"(7) == TimeOfDay(12, 23, 33));
        assert(tod - dur!"seconds"(-7) == TimeOfDay(12, 30, 40));
        assert(tod - dur!"seconds"(7) == TimeOfDay(12, 30, 26));

        assert(tod - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40));
        assert(tod - dur!"msecs"(7000) == TimeOfDay(12, 30, 26));
        assert(tod - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
        assert(tod - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
        assert(tod - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40));
        assert(tod - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26));

        auto duration = dur!"hours"(11);
        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(tod + duration == TimeOfDay(23, 30, 33));
        assert(ctod + duration == TimeOfDay(23, 30, 33));
        assert(itod + duration == TimeOfDay(23, 30, 33));

        assert(tod - duration == TimeOfDay(1, 30, 33));
        assert(ctod - duration == TimeOfDay(1, 30, 33));
        assert(itod - duration == TimeOfDay(1, 30, 33));
    }


    /++
        Gives the result of adding or subtracting a $(REF Duration, core,time)
        from this $(LREF TimeOfDay), as well as assigning the result to this
        $(LREF TimeOfDay).

        The legal types of arithmetic for $(LREF TimeOfDay) using this operator
        are

        $(BOOKTABLE,
        $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay))
        $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay))
        )

        Params:
            duration = The $(REF Duration, core,time) to add to or subtract from
                       this $(LREF TimeOfDay).
      +/
    ref TimeOfDay opOpAssign(string op)(Duration duration) @safe pure nothrow @nogc
        if (op == "+" || op == "-")
    {
        immutable seconds = duration.total!"seconds";
        mixin("return _addSeconds(" ~ op ~ "seconds);");
    }

    @safe unittest
    {
        import core.time : dur;
        auto duration = dur!"hours"(12);

        assert(TimeOfDay(12, 30, 33) + dur!"hours"(7) == TimeOfDay(19, 30, 33));
        assert(TimeOfDay(12, 30, 33) + dur!"hours"(-7) == TimeOfDay(5, 30, 33));
        assert(TimeOfDay(12, 30, 33) + dur!"minutes"(7) == TimeOfDay(12, 37, 33));
        assert(TimeOfDay(12, 30, 33) + dur!"minutes"(-7) == TimeOfDay(12, 23, 33));
        assert(TimeOfDay(12, 30, 33) + dur!"seconds"(7) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) + dur!"seconds"(-7) == TimeOfDay(12, 30, 26));

        assert(TimeOfDay(12, 30, 33) + dur!"msecs"(7000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26));
        assert(TimeOfDay(12, 30, 33) + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));
        assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26));

        assert(TimeOfDay(12, 30, 33) - dur!"hours"(-7) == TimeOfDay(19, 30, 33));
        assert(TimeOfDay(12, 30, 33) - dur!"hours"(7) == TimeOfDay(5, 30, 33));
        assert(TimeOfDay(12, 30, 33) - dur!"minutes"(-7) == TimeOfDay(12, 37, 33));
        assert(TimeOfDay(12, 30, 33) - dur!"minutes"(7) == TimeOfDay(12, 23, 33));
        assert(TimeOfDay(12, 30, 33) - dur!"seconds"(-7) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) - dur!"seconds"(7) == TimeOfDay(12, 30, 26));

        assert(TimeOfDay(12, 30, 33) - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) - dur!"msecs"(7000) == TimeOfDay(12, 30, 26));
        assert(TimeOfDay(12, 30, 33) - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
        assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40));
        assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26));

        auto tod = TimeOfDay(19, 17, 22);
        (tod += dur!"seconds"(9)) += dur!"seconds"(-7292);
        assert(tod == TimeOfDay(17, 15, 59));

        const ctod = TimeOfDay(12, 33, 30);
        immutable itod = TimeOfDay(12, 33, 30);
        static assert(!__traits(compiles, ctod += duration));
        static assert(!__traits(compiles, itod += duration));
        static assert(!__traits(compiles, ctod -= duration));
        static assert(!__traits(compiles, itod -= duration));
    }


    /++
        Gives the difference between two $(LREF TimeOfDay)s.

        The legal types of arithmetic for $(LREF TimeOfDay) using this operator
        are

        $(BOOKTABLE,
        $(TR $(TD TimeOfDay) $(TD -) $(TD TimeOfDay) $(TD -->) $(TD duration))
        )

        Params:
            rhs = The $(LREF TimeOfDay) to subtract from this one.
      +/
    Duration opBinary(string op)(TimeOfDay rhs) const @safe pure nothrow @nogc
        if (op == "-")
    {
        immutable lhsSec = _hour * 3600 + _minute * 60 + _second;
        immutable rhsSec = rhs._hour * 3600 + rhs._minute * 60 + rhs._second;

        import core.time : dur;
        return dur!"seconds"(lhsSec - rhsSec);
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 30, 33);

        import core.time : dur;
        assert(TimeOfDay(7, 12, 52) - TimeOfDay(12, 30, 33) == dur!"seconds"(-19_061));
        assert(TimeOfDay(12, 30, 33) - TimeOfDay(7, 12, 52) == dur!"seconds"(19_061));
        assert(TimeOfDay(12, 30, 33) - TimeOfDay(14, 30, 33) == dur!"seconds"(-7200));
        assert(TimeOfDay(14, 30, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(7200));
        assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 34, 33) == dur!"seconds"(-240));
        assert(TimeOfDay(12, 34, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(240));
        assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 30, 34) == dur!"seconds"(-1));
        assert(TimeOfDay(12, 30, 34) - TimeOfDay(12, 30, 33) == dur!"seconds"(1));

        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(tod - tod == Duration.zero);
        assert(ctod - tod == Duration.zero);
        assert(itod - tod == Duration.zero);

        assert(tod - ctod == Duration.zero);
        assert(ctod - ctod == Duration.zero);
        assert(itod - ctod == Duration.zero);

        assert(tod - itod == Duration.zero);
        assert(ctod - itod == Duration.zero);
        assert(itod - itod == Duration.zero);
    }


    /++
        Converts this $(LREF TimeOfDay) to a string with the format `HHMMSS`.
        If `writer` is set, the resulting string will be written directly to it.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(6);
        try
            toISOString(w);
        catch (Exception e)
            assert(0, "toISOString() threw.");
        return w.data;
    }

    /// ditto
    void toISOString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        formattedWrite(writer, "%02d%02d%02d", _hour, _minute, _second);
    }

    ///
    @safe unittest
    {
        assert(TimeOfDay(0, 0, 0).toISOString() == "000000");
        assert(TimeOfDay(12, 30, 33).toISOString() == "123033");
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 30, 33);
        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(tod.toISOString() == "123033");
        assert(ctod.toISOString() == "123033");
        assert(itod.toISOString() == "123033");
    }


    /++
        Converts this $(LREF TimeOfDay) to a string with the format `HH:MM:SS`.
        If `writer` is set, the resulting string will be written directly to it.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toISOExtString() const @safe pure nothrow
    {
        import std.array : appender;
        auto w = appender!string();
        w.reserve(8);
        try
            toISOExtString(w);
        catch (Exception e)
            assert(0, "toISOExtString() threw.");
        return w.data;
    }

    /// ditto
    void toISOExtString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        import std.format : formattedWrite;
        formattedWrite(writer, "%02d:%02d:%02d", _hour, _minute, _second);
    }

    ///
    @safe unittest
    {
        assert(TimeOfDay(0, 0, 0).toISOExtString() == "00:00:00");
        assert(TimeOfDay(12, 30, 33).toISOExtString() == "12:30:33");
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 30, 33);
        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(tod.toISOExtString() == "12:30:33");
        assert(ctod.toISOExtString() == "12:30:33");
        assert(itod.toISOExtString() == "12:30:33");
    }


    /++
        Converts this TimeOfDay to a string.

        This function exists to make it easy to convert a $(LREF TimeOfDay) to a
        string for code that does not care what the exact format is - just that
        it presents the information in a clear manner. It also makes it easy to
        simply convert a $(LREF TimeOfDay) to a string when using functions such
        as `to!string`, `format`, or `writeln` which use toString to convert
        user-defined types. So, it is unlikely that much code will call
        toString directly.

        The format of the string is purposefully unspecified, and code that
        cares about the format of the string should use `toISOString`,
        `toISOExtString`, or some other custom formatting function that
        explicitly generates the format that the code needs. The reason is that
        the code is then clear about what format it's using, making it less
        error-prone to maintain the code and interact with other software that
        consumes the generated strings. It's for this same reason that
        $(LREF TimeOfDay) has no `fromString` function, whereas it does have
        `fromISOString` and `fromISOExtString`.

        The format returned by toString may or may not change in the future.

        Params:
            writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
        Returns:
            A `string` when not using an output range; `void` otherwise.
      +/
    string toString() const @safe pure nothrow
    {
        return toISOExtString();
    }

    /// ditto
    void toString(W)(ref W writer) const
    if (isOutputRange!(W, char))
    {
        toISOExtString(writer);
    }

    @safe unittest
    {
        auto tod = TimeOfDay(12, 30, 33);
        const ctod = TimeOfDay(12, 30, 33);
        immutable itod = TimeOfDay(12, 30, 33);
        assert(tod.toString());
        assert(ctod.toString());
        assert(itod.toString());
    }


    /++
        Creates a $(LREF TimeOfDay) from a string with the format HHMMSS.
        Whitespace is stripped from the given string.

        Params:
            isoString = A string formatted in the ISO format for times.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO format or if the resulting $(LREF TimeOfDay) would
            not be valid.
      +/
    static TimeOfDay fromISOString(S)(scope const S isoString) @safe pure
        if (isSomeString!S)
    {
        import std.conv : to, text, ConvException;
        import std.exception : enforce;
        import std.string : strip;

        int hours, minutes, seconds;
        auto str = strip(isoString);

        enforce!DateTimeException(str.length == 6, text("Invalid ISO String: ", isoString));

        try
        {
            // cast to int from uint is used because it checks for
            // non digits without extra loops
            hours = cast(int) to!uint(str[0 .. 2]);
            minutes = cast(int) to!uint(str[2 .. 4]);
            seconds = cast(int) to!uint(str[4 .. $]);
        }
        catch (ConvException)
        {
            throw new DateTimeException(text("Invalid ISO String: ", isoString));
        }

        return TimeOfDay(hours, minutes, seconds);
    }

    ///
    @safe unittest
    {
        assert(TimeOfDay.fromISOString("000000") == TimeOfDay(0, 0, 0));
        assert(TimeOfDay.fromISOString("123033") == TimeOfDay(12, 30, 33));
        assert(TimeOfDay.fromISOString(" 123033 ") == TimeOfDay(12, 30, 33));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(TimeOfDay.fromISOString(""));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("00000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("13033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("1277"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12707"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12070"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12303a"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("1230a3"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("123a33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12a033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("1a0033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("a20033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("1200330"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("-120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("+120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("120033am"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("120033pm"));

        assertThrown!DateTimeException(TimeOfDay.fromISOString("0::"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString(":0:"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("::0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("0:00:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:00:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("13:0:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:7"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:07"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:07:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:3a"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:a3"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:3a:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:a0:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("1a:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("a2:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:003:30"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("120:03:30"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("012:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("01:200:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("-12:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("+12:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33am"));
        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33pm"));

        assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33"));

        assert(TimeOfDay.fromISOString("011217") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOString("001412") == TimeOfDay(0, 14, 12));
        assert(TimeOfDay.fromISOString("000007") == TimeOfDay(0, 0, 7));
        assert(TimeOfDay.fromISOString("011217 ") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOString(" 011217") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOString(" 011217 ") == TimeOfDay(1, 12, 17));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(TimeOfDay.fromISOString(to!S("141516")) == TimeOfDay(14, 15, 16));
        }
    }


    /++
        Creates a $(LREF TimeOfDay) from a string with the format HH:MM:SS.
        Whitespace is stripped from the given string.

        Params:
            isoExtString = A string formatted in the ISO Extended format for
            times.

        Throws:
            $(REF DateTimeException,std,datetime,date) if the given string is
            not in the ISO Extended format or if the resulting $(LREF TimeOfDay)
            would not be valid.
      +/
    static TimeOfDay fromISOExtString(S)(scope const S isoExtString) @safe pure
        if (isSomeString!S)
    {
        import std.conv : ConvException, text, to;
        import std.string : strip;

        auto str = strip(isoExtString);
        int hours, minutes, seconds;

        if (str.length != 8 || str[2] != ':' || str[5] != ':')
            throw new DateTimeException(text("Invalid ISO Extended String: ", isoExtString));

        try
        {
            // cast to int from uint is used because it checks for
            // non digits without extra loops
            hours = cast(int) to!uint(str[0 .. 2]);
            minutes = cast(int) to!uint(str[3 .. 5]);
            seconds = cast(int) to!uint(str[6 .. $]);
        }
        catch (ConvException)
        {
            throw new DateTimeException(text("Invalid ISO Extended String: ", isoExtString));
        }

        return TimeOfDay(hours, minutes, seconds);
    }

    ///
    @safe unittest
    {
        assert(TimeOfDay.fromISOExtString("00:00:00") == TimeOfDay(0, 0, 0));
        assert(TimeOfDay.fromISOExtString("12:30:33") == TimeOfDay(12, 30, 33));
        assert(TimeOfDay.fromISOExtString(" 12:30:33 ") == TimeOfDay(12, 30, 33));
    }

    @safe unittest
    {
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString(""));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00000"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1277"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12707"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12070"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12303a"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1230a3"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("123a33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12a033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a0033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a20033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1200330"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+120033"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033am"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033pm"));

        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0::"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString(":0:"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("::0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:00:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:00:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:00"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13:0:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:7"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:07"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:07:0"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:3a"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:a3"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:3a:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:a0:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a2:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:003:30"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120:03:30"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("012:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("01:200:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-12:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+12:00:33"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33am"));
        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33pm"));

        assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033"));

        assert(TimeOfDay.fromISOExtString("01:12:17") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOExtString("00:14:12") == TimeOfDay(0, 14, 12));
        assert(TimeOfDay.fromISOExtString("00:00:07") == TimeOfDay(0, 0, 7));
        assert(TimeOfDay.fromISOExtString("01:12:17 ") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOExtString(" 01:12:17") == TimeOfDay(1, 12, 17));
        assert(TimeOfDay.fromISOExtString(" 01:12:17 ") == TimeOfDay(1, 12, 17));
    }

    // bug# 17801
    @safe unittest
    {
        import std.conv : to;
        import std.meta : AliasSeq;
        static foreach (C; AliasSeq!(char, wchar, dchar))
        {
            static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[]))
                assert(TimeOfDay.fromISOExtString(to!S("14:15:16")) == TimeOfDay(14, 15, 16));
        }
    }


    /++
        Returns midnight.
      +/
    @property static TimeOfDay min() @safe pure nothrow @nogc
    {
        return TimeOfDay.init;
    }

    @safe unittest
    {
        assert(TimeOfDay.min.hour == 0);
        assert(TimeOfDay.min.minute == 0);
        assert(TimeOfDay.min.second == 0);
        assert(TimeOfDay.min < TimeOfDay.max);
    }


    /++
        Returns one second short of midnight.
      +/
    @property static TimeOfDay max() @safe pure nothrow @nogc
    {
        auto tod = TimeOfDay.init;
        tod._hour = maxHour;
        tod._minute = maxMinute;
        tod._second = maxSecond;

        return tod;
    }

    @safe unittest
    {
        assert(TimeOfDay.max.hour == 23);
        assert(TimeOfDay.max.minute == 59);
        assert(TimeOfDay.max.second == 59);
        assert(TimeOfDay.max > TimeOfDay.min);
    }


private:

    /+
        Add seconds to the time of day. Negative values will subtract. If the
        number of seconds overflows (or underflows), then the seconds will wrap,
        increasing (or decreasing) the number of minutes accordingly. If the
        number of minutes overflows (or underflows), then the minutes will wrap.
        If the number of minutes overflows(or underflows), then the hour will
        wrap. (e.g. adding 90 seconds to 23:59:00 would result in 00:00:30).

        Params:
            seconds = The number of seconds to add to this TimeOfDay.
      +/
    ref TimeOfDay _addSeconds(long seconds) return @safe pure nothrow @nogc
    {
        import core.time : convert;
        long hnsecs = convert!("seconds", "hnsecs")(seconds);
        hnsecs += convert!("hours", "hnsecs")(_hour);
        hnsecs += convert!("minutes", "hnsecs")(_minute);
        hnsecs += convert!("seconds", "hnsecs")(_second);

        hnsecs %= convert!("days", "hnsecs")(1);

        if (hnsecs < 0)
            hnsecs += convert!("days", "hnsecs")(1);

        immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs);
        immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
        immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs);

        _hour = cast(ubyte) newHours;
        _minute = cast(ubyte) newMinutes;
        _second = cast(ubyte) newSeconds;

        return this;
    }

    @safe unittest
    {
        static void testTOD(TimeOfDay orig, int seconds, TimeOfDay expected, size_t line = __LINE__)
        {
            orig._addSeconds(seconds);
            assert(orig == expected);
        }

        testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34));
        testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35));
        testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36));
        testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37));
        testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38));
        testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43));
        testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48));
        testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59));
        testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 31, 0));
        testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 31, 3));
        testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 31, 32));
        testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 31, 33));
        testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 31, 34));

        testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 59, 59));
        testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(13, 0, 0));
        testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(13, 0, 1));
        testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(13, 4, 0));
        testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(13, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(13, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(13, 30, 34));
        testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(14, 30, 33));

        testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31));
        testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30));
        testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29));
        testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28));
        testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23));
        testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18));
        testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 29, 59));
        testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 29, 58));
        testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 29, 34));
        testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 29, 33));
        testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 29, 32));

        testTOD(TimeOfDay(12, 30, 33), -1833, TimeOfDay(12, 0, 0));
        testTOD(TimeOfDay(12, 30, 33), -1834, TimeOfDay(11, 59, 59));
        testTOD(TimeOfDay(12, 30, 33), -3600, TimeOfDay(11, 30, 33));
        testTOD(TimeOfDay(12, 30, 33), -3601, TimeOfDay(11, 30, 32));
        testTOD(TimeOfDay(12, 30, 33), -5134, TimeOfDay(11, 4, 59));
        testTOD(TimeOfDay(12, 30, 33), -7200, TimeOfDay(10, 30, 33));

        testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1));
        testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0));
        testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 29, 59));

        testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1));
        testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0));
        testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(11, 59, 59));

        testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1));
        testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0));
        testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(23, 59, 59));

        testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(0, 0, 0));
        testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59));
        testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58));

        const ctod = TimeOfDay(0, 0, 0);
        immutable itod = TimeOfDay(0, 0, 0);
        static assert(!__traits(compiles, ctod._addSeconds(7)));
        static assert(!__traits(compiles, itod._addSeconds(7)));
    }


    /+
        Whether the given values form a valid $(LREF TimeOfDay).
     +/
    static bool _valid(int hour, int minute, int second) @safe pure nothrow @nogc
    {
        return valid!"hours"(hour) && valid!"minutes"(minute) && valid!"seconds"(second);
    }


    @safe pure invariant()
    {
        import std.format : format;
        assert(_valid(_hour, _minute, _second),
               format("Invariant Failure: hour [%s] minute [%s] second [%s]", _hour, _minute, _second));
    }


package:

    ubyte _hour;
    ubyte _minute;
    ubyte _second;

    enum ubyte maxHour   = 24 - 1;
    enum ubyte maxMinute = 60 - 1;
    enum ubyte maxSecond = 60 - 1;
}

///
@safe pure unittest
{
    import core.time : minutes, seconds;

    auto t = TimeOfDay(12, 30, 0);

    t += 10.minutes + 100.seconds;
    assert(t == TimeOfDay(12, 41, 40));

    assert(t.toISOExtString() == "12:41:40");
    assert(t.toISOString() == "124140");

    assert(TimeOfDay.fromISOExtString("15:00:00") == TimeOfDay(15, 0, 0));
    assert(TimeOfDay.fromISOString("015000") == TimeOfDay(1, 50, 0));
}

/++
    Returns whether the given value is valid for the given unit type when in a
    time point. Naturally, a duration is not held to a particular range, but
    the values in a time point are (e.g. a month must be in the range of
    1 - 12 inclusive).

    Params:
        units = The units of time to validate.
        value = The number to validate.
  +/
bool valid(string units)(int value) @safe pure nothrow @nogc
if (units == "months" ||
    units == "hours" ||
    units == "minutes" ||
    units == "seconds")
{
    static if (units == "months")
        return value >= Month.jan && value <= Month.dec;
    else static if (units == "hours")
        return value >= 0 && value <= 23;
    else static if (units == "minutes")
        return value >= 0 && value <= 59;
    else static if (units == "seconds")
        return value >= 0 && value <= 59;
}

///
@safe unittest
{
    assert(valid!"hours"(12));
    assert(!valid!"hours"(32));
    assert(valid!"months"(12));
    assert(!valid!"months"(13));
}

/++
    Returns whether the given day is valid for the given year and month.

    Params:
        units = The units of time to validate.
        year  = The year of the day to validate.
        month = The month of the day to validate (January is 1).
        day   = The day to validate.
  +/
bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc
if (units == "days")
{
    return day > 0 && day <= maxDay(year, month);
}

///
@safe pure nothrow @nogc unittest
{
    assert(valid!"days"(2016, 2, 29));
    assert(!valid!"days"(2016, 2, 30));
    assert(valid!"days"(2017, 2, 20));
    assert(!valid!"days"(2017, 2, 29));
}


/++
    Params:
        units = The units of time to validate.
        value = The number to validate.
        file  = The file that the $(LREF DateTimeException) will list if thrown.
        line  = The line number that the $(LREF DateTimeException) will list if
                thrown.

    Throws:
        $(LREF DateTimeException) if `valid!units(value)` is false.
  +/
void enforceValid(string units)(int value, string file = __FILE__, size_t line = __LINE__) @safe pure
if (units == "months" ||
    units == "hours" ||
    units == "minutes" ||
    units == "seconds")
{
    import std.format : format;

    static if (units == "months")
    {
        if (!valid!units(value))
            throw new DateTimeException(format("%s is not a valid month of the year.", value), file, line);
    }
    else static if (units == "hours")
    {
        if (!valid!units(value))
            throw new DateTimeException(format("%s is not a valid hour of the day.", value), file, line);
    }
    else static if (units == "minutes")
    {
        if (!valid!units(value))
            throw new DateTimeException(format("%s is not a valid minute of an hour.", value), file, line);
    }
    else static if (units == "seconds")
    {
        if (!valid!units(value))
            throw new DateTimeException(format("%s is not a valid second of a minute.", value), file, line);
    }
}

///
@safe pure unittest
{
    import std.exception : assertThrown, assertNotThrown;

    assertNotThrown(enforceValid!"months"(10));
    assertNotThrown(enforceValid!"seconds"(40));

    assertThrown!DateTimeException(enforceValid!"months"(0));
    assertThrown!DateTimeException(enforceValid!"hours"(24));
    assertThrown!DateTimeException(enforceValid!"minutes"(60));
    assertThrown!DateTimeException(enforceValid!"seconds"(60));
}


/++
    Because the validity of the day number depends on both on the year
    and month of which the day is occurring, take all three variables
    to validate the day.

    Params:
        units = The units of time to validate.
        year  = The year of the day to validate.
        month = The month of the day to validate.
        day   = The day to validate.
        file  = The file that the $(LREF DateTimeException) will list if thrown.
        line  = The line number that the $(LREF DateTimeException) will list if
                thrown.

    Throws:
        $(LREF DateTimeException) if $(D valid!"days"(year, month, day)) is false.
  +/
void enforceValid(string units)
                 (int year, Month month, int day, string file = __FILE__, size_t line = __LINE__) @safe pure
if (units == "days")
{
    import std.format : format;
    if (!valid!"days"(year, month, day))
        throw new DateTimeException(format("%s is not a valid day in %s in %s", day, month, year), file, line);
}

///
@safe pure unittest
{
    import std.exception : assertThrown, assertNotThrown;

    assertNotThrown(enforceValid!"days"(2000, Month.jan, 1));
    // leap year
    assertNotThrown(enforceValid!"days"(2000, Month.feb, 29));

    assertThrown!DateTimeException(enforceValid!"days"(2001, Month.feb, 29));
    assertThrown!DateTimeException(enforceValid!"days"(2000, Month.jan, 32));
    assertThrown!DateTimeException(enforceValid!"days"(2000, Month.apr, 31));
}


/++
    Returns the number of days from the current day of the week to the given
    day of the week. If they are the same, then the result is 0.

    Params:
        currDoW = The current day of the week.
        dow     = The day of the week to get the number of days to.
  +/
int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc
{
    if (currDoW == dow)
        return 0;
    if (currDoW < dow)
        return dow - currDoW;
    return DayOfWeek.sat - currDoW + dow + 1;
}

///
@safe pure nothrow @nogc unittest
{
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
}

@safe unittest
{
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5);
    assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6);

    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4);
    assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5);

    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3);
    assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4);

    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2);
    assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3);

    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1);
    assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2);

    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0);
    assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1);

    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6);
    assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0);
}


/++
    Returns the number of months from the current months of the year to the
    given month of the year. If they are the same, then the result is 0.

    Params:
        currMonth = The current month of the year.
        month     = The month of the year to get the number of months to.
  +/
int monthsToMonth(int currMonth, int month) @safe pure
{
    enforceValid!"months"(currMonth);
    enforceValid!"months"(month);

    if (currMonth == month)
        return 0;
    if (currMonth < month)
        return month - currMonth;
    return Month.dec - currMonth + month;
}

///
@safe pure unittest
{
    assert(monthsToMonth(Month.jan, Month.jan) == 0);
    assert(monthsToMonth(Month.jan, Month.dec) == 11);
    assert(monthsToMonth(Month.jul, Month.oct) == 3);
}

@safe unittest
{
    assert(monthsToMonth(Month.jan, Month.jan) == 0);
    assert(monthsToMonth(Month.jan, Month.feb) == 1);
    assert(monthsToMonth(Month.jan, Month.mar) == 2);
    assert(monthsToMonth(Month.jan, Month.apr) == 3);
    assert(monthsToMonth(Month.jan, Month.may) == 4);
    assert(monthsToMonth(Month.jan, Month.jun) == 5);
    assert(monthsToMonth(Month.jan, Month.jul) == 6);
    assert(monthsToMonth(Month.jan, Month.aug) == 7);
    assert(monthsToMonth(Month.jan, Month.sep) == 8);
    assert(monthsToMonth(Month.jan, Month.oct) == 9);
    assert(monthsToMonth(Month.jan, Month.nov) == 10);
    assert(monthsToMonth(Month.jan, Month.dec) == 11);

    assert(monthsToMonth(Month.may, Month.jan) == 8);
    assert(monthsToMonth(Month.may, Month.feb) == 9);
    assert(monthsToMonth(Month.may, Month.mar) == 10);
    assert(monthsToMonth(Month.may, Month.apr) == 11);
    assert(monthsToMonth(Month.may, Month.may) == 0);
    assert(monthsToMonth(Month.may, Month.jun) == 1);
    assert(monthsToMonth(Month.may, Month.jul) == 2);
    assert(monthsToMonth(Month.may, Month.aug) == 3);
    assert(monthsToMonth(Month.may, Month.sep) == 4);
    assert(monthsToMonth(Month.may, Month.oct) == 5);
    assert(monthsToMonth(Month.may, Month.nov) == 6);
    assert(monthsToMonth(Month.may, Month.dec) == 7);

    assert(monthsToMonth(Month.oct, Month.jan) == 3);
    assert(monthsToMonth(Month.oct, Month.feb) == 4);
    assert(monthsToMonth(Month.oct, Month.mar) == 5);
    assert(monthsToMonth(Month.oct, Month.apr) == 6);
    assert(monthsToMonth(Month.oct, Month.may) == 7);
    assert(monthsToMonth(Month.oct, Month.jun) == 8);
    assert(monthsToMonth(Month.oct, Month.jul) == 9);
    assert(monthsToMonth(Month.oct, Month.aug) == 10);
    assert(monthsToMonth(Month.oct, Month.sep) == 11);
    assert(monthsToMonth(Month.oct, Month.oct) == 0);
    assert(monthsToMonth(Month.oct, Month.nov) == 1);
    assert(monthsToMonth(Month.oct, Month.dec) == 2);

    assert(monthsToMonth(Month.dec, Month.jan) == 1);
    assert(monthsToMonth(Month.dec, Month.feb) == 2);
    assert(monthsToMonth(Month.dec, Month.mar) == 3);
    assert(monthsToMonth(Month.dec, Month.apr) == 4);
    assert(monthsToMonth(Month.dec, Month.may) == 5);
    assert(monthsToMonth(Month.dec, Month.jun) == 6);
    assert(monthsToMonth(Month.dec, Month.jul) == 7);
    assert(monthsToMonth(Month.dec, Month.aug) == 8);
    assert(monthsToMonth(Month.dec, Month.sep) == 9);
    assert(monthsToMonth(Month.dec, Month.oct) == 10);
    assert(monthsToMonth(Month.dec, Month.nov) == 11);
    assert(monthsToMonth(Month.dec, Month.dec) == 0);
}


/++
    Whether the given Gregorian Year is a leap year.

    Params:
        year = The year to to be tested.
 +/
bool yearIsLeapYear(int year) @safe pure nothrow @nogc
{
    if (year % 400 == 0)
        return true;
    if (year % 100 == 0)
        return false;
    return year % 4 == 0;
}

///
@safe unittest
{
    foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010])
    {
        assert(!yearIsLeapYear(year));
        assert(!yearIsLeapYear(-year));
    }

    foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
    {
        assert(yearIsLeapYear(year));
        assert(yearIsLeapYear(-year));
    }
}

@safe unittest
{
    import std.format : format;
    foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999,
                    2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011])
    {
        assert(!yearIsLeapYear(year), format("year: %s.", year));
        assert(!yearIsLeapYear(-year), format("year: %s.", year));
    }

    foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012])
    {
        assert(yearIsLeapYear(year), format("year: %s.", year));
        assert(yearIsLeapYear(-year), format("year: %s.", year));
    }
}


/++
    Whether the given type defines all of the necessary functions for it to
    function as a time point.

    1. `T` must define a static property named `min` which is the smallest
       value of `T` as `Unqual!T`.

    2. `T` must define a static property named `max` which is the largest
       value of `T` as `Unqual!T`.

    3. `T` must define an `opBinary` for addition and subtraction that
       accepts $(REF Duration, core,time) and returns `Unqual!T`.

    4. `T` must define an `opOpAssign` for addition and subtraction that
       accepts $(REF Duration, core,time) and returns $(D ref Unqual!T).

    5. `T` must define a `opBinary` for subtraction which accepts `T`
       and returns returns $(REF Duration, core,time).
  +/
template isTimePoint(T)
{
    import core.time : Duration;
    import std.traits : FunctionAttribute, functionAttributes, Unqual;

    enum isTimePoint = hasMin &&
                       hasMax &&
                       hasOverloadedOpBinaryWithDuration &&
                       hasOverloadedOpAssignWithDuration &&
                       hasOverloadedOpBinaryWithSelf &&
                       !is(U == Duration);

private:

    alias U = Unqual!T;

    enum hasMin = __traits(hasMember, T, "min") &&
                  is(typeof(T.min) == U) &&
                  is(typeof({static assert(__traits(isStaticFunction, T.min));}));

    enum hasMax = __traits(hasMember, T, "max") &&
                  is(typeof(T.max) == U) &&
                  is(typeof({static assert(__traits(isStaticFunction, T.max));}));

    enum hasOverloadedOpBinaryWithDuration = is(typeof(T.init + Duration.init) == U) &&
                                             is(typeof(T.init - Duration.init) == U);

    enum hasOverloadedOpAssignWithDuration = is(typeof(U.init += Duration.init) == U) &&
                                             is(typeof(U.init -= Duration.init) == U) &&
                                             is(typeof(
                                             {
                                                 alias add = U.opOpAssign!"+";
                                                 alias sub = U.opOpAssign!"-";
                                                 alias FA = FunctionAttribute;
                                                 static assert((functionAttributes!add & FA.ref_) != 0);
                                                 static assert((functionAttributes!sub & FA.ref_) != 0);
                                             }));

    enum hasOverloadedOpBinaryWithSelf = is(typeof(T.init - T.init) == Duration);
}

///
@safe unittest
{
    import core.time : Duration;
    import std.datetime.interval : Interval;
    import std.datetime.systime : SysTime;

    static assert(isTimePoint!Date);
    static assert(isTimePoint!DateTime);
    static assert(isTimePoint!SysTime);
    static assert(isTimePoint!TimeOfDay);

    static assert(!isTimePoint!int);
    static assert(!isTimePoint!Duration);
    static assert(!isTimePoint!(Interval!SysTime));
}

@safe unittest
{
    import core.time;
    import std.datetime.interval;
    import std.datetime.systime;
    import std.meta : AliasSeq;

    static foreach (TP; AliasSeq!(Date, DateTime, SysTime, TimeOfDay))
    {
        static assert(isTimePoint!(const TP), TP.stringof);
        static assert(isTimePoint!(immutable TP), TP.stringof);
    }

    static foreach (T; AliasSeq!(float, string, Duration, Interval!Date, PosInfInterval!Date, NegInfInterval!Date))
        static assert(!isTimePoint!T, T.stringof);
}


/++
    Whether all of the given strings are valid units of time.

    `"nsecs"` is not considered a valid unit of time. Nothing in std.datetime
    can handle precision greater than hnsecs, and the few functions in core.time
    which deal with "nsecs" deal with it explicitly.
  +/
bool validTimeUnits(string[] units...) @safe pure nothrow @nogc
{
    import std.algorithm.searching : canFind;
    foreach (str; units)
    {
        if (!canFind(timeStrings[], str))
            return false;
    }
    return true;
}

///
@safe @nogc nothrow unittest
{
    assert(validTimeUnits("msecs", "seconds", "minutes"));
    assert(validTimeUnits("days", "weeks", "months"));
    assert(!validTimeUnits("ms", "seconds", "minutes"));
}


/++
    Compares two time unit strings. `"years"` are the largest units and
    `"hnsecs"` are the smallest.

    Returns:
        $(BOOKTABLE,
        $(TR $(TD this &lt; rhs) $(TD &lt; 0))
        $(TR $(TD this == rhs) $(TD 0))
        $(TR $(TD this &gt; rhs) $(TD &gt; 0))
        )

    Throws:
        $(LREF DateTimeException) if either of the given strings is not a valid
        time unit string.
 +/
int cmpTimeUnits(string lhs, string rhs) @safe pure
{
    import std.algorithm.searching : countUntil;
    import std.exception : enforce;
    import std.format : format;

    immutable indexOfLHS = countUntil(timeStrings, lhs);
    immutable indexOfRHS = countUntil(timeStrings, rhs);

    enforce!DateTimeException(indexOfLHS != -1, format("%s is not a valid TimeString", lhs));
    enforce!DateTimeException(indexOfRHS != -1, format("%s is not a valid TimeString", rhs));

    if (indexOfLHS < indexOfRHS)
        return -1;
    if (indexOfLHS > indexOfRHS)
        return 1;

    return 0;
}

///
@safe pure unittest
{
    import std.exception : assertThrown;

    assert(cmpTimeUnits("hours", "hours") == 0);
    assert(cmpTimeUnits("hours", "weeks") < 0);
    assert(cmpTimeUnits("months", "seconds") > 0);

    assertThrown!DateTimeException(cmpTimeUnits("month", "second"));
}

@safe unittest
{
    foreach (i, outerUnits; timeStrings)
    {
        assert(cmpTimeUnits(outerUnits, outerUnits) == 0);

        // For some reason, $ won't compile.
        foreach (innerUnits; timeStrings[i + 1 .. timeStrings.length])
            assert(cmpTimeUnits(outerUnits, innerUnits) == -1);
    }

    foreach (i, outerUnits; timeStrings)
    {
        foreach (innerUnits; timeStrings[0 .. i])
            assert(cmpTimeUnits(outerUnits, innerUnits) == 1);
    }
}


/++
    Compares two time unit strings at compile time. `"years"` are the largest
    units and `"hnsecs"` are the smallest.

    This template is used instead of `cmpTimeUnits` because exceptions
    can't be thrown at compile time and `cmpTimeUnits` must enforce that
    the strings it's given are valid time unit strings. This template uses a
    template constraint instead.

    Returns:
        $(BOOKTABLE,
        $(TR $(TD this &lt; rhs) $(TD &lt; 0))
        $(TR $(TD this == rhs) $(TD 0))
        $(TR $(TD this &gt; rhs) $(TD &gt; 0))
        )
 +/
template CmpTimeUnits(string lhs, string rhs)
if (validTimeUnits(lhs, rhs))
{
    enum CmpTimeUnits = cmpTimeUnitsCTFE(lhs, rhs);
}

///
@safe pure unittest
{
    static assert(CmpTimeUnits!("years", "weeks") > 0);
    static assert(CmpTimeUnits!("days", "days") == 0);
    static assert(CmpTimeUnits!("seconds", "hours") < 0);
}

// Helper function for CmpTimeUnits.
private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow @nogc
{
    import std.algorithm.searching : countUntil;
    auto tstrings = timeStrings;
    immutable indexOfLHS = countUntil(tstrings, lhs);
    immutable indexOfRHS = countUntil(tstrings, rhs);

    if (indexOfLHS < indexOfRHS)
        return -1;
    if (indexOfLHS > indexOfRHS)
        return 1;

    return 0;
}

@safe unittest
{
    static foreach (i; 0 .. timeStrings.length)
    {
        static assert(CmpTimeUnits!(timeStrings[i], timeStrings[i]) == 0);

        static foreach (next; timeStrings[i + 1 .. $])
            static assert(CmpTimeUnits!(timeStrings[i], next) == -1);

        static foreach (prev; timeStrings[0 .. i])
            static assert(CmpTimeUnits!(timeStrings[i], prev) == 1);
    }
}


package:


/+
    Array of the short (three letter) names of each month.
  +/
immutable string[12] _monthNames = ["Jan",
                                    "Feb",
                                    "Mar",
                                    "Apr",
                                    "May",
                                    "Jun",
                                    "Jul",
                                    "Aug",
                                    "Sep",
                                    "Oct",
                                    "Nov",
                                    "Dec"];

/+
    The maximum valid Day in the given month in the given year.

    Params:
        year  = The year to get the day for.
        month = The month of the Gregorian Calendar to get the day for.
 +/
ubyte maxDay(int year, int month) @safe pure nothrow @nogc
in
{
    assert(valid!"months"(month));
}
do
{
    switch (month)
    {
        case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec:
            return 31;
        case Month.feb:
            return yearIsLeapYear(year) ? 29 : 28;
        case Month.apr, Month.jun, Month.sep, Month.nov:
            return 30;
        default:
            assert(0, "Invalid month.");
    }
}

@safe unittest
{
    // Test A.D.
    assert(maxDay(1999, 1) == 31);
    assert(maxDay(1999, 2) == 28);
    assert(maxDay(1999, 3) == 31);
    assert(maxDay(1999, 4) == 30);
    assert(maxDay(1999, 5) == 31);
    assert(maxDay(1999, 6) == 30);
    assert(maxDay(1999, 7) == 31);
    assert(maxDay(1999, 8) == 31);
    assert(maxDay(1999, 9) == 30);
    assert(maxDay(1999, 10) == 31);
    assert(maxDay(1999, 11) == 30);
    assert(maxDay(1999, 12) == 31);

    assert(maxDay(2000, 1) == 31);
    assert(maxDay(2000, 2) == 29);
    assert(maxDay(2000, 3) == 31);
    assert(maxDay(2000, 4) == 30);
    assert(maxDay(2000, 5) == 31);
    assert(maxDay(2000, 6) == 30);
    assert(maxDay(2000, 7) == 31);
    assert(maxDay(2000, 8) == 31);
    assert(maxDay(2000, 9) == 30);
    assert(maxDay(2000, 10) == 31);
    assert(maxDay(2000, 11) == 30);
    assert(maxDay(2000, 12) == 31);

    // Test B.C.
    assert(maxDay(-1999, 1) == 31);
    assert(maxDay(-1999, 2) == 28);
    assert(maxDay(-1999, 3) == 31);
    assert(maxDay(-1999, 4) == 30);
    assert(maxDay(-1999, 5) == 31);
    assert(maxDay(-1999, 6) == 30);
    assert(maxDay(-1999, 7) == 31);
    assert(maxDay(-1999, 8) == 31);
    assert(maxDay(-1999, 9) == 30);
    assert(maxDay(-1999, 10) == 31);
    assert(maxDay(-1999, 11) == 30);
    assert(maxDay(-1999, 12) == 31);

    assert(maxDay(-2000, 1) == 31);
    assert(maxDay(-2000, 2) == 29);
    assert(maxDay(-2000, 3) == 31);
    assert(maxDay(-2000, 4) == 30);
    assert(maxDay(-2000, 5) == 31);
    assert(maxDay(-2000, 6) == 30);
    assert(maxDay(-2000, 7) == 31);
    assert(maxDay(-2000, 8) == 31);
    assert(maxDay(-2000, 9) == 30);
    assert(maxDay(-2000, 10) == 31);
    assert(maxDay(-2000, 11) == 30);
    assert(maxDay(-2000, 12) == 31);
}

/+
    Splits out a particular unit from hnsecs and gives the value for that
    unit and the remaining hnsecs. It really shouldn't be used unless unless
    all units larger than the given units have already been split out.

    Params:
        units  = The units to split out.
        hnsecs = The current total hnsecs. Upon returning, it is the hnsecs left
                 after splitting out the given units.

    Returns:
        The number of the given units from converting hnsecs to those units.
  +/
long splitUnitsFromHNSecs(string units)(ref long hnsecs) @safe pure nothrow @nogc
if (validTimeUnits(units) && CmpTimeUnits!(units, "months") < 0)
{
    import core.time : convert;
    immutable value = convert!("hnsecs", units)(hnsecs);
    hnsecs -= convert!(units, "hnsecs")(value);
    return value;
}

@safe unittest
{
    auto hnsecs = 2595000000007L;
    immutable days = splitUnitsFromHNSecs!"days"(hnsecs);
    assert(days == 3);
    assert(hnsecs == 3000000007);

    immutable minutes = splitUnitsFromHNSecs!"minutes"(hnsecs);
    assert(minutes == 5);
    assert(hnsecs == 7);
}


/+
    Returns the day of the week for the given day of the Gregorian Calendar.

    Params:
        day = The day of the Gregorian Calendar for which to get the day of
              the week.
  +/
DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc
{
    // January 1st, 1 A.D. was a Monday
    if (day >= 0)
        return cast(DayOfWeek)(day % 7);
    else
    {
        immutable dow = cast(DayOfWeek)((day % 7) + 7);

        if (dow == 7)
            return DayOfWeek.sun;
        else
            return dow;
    }
}

@safe unittest
{
    import std.datetime.systime : SysTime;

    // Test A.D.
    assert(getDayOfWeek(SysTime(Date(1, 1, 1)).dayOfGregorianCal) == DayOfWeek.mon);
    assert(getDayOfWeek(SysTime(Date(1, 1, 2)).dayOfGregorianCal) == DayOfWeek.tue);
    assert(getDayOfWeek(SysTime(Date(1, 1, 3)).dayOfGregorianCal) == DayOfWeek.wed);
    assert(getDayOfWeek(SysTime(Date(1, 1, 4)).dayOfGregorianCal) == DayOfWeek.thu);
    assert(getDayOfWeek(SysTime(Date(1, 1, 5)).dayOfGregorianCal) == DayOfWeek.fri);
    assert(getDayOfWeek(SysTime(Date(1, 1, 6)).dayOfGregorianCal) == DayOfWeek.sat);
    assert(getDayOfWeek(SysTime(Date(1, 1, 7)).dayOfGregorianCal) == DayOfWeek.sun);
    assert(getDayOfWeek(SysTime(Date(1, 1, 8)).dayOfGregorianCal) == DayOfWeek.mon);
    assert(getDayOfWeek(SysTime(Date(1, 1, 9)).dayOfGregorianCal) == DayOfWeek.tue);
    assert(getDayOfWeek(SysTime(Date(2, 1, 1)).dayOfGregorianCal) == DayOfWeek.tue);
    assert(getDayOfWeek(SysTime(Date(3, 1, 1)).dayOfGregorianCal) == DayOfWeek.wed);
    assert(getDayOfWeek(SysTime(Date(4, 1, 1)).dayOfGregorianCal) == DayOfWeek.thu);
    assert(getDayOfWeek(SysTime(Date(5, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat);
    assert(getDayOfWeek(SysTime(Date(2000, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 22)).dayOfGregorianCal) == DayOfWeek.sun);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 23)).dayOfGregorianCal) == DayOfWeek.mon);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 24)).dayOfGregorianCal) == DayOfWeek.tue);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 25)).dayOfGregorianCal) == DayOfWeek.wed);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 26)).dayOfGregorianCal) == DayOfWeek.thu);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 27)).dayOfGregorianCal) == DayOfWeek.fri);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 28)).dayOfGregorianCal) == DayOfWeek.sat);
    assert(getDayOfWeek(SysTime(Date(2010, 8, 29)).dayOfGregorianCal) == DayOfWeek.sun);

    // Test B.C.
    assert(getDayOfWeek(SysTime(Date(0, 12, 31)).dayOfGregorianCal) == DayOfWeek.sun);
    assert(getDayOfWeek(SysTime(Date(0, 12, 30)).dayOfGregorianCal) == DayOfWeek.sat);
    assert(getDayOfWeek(SysTime(Date(0, 12, 29)).dayOfGregorianCal) == DayOfWeek.fri);
    assert(getDayOfWeek(SysTime(Date(0, 12, 28)).dayOfGregorianCal) == DayOfWeek.thu);
    assert(getDayOfWeek(SysTime(Date(0, 12, 27)).dayOfGregorianCal) == DayOfWeek.wed);
    assert(getDayOfWeek(SysTime(Date(0, 12, 26)).dayOfGregorianCal) == DayOfWeek.tue);
    assert(getDayOfWeek(SysTime(Date(0, 12, 25)).dayOfGregorianCal) == DayOfWeek.mon);
    assert(getDayOfWeek(SysTime(Date(0, 12, 24)).dayOfGregorianCal) == DayOfWeek.sun);
    assert(getDayOfWeek(SysTime(Date(0, 12, 23)).dayOfGregorianCal) == DayOfWeek.sat);
}


private:

enum daysInYear     = 365;  // The number of days in a non-leap year.
enum daysInLeapYear = 366;  // The numbef or days in a leap year.
enum daysIn4Years   = daysInYear * 3 + daysInLeapYear;  // Number of days in 4 years.
enum daysIn100Years = daysIn4Years * 25 - 1;  // The number of days in 100 years.
enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years.

/+
    Array of integers representing the last days of each month in a year.
  +/
immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];

/+
    Array of integers representing the last days of each month in a leap year.
  +/
immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];


/+
    Returns the string representation of the given month.
  +/
string monthToString(Month month) @safe pure
{
    import std.format : format;
    assert(month >= Month.jan && month <= Month.dec, format("Invalid month: %s", month));
    return _monthNames[month - Month.jan];
}

@safe unittest
{
    assert(monthToString(Month.jan) == "Jan");
    assert(monthToString(Month.feb) == "Feb");
    assert(monthToString(Month.mar) == "Mar");
    assert(monthToString(Month.apr) == "Apr");
    assert(monthToString(Month.may) == "May");
    assert(monthToString(Month.jun) == "Jun");
    assert(monthToString(Month.jul) == "Jul");
    assert(monthToString(Month.aug) == "Aug");
    assert(monthToString(Month.sep) == "Sep");
    assert(monthToString(Month.oct) == "Oct");
    assert(monthToString(Month.nov) == "Nov");
    assert(monthToString(Month.dec) == "Dec");
}


/+
    Returns the Month corresponding to the given string.

    Params:
        monthStr = The string representation of the month to get the Month for.

    Throws:
        $(REF DateTimeException,std,datetime,date) if the given month is not a
        valid month string.
  +/
Month monthFromString(T)(T monthStr) @safe pure
if (isSomeString!T)
{
    import std.format : format;
    switch (monthStr)
    {
        case "Jan":
            return Month.jan;
        case "Feb":
            return Month.feb;
        case "Mar":
            return Month.mar;
        case "Apr":
            return Month.apr;
        case "May":
            return Month.may;
        case "Jun":
            return Month.jun;
        case "Jul":
            return Month.jul;
        case "Aug":
            return Month.aug;
        case "Sep":
            return Month.sep;
        case "Oct":
            return Month.oct;
        case "Nov":
            return Month.nov;
        case "Dec":
            return Month.dec;
        default:
            throw new DateTimeException(format!"Invalid month %s"(monthStr));
    }
}

@safe unittest
{
    import std.conv : to;
    import std.traits : EnumMembers;
    foreach (badStr; ["Ja", "Janu", "Januar", "Januarys", "JJanuary", "JANUARY",
                      "JAN", "january", "jaNuary", "jaN", "jaNuaRy", "jAn"])
    {
        assertThrown!DateTimeException(monthFromString(badStr), badStr);
    }

    foreach (month; EnumMembers!Month)
    {
        assert(monthFromString(monthToString(month)) == month, month.to!string);
    }
}


version (unittest)
{
private:
    // All of these helper arrays are sorted in ascending order.
    auto testYearsBC = [-1999, -1200, -600, -4, -1, 0];
    auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012];

    // I'd use a Tuple, but I get forward reference errors if I try.
    struct MonthDay
    {
        Month month;
        short day;

        this(int m, short d)
        {
            month = cast(Month) m;
            day = d;
        }
    }

    MonthDay[] testMonthDays = [MonthDay(1, 1),
                                MonthDay(1, 2),
                                MonthDay(3, 17),
                                MonthDay(7, 4),
                                MonthDay(10, 27),
                                MonthDay(12, 30),
                                MonthDay(12, 31)];

    auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31];

    auto testTODs = [TimeOfDay(0, 0, 0),
                     TimeOfDay(0, 0, 1),
                     TimeOfDay(0, 1, 0),
                     TimeOfDay(1, 0, 0),
                     TimeOfDay(13, 13, 13),
                     TimeOfDay(23, 59, 59)];

    auto testHours = [0, 1, 12, 22, 23];
    auto testMinSecs = [0, 1, 30, 58, 59];

    // Throwing exceptions is incredibly expensive, so we want to use a smaller
    // set of values for tests using assertThrown.
    auto testTODsThrown = [TimeOfDay(0, 0, 0),
                           TimeOfDay(13, 13, 13),
                           TimeOfDay(23, 59, 59)];

    Date[] testDatesBC;
    Date[] testDatesAD;

    DateTime[] testDateTimesBC;
    DateTime[] testDateTimesAD;

    // I'd use a Tuple, but I get forward reference errors if I try.
    struct GregDay { int day; Date date; }
    auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar
                           GregDay(-735_233, Date(-2012, 1, 1)),
                           GregDay(-735_202, Date(-2012, 2, 1)),
                           GregDay(-735_175, Date(-2012, 2, 28)),
                           GregDay(-735_174, Date(-2012, 2, 29)),
                           GregDay(-735_173, Date(-2012, 3, 1)),
                           GregDay(-734_502, Date(-2010, 1, 1)),
                           GregDay(-734_472, Date(-2010, 1, 31)),
                           GregDay(-734_471, Date(-2010, 2, 1)),
                           GregDay(-734_444, Date(-2010, 2, 28)),
                           GregDay(-734_443, Date(-2010, 3, 1)),
                           GregDay(-734_413, Date(-2010, 3, 31)),
                           GregDay(-734_412, Date(-2010, 4, 1)),
                           GregDay(-734_383, Date(-2010, 4, 30)),
                           GregDay(-734_382, Date(-2010, 5, 1)),
                           GregDay(-734_352, Date(-2010, 5, 31)),
                           GregDay(-734_351, Date(-2010, 6, 1)),
                           GregDay(-734_322, Date(-2010, 6, 30)),
                           GregDay(-734_321, Date(-2010, 7, 1)),
                           GregDay(-734_291, Date(-2010, 7, 31)),
                           GregDay(-734_290, Date(-2010, 8, 1)),
                           GregDay(-734_260, Date(-2010, 8, 31)),
                           GregDay(-734_259, Date(-2010, 9, 1)),
                           GregDay(-734_230, Date(-2010, 9, 30)),
                           GregDay(-734_229, Date(-2010, 10, 1)),
                           GregDay(-734_199, Date(-2010, 10, 31)),
                           GregDay(-734_198, Date(-2010, 11, 1)),
                           GregDay(-734_169, Date(-2010, 11, 30)),
                           GregDay(-734_168, Date(-2010, 12, 1)),
                           GregDay(-734_139, Date(-2010, 12, 30)),
                           GregDay(-734_138, Date(-2010, 12, 31)),
                           GregDay(-731_215, Date(-2001, 1, 1)),
                           GregDay(-730_850, Date(-2000, 1, 1)),
                           GregDay(-730_849, Date(-2000, 1, 2)),
                           GregDay(-730_486, Date(-2000, 12, 30)),
                           GregDay(-730_485, Date(-2000, 12, 31)),
                           GregDay(-730_484, Date(-1999, 1, 1)),
                           GregDay(-694_690, Date(-1901, 1, 1)),
                           GregDay(-694_325, Date(-1900, 1, 1)),
                           GregDay(-585_118, Date(-1601, 1, 1)),
                           GregDay(-584_753, Date(-1600, 1, 1)),
                           GregDay(-584_388, Date(-1600, 12, 31)),
                           GregDay(-584_387, Date(-1599, 1, 1)),
                           GregDay(-365_972, Date(-1001, 1, 1)),
                           GregDay(-365_607, Date(-1000, 1, 1)),
                           GregDay(-183_351, Date(-501, 1, 1)),
                           GregDay(-182_986, Date(-500, 1, 1)),
                           GregDay(-182_621, Date(-499, 1, 1)),
                           GregDay(-146_827, Date(-401, 1, 1)),
                           GregDay(-146_462, Date(-400, 1, 1)),
                           GregDay(-146_097, Date(-400, 12, 31)),
                           GregDay(-110_302, Date(-301, 1, 1)),
                           GregDay(-109_937, Date(-300, 1, 1)),
                           GregDay(-73_778, Date(-201, 1, 1)),
                           GregDay(-73_413, Date(-200, 1, 1)),
                           GregDay(-38_715, Date(-105, 1, 1)),
                           GregDay(-37_254, Date(-101, 1, 1)),
                           GregDay(-36_889, Date(-100, 1, 1)),
                           GregDay(-36_524, Date(-99, 1, 1)),
                           GregDay(-36_160, Date(-99, 12, 31)),
                           GregDay(-35_794, Date(-97, 1, 1)),
                           GregDay(-18_627, Date(-50, 1, 1)),
                           GregDay(-18_262, Date(-49, 1, 1)),
                           GregDay(-3652, Date(-9, 1, 1)),
                           GregDay(-2191, Date(-5, 1, 1)),
                           GregDay(-1827, Date(-5, 12, 31)),
                           GregDay(-1826, Date(-4, 1, 1)),
                           GregDay(-1825, Date(-4, 1, 2)),
                           GregDay(-1462, Date(-4, 12, 30)),
                           GregDay(-1461, Date(-4, 12, 31)),
                           GregDay(-1460, Date(-3, 1, 1)),
                           GregDay(-1096, Date(-3, 12, 31)),
                           GregDay(-1095, Date(-2, 1, 1)),
                           GregDay(-731, Date(-2, 12, 31)),
                           GregDay(-730, Date(-1, 1, 1)),
                           GregDay(-367, Date(-1, 12, 30)),
                           GregDay(-366, Date(-1, 12, 31)),
                           GregDay(-365, Date(0, 1, 1)),
                           GregDay(-31, Date(0, 11, 30)),
                           GregDay(-30, Date(0, 12, 1)),
                           GregDay(-1, Date(0, 12, 30)),
                           GregDay(0, Date(0, 12, 31))];

    auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)),
                           GregDay(2, Date(1, 1, 2)),
                           GregDay(32, Date(1, 2, 1)),
                           GregDay(365, Date(1, 12, 31)),
                           GregDay(366, Date(2, 1, 1)),
                           GregDay(731, Date(3, 1, 1)),
                           GregDay(1096, Date(4, 1, 1)),
                           GregDay(1097, Date(4, 1, 2)),
                           GregDay(1460, Date(4, 12, 30)),
                           GregDay(1461, Date(4, 12, 31)),
                           GregDay(1462, Date(5, 1, 1)),
                           GregDay(17_898, Date(50, 1, 1)),
                           GregDay(35_065, Date(97, 1, 1)),
                           GregDay(36_160, Date(100, 1, 1)),
                           GregDay(36_525, Date(101, 1, 1)),
                           GregDay(37_986, Date(105, 1, 1)),
                           GregDay(72_684, Date(200, 1, 1)),
                           GregDay(73_049, Date(201, 1, 1)),
                           GregDay(109_208, Date(300, 1, 1)),
                           GregDay(109_573, Date(301, 1, 1)),
                           GregDay(145_732, Date(400, 1, 1)),
                           GregDay(146_098, Date(401, 1, 1)),
                           GregDay(182_257, Date(500, 1, 1)),
                           GregDay(182_622, Date(501, 1, 1)),
                           GregDay(364_878, Date(1000, 1, 1)),
                           GregDay(365_243, Date(1001, 1, 1)),
                           GregDay(584_023, Date(1600, 1, 1)),
                           GregDay(584_389, Date(1601, 1, 1)),
                           GregDay(693_596, Date(1900, 1, 1)),
                           GregDay(693_961, Date(1901, 1, 1)),
                           GregDay(729_755, Date(1999, 1, 1)),
                           GregDay(730_120, Date(2000, 1, 1)),
                           GregDay(730_121, Date(2000, 1, 2)),
                           GregDay(730_484, Date(2000, 12, 30)),
                           GregDay(730_485, Date(2000, 12, 31)),
                           GregDay(730_486, Date(2001, 1, 1)),
                           GregDay(733_773, Date(2010, 1, 1)),
                           GregDay(733_774, Date(2010, 1, 2)),
                           GregDay(733_803, Date(2010, 1, 31)),
                           GregDay(733_804, Date(2010, 2, 1)),
                           GregDay(733_831, Date(2010, 2, 28)),
                           GregDay(733_832, Date(2010, 3, 1)),
                           GregDay(733_862, Date(2010, 3, 31)),
                           GregDay(733_863, Date(2010, 4, 1)),
                           GregDay(733_892, Date(2010, 4, 30)),
                           GregDay(733_893, Date(2010, 5, 1)),
                           GregDay(733_923, Date(2010, 5, 31)),
                           GregDay(733_924, Date(2010, 6, 1)),
                           GregDay(733_953, Date(2010, 6, 30)),
                           GregDay(733_954, Date(2010, 7, 1)),
                           GregDay(733_984, Date(2010, 7, 31)),
                           GregDay(733_985, Date(2010, 8, 1)),
                           GregDay(734_015, Date(2010, 8, 31)),
                           GregDay(734_016, Date(2010, 9, 1)),
                           GregDay(734_045, Date(2010, 9, 30)),
                           GregDay(734_046, Date(2010, 10, 1)),
                           GregDay(734_076, Date(2010, 10, 31)),
                           GregDay(734_077, Date(2010, 11, 1)),
                           GregDay(734_106, Date(2010, 11, 30)),
                           GregDay(734_107, Date(2010, 12, 1)),
                           GregDay(734_136, Date(2010, 12, 30)),
                           GregDay(734_137, Date(2010, 12, 31)),
                           GregDay(734_503, Date(2012, 1, 1)),
                           GregDay(734_534, Date(2012, 2, 1)),
                           GregDay(734_561, Date(2012, 2, 28)),
                           GregDay(734_562, Date(2012, 2, 29)),
                           GregDay(734_563, Date(2012, 3, 1)),
                           GregDay(734_858, Date(2012, 12, 21))];

    // I'd use a Tuple, but I get forward reference errors if I try.
    struct DayOfYear { int day; MonthDay md; }
    auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)),
                           DayOfYear(2, MonthDay(1, 2)),
                           DayOfYear(3, MonthDay(1, 3)),
                           DayOfYear(31, MonthDay(1, 31)),
                           DayOfYear(32, MonthDay(2, 1)),
                           DayOfYear(59, MonthDay(2, 28)),
                           DayOfYear(60, MonthDay(3, 1)),
                           DayOfYear(90, MonthDay(3, 31)),
                           DayOfYear(91, MonthDay(4, 1)),
                           DayOfYear(120, MonthDay(4, 30)),
                           DayOfYear(121, MonthDay(5, 1)),
                           DayOfYear(151, MonthDay(5, 31)),
                           DayOfYear(152, MonthDay(6, 1)),
                           DayOfYear(181, MonthDay(6, 30)),
                           DayOfYear(182, MonthDay(7, 1)),
                           DayOfYear(212, MonthDay(7, 31)),
                           DayOfYear(213, MonthDay(8, 1)),
                           DayOfYear(243, MonthDay(8, 31)),
                           DayOfYear(244, MonthDay(9, 1)),
                           DayOfYear(273, MonthDay(9, 30)),
                           DayOfYear(274, MonthDay(10, 1)),
                           DayOfYear(304, MonthDay(10, 31)),
                           DayOfYear(305, MonthDay(11, 1)),
                           DayOfYear(334, MonthDay(11, 30)),
                           DayOfYear(335, MonthDay(12, 1)),
                           DayOfYear(363, MonthDay(12, 29)),
                           DayOfYear(364, MonthDay(12, 30)),
                           DayOfYear(365, MonthDay(12, 31))];

    auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)),
                               DayOfYear(2, MonthDay(1, 2)),
                               DayOfYear(3, MonthDay(1, 3)),
                               DayOfYear(31, MonthDay(1, 31)),
                               DayOfYear(32, MonthDay(2, 1)),
                               DayOfYear(59, MonthDay(2, 28)),
                               DayOfYear(60, MonthDay(2, 29)),
                               DayOfYear(61, MonthDay(3, 1)),
                               DayOfYear(91, MonthDay(3, 31)),
                               DayOfYear(92, MonthDay(4, 1)),
                               DayOfYear(121, MonthDay(4, 30)),
                               DayOfYear(122, MonthDay(5, 1)),
                               DayOfYear(152, MonthDay(5, 31)),
                               DayOfYear(153, MonthDay(6, 1)),
                               DayOfYear(182, MonthDay(6, 30)),
                               DayOfYear(183, MonthDay(7, 1)),
                               DayOfYear(213, MonthDay(7, 31)),
                               DayOfYear(214, MonthDay(8, 1)),
                               DayOfYear(244, MonthDay(8, 31)),
                               DayOfYear(245, MonthDay(9, 1)),
                               DayOfYear(274, MonthDay(9, 30)),
                               DayOfYear(275, MonthDay(10, 1)),
                               DayOfYear(305, MonthDay(10, 31)),
                               DayOfYear(306, MonthDay(11, 1)),
                               DayOfYear(335, MonthDay(11, 30)),
                               DayOfYear(336, MonthDay(12, 1)),
                               DayOfYear(364, MonthDay(12, 29)),
                               DayOfYear(365, MonthDay(12, 30)),
                               DayOfYear(366, MonthDay(12, 31))];

    void initializeTests() @safe
    {
        foreach (year; testYearsBC)
        {
            foreach (md; testMonthDays)
                testDatesBC ~= Date(year, md.month, md.day);
        }

        foreach (year; testYearsAD)
        {
            foreach (md; testMonthDays)
                testDatesAD ~= Date(year, md.month, md.day);
        }

        foreach (dt; testDatesBC)
        {
            foreach (tod; testTODs)
                testDateTimesBC ~= DateTime(dt, tod);
        }

        foreach (dt; testDatesAD)
        {
            foreach (tod; testTODs)
                testDateTimesAD ~= DateTime(dt, tod);
        }
    }
}
